refactor: 重构数据库配置为SQLite开发环境并移除冗余文档

This commit is contained in:
2025-09-21 15:16:48 +08:00
parent d207610009
commit 3c8648a635
259 changed files with 88239 additions and 8379 deletions

View File

@@ -1,80 +1,418 @@
// 牛肉商城小程序
App({
onLaunch() {
// 小程序初始化
console.log('牛肉商城小程序初始化');
// 检查登录状态
this.checkLoginStatus();
},
onShow() {
// 小程序显示
console.log('牛肉商城小程序显示');
},
onHide() {
// 小程序隐藏
console.log('牛肉商城小程序隐藏');
},
onError(msg) {
// 错误处理
console.log('小程序发生错误:', msg);
},
globalData: {
userInfo: null,
token: null,
baseUrl: 'http://localhost:8000/api'
apiBase: 'https://api.xlxumu.com',
version: '1.0.0',
cart: [],
cartCount: 0
},
// 检查登录状态
checkLoginStatus() {
try {
const token = wx.getStorageSync('token');
if (token) {
this.globalData.token = token;
// 验证token有效性
this.verifyToken(token);
}
} catch (e) {
console.error('检查登录状态失败:', e);
onLaunch() {
console.log('牛肉商城小程序启动');
// 检查更新
this.checkForUpdate();
// 初始化用户信息
this.initUserInfo();
// 初始化购物车
this.initCart();
// 设置网络状态监听
this.setupNetworkListener();
},
onShow() {
console.log('牛肉商城小程序显示');
},
onHide() {
console.log('牛肉商城小程序隐藏');
},
onError(msg) {
console.error('小程序错误:', msg);
},
// 检查小程序更新
checkForUpdate() {
if (wx.canIUse('getUpdateManager')) {
const updateManager = wx.getUpdateManager();
updateManager.onCheckForUpdate((res) => {
if (res.hasUpdate) {
console.log('发现新版本');
}
});
updateManager.onUpdateReady(() => {
wx.showModal({
title: '更新提示',
content: '新版本已经准备好,是否重启应用?',
success: (res) => {
if (res.confirm) {
updateManager.applyUpdate();
}
}
});
});
updateManager.onUpdateFailed(() => {
console.error('新版本下载失败');
});
}
},
// 验证token
verifyToken(token) {
wx.request({
url: `${this.globalData.baseUrl}/auth/verify`,
method: 'GET',
header: {
'Authorization': `Bearer ${token}`
},
success: (res) => {
if (res.data.valid) {
this.globalData.userInfo = res.data.user;
} else {
// token无效清除本地存储
wx.removeStorageSync('token');
this.globalData.token = null;
this.globalData.userInfo = null;
}
},
fail: (err) => {
console.error('验证token失败', err);
// 初始化用户信息
initUserInfo() {
try {
const token = wx.getStorageSync('token');
const userInfo = wx.getStorageSync('userInfo');
if (token && userInfo) {
this.globalData.token = token;
this.globalData.userInfo = userInfo;
}
} catch (error) {
console.error('初始化用户信息失败:', error);
}
},
// 初始化购物车
initCart() {
try {
const cart = wx.getStorageSync('cart') || [];
this.globalData.cart = cart;
this.updateCartCount();
} catch (error) {
console.error('初始化购物车失败:', error);
}
},
// 设置网络状态监听
setupNetworkListener() {
wx.onNetworkStatusChange((res) => {
if (!res.isConnected) {
this.showError('网络连接已断开');
}
});
},
// 登录方法
login(userInfo) {
this.globalData.userInfo = userInfo;
// 微信登录
async wxLogin() {
try {
// 检查登录状态
const loginRes = await this.checkSession();
if (loginRes) {
return this.globalData.userInfo;
}
} catch (error) {
console.log('登录状态已过期,重新登录');
}
try {
// 获取登录code
const { code } = await this.promisify(wx.login)();
// 获取用户信息
const userProfile = await this.getUserProfile();
// 发送登录请求
const response = await this.request({
url: '/auth/wechat/login',
method: 'POST',
data: {
code,
encrypted_data: userProfile.encryptedData,
iv: userProfile.iv
}
});
const { token, user } = response.data;
// 保存用户信息
this.globalData.token = token;
this.globalData.userInfo = user;
wx.setStorageSync('token', token);
wx.setStorageSync('userInfo', user);
return user;
} catch (error) {
console.error('微信登录失败:', error);
throw error;
}
},
// 登出方法
logout() {
this.globalData.userInfo = null;
// 检查登录状态
checkSession() {
return new Promise((resolve, reject) => {
wx.checkSession({
success: () => {
if (this.globalData.userInfo) {
resolve(this.globalData.userInfo);
} else {
reject(new Error('用户信息不存在'));
}
},
fail: reject
});
});
},
// 获取用户信息
getUserProfile() {
return new Promise((resolve, reject) => {
wx.getUserProfile({
desc: '用于完善用户资料',
success: resolve,
fail: reject
});
});
},
// 网络请求封装
async request(options) {
const {
url,
method = 'GET',
data = {},
header = {}
} = options;
// 添加认证头
if (this.globalData.token) {
header.Authorization = `Bearer ${this.globalData.token}`;
}
try {
const response = await this.promisify(wx.request)({
url: this.globalData.apiBase + url,
method,
data,
header: {
'Content-Type': 'application/json',
...header
}
});
const { statusCode, data: responseData } = response;
if (statusCode === 200) {
if (responseData.code === 0) {
return responseData;
} else {
throw new Error(responseData.message || '请求失败');
}
} else if (statusCode === 401) {
// 登录过期,清除用户信息
this.clearUserInfo();
wx.navigateTo({
url: '/pages/auth/login'
});
throw new Error('登录已过期,请重新登录');
} else {
throw new Error(`请求失败:${statusCode}`);
}
} catch (error) {
console.error('请求错误:', error);
throw error;
}
},
// 清除用户信息
clearUserInfo() {
this.globalData.token = null;
this.globalData.userInfo = null;
wx.removeStorageSync('token');
wx.removeStorageSync('userInfo');
},
// 购物车管理
addToCart(product, quantity = 1, specs = {}) {
const cartItem = {
id: product.id,
title: product.title,
price: product.price,
image: product.cover_image,
quantity,
specs,
selected: true,
addTime: Date.now()
};
// 检查是否已存在相同商品和规格
const existingIndex = this.globalData.cart.findIndex(item =>
item.id === product.id && JSON.stringify(item.specs) === JSON.stringify(specs)
);
if (existingIndex >= 0) {
// 增加数量
this.globalData.cart[existingIndex].quantity += quantity;
} else {
// 添加新商品
this.globalData.cart.push(cartItem);
}
this.saveCart();
this.updateCartCount();
this.showSuccess('已添加到购物车');
},
// 从购物车移除
removeFromCart(productId, specs = {}) {
this.globalData.cart = this.globalData.cart.filter(item =>
!(item.id === productId && JSON.stringify(item.specs) === JSON.stringify(specs))
);
this.saveCart();
this.updateCartCount();
},
// 更新购物车商品数量
updateCartQuantity(productId, specs, quantity) {
const item = this.globalData.cart.find(item =>
item.id === productId && JSON.stringify(item.specs) === JSON.stringify(specs)
);
if (item) {
if (quantity <= 0) {
this.removeFromCart(productId, specs);
} else {
item.quantity = quantity;
this.saveCart();
this.updateCartCount();
}
}
},
// 清空购物车
clearCart() {
this.globalData.cart = [];
this.saveCart();
this.updateCartCount();
},
// 保存购物车到本地
saveCart() {
try {
wx.setStorageSync('cart', this.globalData.cart);
} catch (error) {
console.error('保存购物车失败:', error);
}
},
// 更新购物车数量
updateCartCount() {
const count = this.globalData.cart.reduce((total, item) => total + item.quantity, 0);
this.globalData.cartCount = count;
// 更新tabBar徽标
if (count > 0) {
wx.setTabBarBadge({
index: 2, // 购物车tab的索引
text: count > 99 ? '99+' : count.toString()
});
} else {
wx.removeTabBarBadge({
index: 2
});
}
},
// 计算购物车总价
getCartTotal() {
return this.globalData.cart
.filter(item => item.selected)
.reduce((total, item) => total + (item.price * item.quantity), 0);
},
// Promise化微信API
promisify(fn) {
return (options = {}) => {
return new Promise((resolve, reject) => {
fn({
...options,
success: resolve,
fail: reject
});
});
};
},
// 显示加载提示
showLoading(title = '加载中...') {
wx.showLoading({
title,
mask: true
});
},
// 隐藏加载提示
hideLoading() {
wx.hideLoading();
},
// 显示成功提示
showSuccess(title) {
wx.showToast({
title,
icon: 'success',
duration: 2000
});
},
// 显示错误提示
showError(title) {
wx.showToast({
title,
icon: 'none',
duration: 3000
});
},
// 显示确认对话框
showConfirm(options) {
return new Promise((resolve) => {
wx.showModal({
title: options.title || '提示',
content: options.content,
confirmText: options.confirmText || '确定',
cancelText: options.cancelText || '取消',
success: (res) => {
resolve(res.confirm);
}
});
});
},
// 格式化价格
formatPrice(price) {
if (!price) return '¥0.00';
return `¥${parseFloat(price).toFixed(2)}`;
},
// 格式化时间
formatTime(timestamp) {
const date = new Date(timestamp);
const now = new Date();
const diff = now - date;
if (diff < 60000) { // 1分钟内
return '刚刚';
} else if (diff < 3600000) { // 1小时内
return `${Math.floor(diff / 60000)}分钟前`;
} else if (diff < 86400000) { // 1天内
return `${Math.floor(diff / 3600000)}小时前`;
} else if (diff < 2592000000) { // 30天内
return `${Math.floor(diff / 86400000)}天前`;
} else {
return date.toLocaleDateString();
}
}
})
});

View File

@@ -1,14 +1,84 @@
{
"pages": [
"pages/index/index",
"pages/logs/logs"
"pages/auth/login",
"pages/category/index",
"pages/product/detail",
"pages/product/list",
"pages/cart/index",
"pages/order/confirm",
"pages/order/list",
"pages/order/detail",
"pages/profile/index",
"pages/profile/address",
"pages/profile/address-edit",
"pages/search/index",
"pages/common/webview"
],
"tabBar": {
"color": "#666666",
"selectedColor": "#e74c3c",
"backgroundColor": "#ffffff",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "images/tabbar/home.png",
"selectedIconPath": "images/tabbar/home-active.png"
},
{
"pagePath": "pages/category/index",
"text": "分类",
"iconPath": "images/tabbar/category.png",
"selectedIconPath": "images/tabbar/category-active.png"
},
{
"pagePath": "pages/cart/index",
"text": "购物车",
"iconPath": "images/tabbar/cart.png",
"selectedIconPath": "images/tabbar/cart-active.png"
},
{
"pagePath": "pages/order/list",
"text": "订单",
"iconPath": "images/tabbar/order.png",
"selectedIconPath": "images/tabbar/order-active.png"
},
{
"pagePath": "pages/profile/index",
"text": "我的",
"iconPath": "images/tabbar/profile.png",
"selectedIconPath": "images/tabbar/profile-active.png"
}
]
},
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "锡林郭勒盟智慧养殖",
"navigationBarTextStyle": "black"
"navigationBarBackgroundColor": "#e74c3c",
"navigationBarTitleText": "优质牛肉商城",
"navigationBarTextStyle": "white",
"backgroundColor": "#f5f5f5",
"enablePullDownRefresh": true,
"onReachBottomDistance": 50
},
"style": "v2",
"sitemapLocation": "sitemap.json"
}
"networkTimeout": {
"request": 10000,
"downloadFile": 10000
},
"debug": false,
"permission": {
"scope.userLocation": {
"desc": "您的位置信息将用于推荐附近的商品和配送服务"
}
},
"requiredBackgroundModes": [],
"plugins": {},
"preloadRule": {
"pages/product/list": {
"network": "all",
"packages": ["product"]
}
},
"lazyCodeLoading": "requiredComponents"
}

View File

@@ -0,0 +1,53 @@
{
"name": "beef-mall",
"appid": "wx3456789012cdefgh",
"description": "优质牛肉在线购买平台",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
"mp-weixin": {
"appid": "wx3456789012cdefgh",
"setting": {
"urlCheck": false,
"es6": true,
"enhance": true,
"postcss": true,
"minified": true,
"newFeature": false,
"coverView": true,
"nodeModules": false,
"autoAudits": false,
"showShadowRootInWxmlPanel": true,
"scopeDataCheck": false,
"checkInvalidKey": true,
"checkSiteMap": true,
"uploadWithSourceMap": true,
"useMultiFrameRuntime": true,
"useApiHook": true,
"useApiHostProcess": true,
"enableEngineNative": false,
"useIsolateContext": true,
"minifyWXSS": true,
"disableUseStrict": false,
"minifyWXML": true
},
"usingComponents": true,
"permission": {
"scope.userLocation": {
"desc": "用于配送地址定位和物流跟踪"
},
"scope.camera": {
"desc": "需要使用摄像头扫描二维码"
},
"scope.album": {
"desc": "需要访问相册选择图片"
}
},
"requiredPrivateInfos": [
"getLocation",
"chooseLocation",
"chooseImage"
]
},
"vueVersion": "3"
}

View File

@@ -0,0 +1,105 @@
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "牛肉商城",
"enablePullDownRefresh": true
}
},
{
"path": "pages/category/index",
"style": {
"navigationBarTitleText": "商品分类"
}
},
{
"path": "pages/product/list",
"style": {
"navigationBarTitleText": "商品列表",
"enablePullDownRefresh": true,
"onReachBottomDistance": 50
}
},
{
"path": "pages/product/detail",
"style": {
"navigationBarTitleText": "商品详情"
}
},
{
"path": "pages/cart/index",
"style": {
"navigationBarTitleText": "购物车"
}
},
{
"path": "pages/order/confirm",
"style": {
"navigationBarTitleText": "确认订单"
}
},
{
"path": "pages/order/list",
"style": {
"navigationBarTitleText": "我的订单",
"enablePullDownRefresh": true
}
},
{
"path": "pages/order/detail",
"style": {
"navigationBarTitleText": "订单详情"
}
},
{
"path": "pages/user/index",
"style": {
"navigationBarTitleText": "个人中心"
}
},
{
"path": "pages/user/address",
"style": {
"navigationBarTitleText": "收货地址"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "牛肉商城",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#ff4757",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png",
"text": "首页"
},
{
"pagePath": "pages/category/index",
"iconPath": "static/tabbar/category.png",
"selectedIconPath": "static/tabbar/category-active.png",
"text": "分类"
},
{
"pagePath": "pages/cart/index",
"iconPath": "static/tabbar/cart.png",
"selectedIconPath": "static/tabbar/cart-active.png",
"text": "购物车"
},
{
"pagePath": "pages/user/index",
"iconPath": "static/tabbar/user.png",
"selectedIconPath": "static/tabbar/user-active.png",
"text": "我的"
}
]
}
}

