refactor: 重构数据库配置为SQLite开发环境并移除冗余文档
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
@@ -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"
|
||||
}
|
||||
53
mini_program/beef-mall/manifest.json
Normal file
53
mini_program/beef-mall/manifest.json
Normal 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"
|
||||
}
|
||||
105
mini_program/beef-mall/pages.json
Normal file
105
mini_program/beef-mall/pages.json
Normal 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": "我的"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
771
mini_program/beef-mall/pages/cart/cart.vue
Normal file
771
mini_program/beef-mall/pages/cart/cart.vue
Normal 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>
|
||||
269
mini_program/beef-mall/pages/index/index.js
Normal file
269
mini_program/beef-mall/pages/index/index.js
Normal 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();
|
||||
}
|
||||
});
|
||||
172
mini_program/beef-mall/pages/index/index.wxml
Normal file
172
mini_program/beef-mall/pages/index/index.wxml
Normal 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>
|
||||
457
mini_program/beef-mall/pages/index/index.wxss
Normal file
457
mini_program/beef-mall/pages/index/index.wxss
Normal 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;
|
||||
}
|
||||
}
|
||||
1000
mini_program/beef-mall/pages/product/detail.vue
Normal file
1000
mini_program/beef-mall/pages/product/detail.vue
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user