View File

@@ -0,0 +1,771 @@
<template>
<view class="cart-page">
<!-- 导航栏 -->
<view class="navbar">
<view class="navbar-title">购物车</view>
<view class="navbar-action" @click="toggleEdit">
{{ isEditing ? '完成' : '编辑' }}
</view>
</view>
<!-- 购物车列表 -->
<scroll-view class="cart-list" scroll-y>
<view v-if="cartItems.length === 0" class="empty-cart">
<image src="/static/images/empty-cart.png" class="empty-image" />
<text class="empty-text">购物车空空如也</text>
<button class="go-shopping-btn" @click="goShopping">去逛逛</button>
</view>
<view v-else class="cart-content">
<!-- 店铺分组 -->
<view
v-for="shop in groupedCartItems"
:key="shop.shopId"
class="shop-group"
>
<!-- 店铺头部 -->
<view class="shop-header">
<view class="shop-select" @click="toggleShopSelect(shop.shopId)">
<uni-icons
:type="shop.selected ? 'checkbox-filled' : 'circle'"
:color="shop.selected ? '#2E8B57' : '#ccc'"
size="20"
/>
</view>
<view class="shop-info">
<uni-icons type="shop" size="16" color="#666" />
<text class="shop-name">{{ shop.shopName }}</text>
</view>
</view>
<!-- 商品列表 -->
<view class="product-list">
<view
v-for="item in shop.items"
:key="item.id"
class="cart-item"
>
<view class="item-select" @click="toggleItemSelect(item.id)">
<uni-icons
:type="item.selected ? 'checkbox-filled' : 'circle'"
:color="item.selected ? '#2E8B57' : '#ccc'"
size="20"
/>
</view>
<view class="item-image">
<image :src="item.image" mode="aspectFill" />
</view>
<view class="item-info">
<view class="item-title">{{ item.title }}</view>
<view class="item-spec">{{ item.specification }}</view>
<view class="item-footer">
<view class="item-price">
<text class="currency">¥</text>
<text class="amount">{{ item.price }}</text>
</view>
<view v-if="!isEditing" class="quantity-control">
<view
class="quantity-btn decrease"
:class="{ disabled: item.quantity <= 1 }"
@click="decreaseQuantity(item.id)"
>
<uni-icons type="minus" size="14" />
</view>
<input
v-model="item.quantity"
type="number"
class="quantity-input"
@blur="updateQuantity(item.id, $event)"
/>
<view
class="quantity-btn increase"
@click="increaseQuantity(item.id)"
>
<uni-icons type="plus" size="14" />
</view>
</view>
<view v-else class="edit-actions">
<button class="collect-btn" @click="collectItem(item.id)">
收藏
</button>
<button class="delete-btn" @click="deleteItem(item.id)">
删除
</button>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 推荐商品 -->
<view class="recommend-section">
<view class="section-title">
<text>为你推荐</text>
</view>
<scroll-view class="recommend-list" scroll-x>
<view
v-for="product in recommendProducts"
:key="product.id"
class="recommend-item"
@click="goToProduct(product.id)"
>
<image :src="product.image" mode="aspectFill" />
<view class="recommend-info">
<text class="recommend-title">{{ product.title }}</text>
<text class="recommend-price">¥{{ product.price }}</text>
</view>
</view>
</scroll-view>
</view>
</view>
</scroll-view>
<!-- 底部操作栏 -->
<view v-if="cartItems.length > 0" class="bottom-bar">
<view class="select-all" @click="toggleSelectAll">
<uni-icons
:type="isAllSelected ? 'checkbox-filled' : 'circle'"
:color="isAllSelected ? '#2E8B57' : '#ccc'"
size="20"
/>
<text>全选</text>
</view>
<view class="price-info">
<view class="total-price">
<text class="label">合计</text>
<text class="currency">¥</text>
<text class="amount">{{ totalPrice }}</text>
</view>
<view v-if="totalDiscount > 0" class="discount">
已优惠 ¥{{ totalDiscount }}
</view>
</view>
<button
class="checkout-btn"
:class="{ disabled: selectedItems.length === 0 }"
@click="goToCheckout"
>
{{ isEditing ? `删除(${selectedItems.length})` : `结算(${selectedItems.length})` }}
</button>
</view>
<!-- 删除确认弹窗 -->
<uni-popup ref="deletePopup" type="dialog">
<uni-popup-dialog
type="warn"
title="确认删除"
content="确定要删除选中的商品吗?"
@confirm="confirmDelete"
@close="cancelDelete"
/>
</uni-popup>
</view>
</template>
<script>
import { ref, reactive, computed, onMounted } from 'vue'
import { useRequest } from '@/common/utils/request'
import { showToast, showModal } from '@/common/utils/uni-helper'
export default {
name: 'CartPage',
setup() {
const request = useRequest()
// 响应式数据
const cartItems = ref([])
const recommendProducts = ref([])
const isEditing = ref(false)
const loading = ref(false)
// 计算属性
const groupedCartItems = computed(() => {
const groups = {}
cartItems.value.forEach(item => {
if (!groups[item.shopId]) {
groups[item.shopId] = {
shopId: item.shopId,
shopName: item.shopName,
selected: false,
items: []
}
}
groups[item.shopId].items.push(item)
})
// 计算店铺选中状态
Object.values(groups).forEach(shop => {
shop.selected = shop.items.every(item => item.selected)
})
return Object.values(groups)
})
const selectedItems = computed(() => {
return cartItems.value.filter(item => item.selected)
})
const isAllSelected = computed(() => {
return cartItems.value.length > 0 && cartItems.value.every(item => item.selected)
})
const totalPrice = computed(() => {
return selectedItems.value.reduce((total, item) => {
return total + (item.price * item.quantity)
}, 0).toFixed(2)
})
const totalDiscount = computed(() => {
return selectedItems.value.reduce((total, item) => {
return total + (item.originalPrice - item.price) * item.quantity
}, 0).toFixed(2)
})
// 获取购物车数据
const getCartData = async () => {
try {
loading.value = true
const response = await request.get('/api/cart/list')
cartItems.value = response.data.map(item => ({
...item,
selected: false
}))
} catch (error) {
showToast('获取购物车数据失败')
} finally {
loading.value = false
}
}
// 获取推荐商品
const getRecommendProducts = async () => {
try {
const response = await request.get('/api/products/recommend')
recommendProducts.value = response.data
} catch (error) {
console.error('获取推荐商品失败', error)
}
}
// 切换编辑模式
const toggleEdit = () => {
isEditing.value = !isEditing.value
if (!isEditing.value) {
// 退出编辑模式时清除选中状态
cartItems.value.forEach(item => {
item.selected = false
})
}
}
// 切换商品选中状态
const toggleItemSelect = (itemId) => {
const item = cartItems.value.find(item => item.id === itemId)
if (item) {
item.selected = !item.selected
}
}
// 切换店铺选中状态
const toggleShopSelect = (shopId) => {
const shopItems = cartItems.value.filter(item => item.shopId === shopId)
const allSelected = shopItems.every(item => item.selected)
shopItems.forEach(item => {
item.selected = !allSelected
})
}
// 切换全选状态
const toggleSelectAll = () => {
const newSelectState = !isAllSelected.value
cartItems.value.forEach(item => {
item.selected = newSelectState
})
}
// 增加数量
const increaseQuantity = async (itemId) => {
const item = cartItems.value.find(item => item.id === itemId)
if (item) {
item.quantity++
await updateCartItem(itemId, item.quantity)
}
}
// 减少数量
const decreaseQuantity = async (itemId) => {
const item = cartItems.value.find(item => item.id === itemId)
if (item && item.quantity > 1) {
item.quantity--
await updateCartItem(itemId, item.quantity)
}
}
// 更新数量
const updateQuantity = async (itemId, event) => {
const quantity = parseInt(event.detail.value) || 1
const item = cartItems.value.find(item => item.id === itemId)
if (item) {
item.quantity = Math.max(1, quantity)
await updateCartItem(itemId, item.quantity)
}
}
// 更新购物车商品
const updateCartItem = async (itemId, quantity) => {
try {
await request.put(`/api/cart/update/${itemId}`, { quantity })
} catch (error) {
showToast('更新失败')
}
}
// 删除商品
const deleteItem = (itemId) => {
showModal({
title: '确认删除',
content: '确定要删除这个商品吗?',
success: (res) => {
if (res.confirm) {
removeCartItem(itemId)
}
}
})
}
// 收藏商品
const collectItem = async (itemId) => {
try {
await request.post('/api/favorites/add', { productId: itemId })
showToast('已添加到收藏')
} catch (error) {
showToast('收藏失败')
}
}
// 移除购物车商品
const removeCartItem = async (itemId) => {
try {
await request.delete(`/api/cart/remove/${itemId}`)
const index = cartItems.value.findIndex(item => item.id === itemId)
if (index > -1) {
cartItems.value.splice(index, 1)
}
showToast('删除成功')
} catch (error) {
showToast('删除失败')
}
}
// 批量删除
const confirmDelete = async () => {
try {
const itemIds = selectedItems.value.map(item => item.id)
await request.post('/api/cart/batch-remove', { itemIds })
// 从本地数据中移除
cartItems.value = cartItems.value.filter(item => !item.selected)
showToast('删除成功')
} catch (error) {
showToast('删除失败')
}
}
const cancelDelete = () => {
// 取消删除
}
// 去购物
const goShopping = () => {
uni.switchTab({
url: '/pages/index/index'
})
}
// 跳转到商品详情
const goToProduct = (productId) => {
uni.navigateTo({
url: `/pages/product/product?id=${productId}`
})
}
// 去结算
const goToCheckout = () => {
if (isEditing.value) {
// 编辑模式下执行删除
if (selectedItems.value.length > 0) {
confirmDelete()
}
} else {
// 正常模式下去结算
if (selectedItems.value.length === 0) {
showToast('请选择要结算的商品')
return
}
const itemIds = selectedItems.value.map(item => item.id)
uni.navigateTo({
url: `/pages/checkout/checkout?items=${JSON.stringify(itemIds)}`
})
}
}
// 页面加载
onMounted(() => {
getCartData()
getRecommendProducts()
})
return {
cartItems,
recommendProducts,
isEditing,
loading,
groupedCartItems,
selectedItems,
isAllSelected,
totalPrice,
totalDiscount,
toggleEdit,
toggleItemSelect,
toggleShopSelect,
toggleSelectAll,
increaseQuantity,
decreaseQuantity,
updateQuantity,
deleteItem,
collectItem,
confirmDelete,
cancelDelete,
goShopping,
goToProduct,
goToCheckout
}
}
}
</script>
<style lang="scss" scoped>
.cart-page {
height: 100vh;
display: flex;
flex-direction: column;
background-color: $bg-secondary;
}
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: $spacing-md;
background-color: $white;
border-bottom: 1px solid $border-color;
.navbar-title {
font-size: $font-size-lg;
font-weight: $font-weight-medium;
color: $text-primary;
}
.navbar-action {
color: $primary-color;
font-size: $font-size-base;
}
}
.cart-list {
flex: 1;
}
.empty-cart {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: $spacing-5xl;
.empty-image {
width: 120px;
height: 120px;
margin-bottom: $spacing-lg;
}
.empty-text {
font-size: $font-size-base;
color: $text-muted;
margin-bottom: $spacing-xl;
}
.go-shopping-btn {
background-color: $primary-color;
color: $white;
border-radius: $border-radius-full;
padding: $spacing-sm $spacing-xl;
font-size: $font-size-base;
}
}
.cart-content {
padding: $spacing-md;
}
.shop-group {
background-color: $white;
border-radius: $border-radius-lg;
margin-bottom: $spacing-md;
overflow: hidden;
.shop-header {
display: flex;
align-items: center;
padding: $spacing-md;
border-bottom: 1px solid $border-light;
gap: $spacing-sm;
.shop-info {
display: flex;
align-items: center;
gap: $spacing-xs;
.shop-name {
font-size: $font-size-base;
font-weight: $font-weight-medium;
color: $text-primary;
}
}
}
}
.cart-item {
display: flex;
align-items: center;
padding: $spacing-md;
gap: $spacing-sm;
border-bottom: 1px solid $border-light;
&:last-child {
border-bottom: none;
}
.item-image {
width: 80px;
height: 80px;
border-radius: $border-radius-md;
overflow: hidden;
image {
width: 100%;
height: 100%;
}
}
.item-info {
flex: 1;
.item-title {
font-size: $font-size-base;
color: $text-primary;
@include text-ellipsis(2);
margin-bottom: $spacing-xs;
}
.item-spec {
font-size: $font-size-sm;
color: $text-muted;
margin-bottom: $spacing-sm;
}
.item-footer {
display: flex;
justify-content: space-between;
align-items: center;
.item-price {
.currency {
font-size: $font-size-sm;
color: $error-color;
}
.amount {
font-size: $font-size-lg;
font-weight: $font-weight-bold;
color: $error-color;
}
}
.quantity-control {
display: flex;
align-items: center;
border: 1px solid $border-color;
border-radius: $border-radius-md;
.quantity-btn {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
background-color: $bg-light;
&.disabled {
opacity: 0.5;
}
&.decrease {
border-right: 1px solid $border-color;
}
&.increase {
border-left: 1px solid $border-color;
}
}
.quantity-input {
width: 50px;
height: 32px;
text-align: center;
font-size: $font-size-sm;
}
}
.edit-actions {
display: flex;
gap: $spacing-sm;
button {
padding: $spacing-xs $spacing-sm;
border-radius: $border-radius-sm;
font-size: $font-size-sm;
&.collect-btn {
background-color: $bg-light;
color: $text-secondary;
}
&.delete-btn {
background-color: $error-color;
color: $white;
}
}
}
}
}
}
.recommend-section {
margin-top: $spacing-lg;
.section-title {
font-size: $font-size-base;
font-weight: $font-weight-medium;
color: $text-primary;
margin-bottom: $spacing-md;
}
.recommend-list {
white-space: nowrap;
.recommend-item {
display: inline-block;
width: 120px;
margin-right: $spacing-md;
background-color: $white;
border-radius: $border-radius-md;
overflow: hidden;
image {
width: 100%;
height: 120px;
}
.recommend-info {
padding: $spacing-sm;
.recommend-title {
font-size: $font-size-sm;
color: $text-primary;
@include text-ellipsis(2);
margin-bottom: $spacing-xs;
}
.recommend-price {
font-size: $font-size-sm;
color: $error-color;
font-weight: $font-weight-medium;
}
}
}
}
}
.bottom-bar {
display: flex;
align-items: center;
padding: $spacing-md;
background-color: $white;
border-top: 1px solid $border-color;
@include safe-area-inset(padding-bottom, bottom);
.select-all {
display: flex;
align-items: center;
gap: $spacing-xs;
margin-right: $spacing-lg;
text {
font-size: $font-size-base;
color: $text-primary;
}
}
.price-info {
flex: 1;
text-align: right;
.total-price {
.label {
font-size: $font-size-base;
color: $text-primary;
}
.currency {
font-size: $font-size-sm;
color: $error-color;
}
.amount {
font-size: $font-size-xl;
font-weight: $font-weight-bold;
color: $error-color;
}
}
.discount {
font-size: $font-size-xs;
color: $text-muted;
}
}
.checkout-btn {
background-color: $primary-color;
color: $white;
border-radius: $border-radius-full;
padding: $spacing-sm $spacing-lg;
font-size: $font-size-base;
margin-left: $spacing-md;
&.disabled {
background-color: $gray-300;
color: $gray-500;
}
}
}
</style>

View File

@@ -0,0 +1,269 @@
// 牛肉商城首页
const app = getApp();
Page({
data: {
userInfo: null,
bannerList: [],
categoryList: [],
hotProducts: [],
newProducts: [],
recommendProducts: [],
loading: true,
refreshing: false,
searchKeyword: ''
},
onLoad() {
this.checkLogin();
},
onShow() {
if (app.globalData.userInfo) {
this.setData({
userInfo: app.globalData.userInfo
});
this.loadData();
}
},
onPullDownRefresh() {
this.setData({ refreshing: true });
this.loadData().finally(() => {
this.setData({ refreshing: false });
wx.stopPullDownRefresh();
});
},
// 检查登录状态
checkLogin() {
if (!app.globalData.userInfo) {
wx.navigateTo({
url: '/pages/auth/login'
});
return;
}
this.setData({
userInfo: app.globalData.userInfo
});
this.loadData();
},
// 加载数据
async loadData() {
try {
this.setData({ loading: true });
await Promise.all([
this.loadBanners(),
this.loadCategories(),
this.loadHotProducts(),
this.loadNewProducts(),
this.loadRecommendProducts()
]);
} catch (error) {
console.error('加载数据失败:', error);
app.showError('加载数据失败');
} finally {
this.setData({ loading: false });
}
},
// 加载轮播图
async loadBanners() {
try {
const res = await app.request({
url: '/mall/banners',
method: 'GET',
data: { type: 'home' }
});
this.setData({
bannerList: res.data.list || []
});
} catch (error) {
console.error('加载轮播图失败:', error);
}
},
// 加载分类
async loadCategories() {
try {
const res = await app.request({
url: '/mall/categories',
method: 'GET',
data: { level: 1, limit: 8 }
});
this.setData({
categoryList: res.data.list || []
});
} catch (error) {
console.error('加载分类失败:', error);
}
},
// 加载热门商品
async loadHotProducts() {
try {
const res = await app.request({
url: '/mall/products',
method: 'GET',
data: {
sort: 'hot',
limit: 6
}
});
this.setData({
hotProducts: res.data.list || []
});
} catch (error) {
console.error('加载热门商品失败:', error);
}
},
// 加载新品推荐
async loadNewProducts() {
try {
const res = await app.request({
url: '/mall/products',
method: 'GET',
data: {
sort: 'latest',
limit: 4
}
});
this.setData({
newProducts: res.data.list || []
});
} catch (error) {
console.error('加载新品推荐失败:', error);
}
},
// 加载推荐商品
async loadRecommendProducts() {
try {
const res = await app.request({
url: '/mall/products/recommend',
method: 'GET',
data: { limit: 10 }
});
this.setData({
recommendProducts: res.data.list || []
});
} catch (error) {
console.error('加载推荐商品失败:', error);
}
},
// 轮播图点击
onBannerTap(e) {
const { banner } = e.currentTarget.dataset;
if (banner.link_type === 'product') {
wx.navigateTo({
url: `/pages/product/detail?id=${banner.link_value}`
});
} else if (banner.link_type === 'category') {
wx.navigateTo({
url: `/pages/product/list?category=${banner.link_value}`
});
} else if (banner.link_type === 'url') {
wx.navigateTo({
url: `/pages/common/webview?url=${encodeURIComponent(banner.link_value)}`
});
}
},
// 分类点击
onCategoryTap(e) {
const { category } = e.currentTarget.dataset;
wx.navigateTo({
url: `/pages/product/list?category=${category.id}`
});
},
// 商品点击
onProductTap(e) {
const { product } = e.currentTarget.dataset;
wx.navigateTo({
url: `/pages/product/detail?id=${product.id}`
});
},
// 搜索输入
onSearchInput(e) {
this.setData({
searchKeyword: e.detail.value
});
},
// 搜索确认
onSearchConfirm() {
if (this.data.searchKeyword.trim()) {
wx.navigateTo({
url: `/pages/search/index?keyword=${encodeURIComponent(this.data.searchKeyword)}`
});
}
},
// 搜索页面
onSearchTap() {
wx.navigateTo({
url: '/pages/search/index'
});
},
// 查看更多热门
onViewMoreHot() {
wx.navigateTo({
url: '/pages/product/list?sort=hot'
});
},
// 查看更多新品
onViewMoreNew() {
wx.navigateTo({
url: '/pages/product/list?sort=latest'
});
},
// 查看全部分类
onViewAllCategories() {
wx.switchTab({
url: '/pages/category/index'
});
},
// 添加到购物车
onAddToCart(e) {
e.stopPropagation();
const { product } = e.currentTarget.dataset;
// 检查是否需要选择规格
if (product.has_specs) {
wx.navigateTo({
url: `/pages/product/detail?id=${product.id}&action=addCart`
});
} else {
app.addToCart(product, 1);
}
},
// 格式化价格
formatPrice(price) {
return app.formatPrice(price);
},
// 格式化销量
formatSales(sales) {
if (sales >= 10000) {
return (sales / 10000).toFixed(1) + '万';
}
return sales.toString();
}
});

View File

@@ -0,0 +1,172 @@
<!--牛肉商城首页-->
<view class="page-container">
<!-- 顶部搜索栏 -->
<view class="header-search">
<view class="search-bar" bindtap="onSearchTap">
<image class="search-icon" src="/images/icons/search.png" mode="aspectFit"></image>
<text class="search-placeholder">搜索优质牛肉...</text>
</view>
</view>
<!-- 轮播图 -->
<swiper
class="banner-swiper"
indicator-dots
autoplay
interval="5000"
duration="500"
wx:if="{{bannerList.length > 0}}"
>
<swiper-item
wx:for="{{bannerList}}"
wx:key="id"
data-banner="{{item}}"
bindtap="onBannerTap"
>
<image class="banner-image" src="{{item.image}}" mode="aspectFill"></image>
</swiper-item>
</swiper>
<!-- 分类导航 -->
<view class="category-nav" wx:if="{{categoryList.length > 0}}">
<view class="category-header">
<text class="category-title">商品分类</text>
<text class="category-more" bindtap="onViewAllCategories">查看全部</text>
</view>
<view class="category-grid">
<view
class="category-item"
wx:for="{{categoryList}}"
wx:key="id"
data-category="{{item}}"
bindtap="onCategoryTap"
>
<image class="category-icon" src="{{item.icon}}" mode="aspectFit"></image>
<text class="category-name">{{item.name}}</text>
</view>
</view>
</view>
<!-- 热门商品 -->
<view class="section" wx:if="{{hotProducts.length > 0}}">
<view class="section-header">
<text class="section-title">热门推荐</text>
<text class="section-more" bindtap="onViewMoreHot">查看更多</text>
</view>
<view class="hot-products">
<scroll-view class="hot-scroll" scroll-x>
<view class="hot-list">
<view
class="hot-item"
wx:for="{{hotProducts}}"
wx:key="id"
data-product="{{item}}"
bindtap="onProductTap"
>
<image class="hot-image" src="{{item.cover_image}}" mode="aspectFill"></image>
<view class="hot-info">
<text class="hot-title">{{item.title}}</text>
<view class="hot-price-row">
<text class="hot-price">{{formatPrice(item.price)}}</text>
<text class="hot-sales">销量{{formatSales(item.sales)}}</text>
</view>
</view>
<button
class="add-cart-btn"
data-product="{{item}}"
bindtap="onAddToCart"
>
<image src="/images/icons/cart-add.png" mode="aspectFit"></image>
</button>
</view>
</view>
</scroll-view>
</view>
</view>
<!-- 新品推荐 -->
<view class="section" wx:if="{{newProducts.length > 0}}">
<view class="section-header">
<text class="section-title">新品上市</text>
<text class="section-more" bindtap="onViewMoreNew">查看更多</text>
</view>
<view class="new-products">
<view class="new-grid">
<view
class="new-item"
wx:for="{{newProducts}}"
wx:key="id"
data-product="{{item}}"
bindtap="onProductTap"
>
<image class="new-image" src="{{item.cover_image}}" mode="aspectFill"></image>
<view class="new-badge">新品</view>
<view class="new-info">
<text class="new-title">{{item.title}}</text>
<text class="new-desc">{{item.description}}</text>
<view class="new-price-row">
<text class="new-price">{{formatPrice(item.price)}}</text>
<view class="new-original-price" wx:if="{{item.original_price > item.price}}">
<text class="original-price">{{formatPrice(item.original_price)}}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 推荐商品 -->
<view class="section" wx:if="{{recommendProducts.length > 0}}">
<view class="section-header">
<text class="section-title">为您推荐</text>
</view>
<view class="recommend-products">
<view
class="recommend-item"
wx:for="{{recommendProducts}}"
wx:key="id"
data-product="{{item}}"
bindtap="onProductTap"
>
<image class="recommend-image" src="{{item.cover_image}}" mode="aspectFill"></image>
<view class="recommend-info">
<text class="recommend-title">{{item.title}}</text>
<text class="recommend-desc">{{item.description}}</text>
<view class="recommend-tags" wx:if="{{item.tags}}">
<text
class="recommend-tag"
wx:for="{{item.tags}}"
wx:key="*this"
wx:for-item="tag"
>{{tag}}</text>
</view>
<view class="recommend-price-row">
<text class="recommend-price">{{formatPrice(item.price)}}</text>
<text class="recommend-sales">已售{{formatSales(item.sales)}}</text>
<button
class="recommend-cart-btn"
data-product="{{item}}"
bindtap="onAddToCart"
>
加购物车
</button>
</view>
</view>
</view>
</view>
</view>
<!-- 加载状态 -->
<view class="loading" wx:if="{{loading}}">
<view class="loading-spinner"></view>
<text>加载中...</text>
</view>
<!-- 空状态 -->
<view class="empty" wx:if="{{!loading && hotProducts.length === 0 && newProducts.length === 0}}">
<image class="empty-icon" src="/images/empty-product.png" mode="aspectFit"></image>
<text class="empty-text">暂无商品</text>
<text class="empty-desc">商品正在准备中,敬请期待</text>
</view>
</view>

View File

@@ -0,0 +1,457 @@
/* 牛肉商城首页样式 */
.page-container {
min-height: 100vh;
background: #f5f5f5;
}
/* 顶部搜索栏 */
.header-search {
padding: 20rpx 24rpx;
background: #e74c3c;
}
.search-bar {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.9);
border-radius: 24rpx;
padding: 0 20rpx;
height: 72rpx;
}
.search-icon {
width: 28rpx;
height: 28rpx;
margin-right: 12rpx;
}
.search-placeholder {
flex: 1;
font-size: 28rpx;
color: #999;
}
/* 轮播图 */
.banner-swiper {
height: 320rpx;
margin: 0 24rpx 20rpx;
border-radius: 16rpx;
overflow: hidden;
}
.banner-image {
width: 100%;
height: 100%;
}
/* 分类导航 */
.category-nav {
background: #fff;
margin: 0 24rpx 20rpx;
border-radius: 16rpx;
padding: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
}
.category-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
}
.category-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.category-more {
font-size: 24rpx;
color: #e74c3c;
}
.category-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 24rpx;
}
.category-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 20rpx;
border-radius: 12rpx;
transition: all 0.3s ease;
}
.category-item:active {
background: #f8f9fa;
}
.category-icon {
width: 64rpx;
height: 64rpx;
margin-bottom: 12rpx;
}
.category-name {
font-size: 24rpx;
color: #333;
text-align: center;
}
/* 区块样式 */
.section {
margin-bottom: 32rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 24rpx 20rpx;
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.section-more {
font-size: 24rpx;
color: #e74c3c;
}
/* 热门商品 */
.hot-products {
padding-left: 24rpx;
}
.hot-scroll {
white-space: nowrap;
}
.hot-list {
display: inline-flex;
gap: 16rpx;
padding-right: 24rpx;
}
.hot-item {
position: relative;
width: 280rpx;
background: #fff;
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
}
.hot-image {
width: 100%;
height: 200rpx;
}
.hot-info {
padding: 16rpx;
padding-bottom: 60rpx;
}
.hot-title {
display: block;
font-size: 26rpx;
font-weight: 500;
color: #333;
margin-bottom: 12rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.hot-price-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.hot-price {
font-size: 28rpx;
font-weight: 600;
color: #e74c3c;
}
.hot-sales {
font-size: 20rpx;
color: #999;
}
.add-cart-btn {
position: absolute;
right: 16rpx;
bottom: 16rpx;
width: 56rpx;
height: 56rpx;
background: #e74c3c;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: none;
box-shadow: 0 2rpx 8rpx rgba(231, 76, 60, 0.3);
}
.add-cart-btn image {
width: 24rpx;
height: 24rpx;
}
/* 新品推荐 */
.new-products {
padding: 0 24rpx;
}
.new-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16rpx;
}
.new-item {
position: relative;
background: #fff;
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
}
.new-image {
width: 100%;
height: 200rpx;
}
.new-badge {
position: absolute;
top: 12rpx;
left: 12rpx;
background: #e74c3c;
color: #fff;
font-size: 20rpx;
padding: 4rpx 12rpx;
border-radius: 12rpx;
}
.new-info {
padding: 16rpx;
}
.new-title {
display: block;
font-size: 26rpx;
font-weight: 500;
color: #333;
margin-bottom: 8rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.new-desc {
display: block;
font-size: 22rpx;
color: #666;
margin-bottom: 12rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.new-price-row {
display: flex;
align-items: center;
gap: 12rpx;
}
.new-price {
font-size: 28rpx;
font-weight: 600;
color: #e74c3c;
}
.new-original-price {
display: flex;
align-items: center;
}
.original-price {
font-size: 22rpx;
color: #999;
text-decoration: line-through;
}
/* 推荐商品 */
.recommend-products {
padding: 0 24rpx;
}
.recommend-item {
display: flex;
background: #fff;
border-radius: 12rpx;
padding: 20rpx;
margin-bottom: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
}
.recommend-image {
width: 160rpx;
height: 120rpx;
border-radius: 8rpx;
margin-right: 20rpx;
}
.recommend-info {
flex: 1;
display: flex;
flex-direction: column;
}
.recommend-title {
font-size: 28rpx;
font-weight: 500;
color: #333;
margin-bottom: 8rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.recommend-desc {
font-size: 24rpx;
color: #666;
margin-bottom: 12rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.recommend-tags {
display: flex;
gap: 8rpx;
margin-bottom: 12rpx;
}
.recommend-tag {
font-size: 20rpx;
color: #e74c3c;
background: rgba(231, 76, 60, 0.1);
padding: 4rpx 8rpx;
border-radius: 8rpx;
}
.recommend-price-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.recommend-price {
font-size: 28rpx;
font-weight: 600;
color: #e74c3c;
}
.recommend-sales {
font-size: 20rpx;
color: #999;
}
.recommend-cart-btn {
background: #e74c3c;
color: #fff;
border: none;
border-radius: 16rpx;
padding: 12rpx 20rpx;
font-size: 22rpx;
font-weight: 500;
}
/* 加载状态 */
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 40rpx;
color: #666;
}
.loading-spinner {
width: 48rpx;
height: 48rpx;
border: 3rpx solid #f0f0f0;
border-top: 3rpx solid #e74c3c;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 24rpx;
}
/* 空状态 */
.empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 40rpx;
text-align: center;
}
.empty-icon {
width: 160rpx;
height: 160rpx;
margin-bottom: 32rpx;
opacity: 0.6;
}
.empty-text {
font-size: 32rpx;
font-weight: 500;
color: #666;
margin-bottom: 16rpx;
}
.empty-desc {
font-size: 24rpx;
color: #999;
line-height: 1.5;
}
/* 动画 */
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 响应式适配 */
@media (max-width: 750rpx) {
.category-grid {
grid-template-columns: repeat(4, 1fr);
gap: 16rpx;
}
.new-grid {
grid-template-columns: 1fr;
}
.recommend-item {
flex-direction: column;
}
.recommend-image {
width: 100%;
height: 200rpx;
margin-right: 0;
margin-bottom: 16rpx;
}
}

File diff suppressed because it is too large Load Diff