Compare commits

...

18 Commits

Author SHA1 Message Date
6deef6cdce 1 2026-04-14 18:11:00 +08:00
e894689918 最新版代码提交 2026-04-13 16:12:25 +08:00
26e57e4891 1 2026-04-10 19:00:01 +08:00
32fa3e9bd2 1 2026-04-10 17:32:50 +08:00
4002f91a54 1 2026-04-10 16:50:22 +08:00
a2fadc57b8 1 2026-04-10 15:47:09 +08:00
b251c899ca 1 2026-04-10 13:17:20 +08:00
cdf0bc5a01 1 2026-04-10 10:19:42 +08:00
7a490f0ffc 1 2026-04-09 17:03:37 +08:00
34148c6084 1 2026-04-09 16:50:25 +08:00
9b6b76f747 1 2026-04-09 15:54:26 +08:00
6ea50b824f 1 2026-04-09 14:31:44 +08:00
72e481c6c5 1 2026-04-09 10:12:23 +08:00
124beb0666 1 2026-04-08 16:11:18 +08:00
078e0bc8ab 1 2026-04-08 10:47:25 +08:00
1739401cb8 1 2026-04-07 19:48:56 +08:00
3afa2e8c10 1 2026-04-07 18:47:12 +08:00
7d1083bf5c fix: calculate actual total item count for multi-item orders 2026-04-07 17:57:00 +08:00
21 changed files with 3401 additions and 2697 deletions

View File

@ -2,7 +2,7 @@ import request from "../utils/request";
import { LOGIN, GET_PHONE, USER_SHARE,USER_WALLET,RECHARGE_WALLET,USER_WXPAY,USER_TRANSACTION,USER_ADDITIONAL,MEMBER_TYPES, import { LOGIN, GET_PHONE, USER_SHARE,USER_WALLET,RECHARGE_WALLET,USER_WXPAY,USER_TRANSACTION,USER_ADDITIONAL,MEMBER_TYPES,
USER_RECHARGE,USER_REDEEM,USER_HolderList,USER_MEMBERSHIP,USER_BINDPETS,USER_PETBINDING,USER_DISCOUNTFEE,USER_COUPONLIST,CANCEL_PET_ORDER, USER_RECHARGE,USER_REDEEM,USER_HolderList,USER_MEMBERSHIP,USER_BINDPETS,USER_PETBINDING,USER_DISCOUNTFEE,USER_COUPONLIST,CANCEL_PET_ORDER,
GET_VIP_PRICE,POINTS_RECHARGE_LIST,POINTS_DONATE,POINTS_RECORDS,POINTS_RANK,OSS_STS,DONATION_SUMMARY, GET_VIP_PRICE,POINTS_RECHARGE_LIST,POINTS_DONATE,POINTS_RECORDS,POINTS_RANK,OSS_STS,DONATION_SUMMARY,
USER_DISPATCHFEE,CANCEL_MALL_ORDER,DOUY_REVIEW USER_DISPATCHFEE,CANCEL_MALL_ORDER,DOUY_REVIEW,CANCEL_NOMO_ORDER,LOGIN_PHONE
} from "./url"; } from "./url";
// 微信登陆 // 微信登陆
export const getCodeByWxLogin = () => { export const getCodeByWxLogin = () => {
@ -10,7 +10,7 @@ export const getCodeByWxLogin = () => {
tt.login({ tt.login({
provider: "toutiao", provider: "toutiao",
success: function (loginRes) { success: function (loginRes) {
// console.log(loginRes,'?1?') console.log(loginRes,'?1?')
resolve(loginRes.code); resolve(loginRes.code);
}, },
fail: function (err) { fail: function (err) {
@ -23,7 +23,7 @@ export const getCodeByWxLogin = () => {
import Store from "../store"; import Store from "../store";
// 登录鉴权 // 登录鉴权
export const login = (nickName, avatarUrl) => { export const login = ( phone) => {
return getCodeByWxLogin().then((code) => { return getCodeByWxLogin().then((code) => {
// 从 vuex 中获取 referrerID如果通过二维码扫描进入 // 从 vuex 中获取 referrerID如果通过二维码扫描进入
const referrerID = Store.state.user?.referrerID || 0; const referrerID = Store.state.user?.referrerID || 0;
@ -32,17 +32,11 @@ export const login = (nickName, avatarUrl) => {
url: LOGIN, url: LOGIN,
method: "POST", method: "POST",
data: { data: {
nickName:nickName,
avatarUrl:avatarUrl,
code: code, code: code,
source:'douyin', phone: phone || null,
referrerID:0, source: 'douyin',
referrerType:'douyin' referrerID: 0,
// // yaoqing_code: inviteCode || null, referrerType: 'douyin'
// phone: phone || null,
// source: "wechat",
// referrerID: Number(referrerID) || 0,
// referrerType: "wechat"
}, },
}).then((res) => { }).then((res) => {
// 登录接口使用完 referrerID 后,清除一次,避免重复使用 // 登录接口使用完 referrerID 后,清除一次,避免重复使用
@ -54,12 +48,13 @@ export const login = (nickName, avatarUrl) => {
}); });
}; };
// 获取手机号 // 获取手机号
export const getPhone = (code) => { export const getPhone = (phoneCode, loginCode) => {
return request({ return request({
url: GET_PHONE, url: GET_PHONE,
method: "POST", method: "POST",
data: { data: {
code: code code: phoneCode,
loginCode: loginCode
}, },
}); });
}; };
@ -74,6 +69,18 @@ export const userShare = (id) => {
}); });
}; };
export const login_phone = (phone) => {
return request({
url: LOGIN_PHONE,
method: "POST",
data: {
phone
},
});
};
// 用户钱包 // 用户钱包
export const userWllet = (id) => { export const userWllet = (id) => {
return request({ return request({
@ -164,10 +171,11 @@ export const getDonationSummary = () => {
}; };
// 抖音审核 // 抖音审核
export const getdouyReview = () => { export const getdouyReview = (data) => {
return request({ return request({
url: DOUY_REVIEW, url: DOUY_REVIEW,
method: "GET" method: "POST",
data,
}); });
}; };
@ -225,6 +233,15 @@ export const cancelPetOrderMall = (data) => {
}); });
}; };
// 未支付取消商城订单
export const cancelUnpaid = (data) => {
return request({
url: CANCEL_NOMO_ORDER,
method: "POST",
data: data
});
};
// 绑定宠物列表 // 绑定宠物列表
export const bindPets = (data) => { export const bindPets = (data) => {
return request({ return request({

View File

@ -115,12 +115,12 @@ export const updateCartSelect = ({ is_select,cart_id }) => {
}; };
// 删除购物车 // 删除购物车
export const deleteCart = ({ cart_id }) => { export const deleteCart = ({ id }) => {
return request({ return request({
url: DELETE_CART, url: DELETE_CART,
method: "post", method: "post",
data: { data: {
cart_id, id,
}, },
}); });
}; };

View File

@ -56,8 +56,13 @@ export const CANCEL_PET_ORDER = '/order/pet/cancel'
// 抖音审核接口 // 抖音审核接口
export const DOUY_REVIEW = '/douyin/goods/order/appointment' export const DOUY_REVIEW = '/douyin/goods/order/appointment'
// 抖音未支付订单取消接口
export const CANCEL_NOMO_ORDER = '/douyin/goods/order/cancel'
export const LOGIN_PHONE = '/api/v1/auth/login'
// 取消商城接口 // 取消商城接口
export const CANCEL_MALL_ORDER = '/douyin/goods/order/cancel' export const CANCEL_MALL_ORDER = '/douyin/goods/order/refund'
// 卡包列表接口 // 卡包列表接口
export const USER_HolderList = '/membership/instances' export const USER_HolderList = '/membership/instances'
@ -198,7 +203,7 @@ export const GET_GOODS_CATEGORY = "/product/type/list";
// 商品列表 // 商品列表
export const GET_GOODS_LIST = "/douyin/goods/product/online/query"; export const GET_GOODS_LIST = "/douyin/goods/product/online/query";
// 商品详情 // 商品详情
export const GET_GOODS_DETAIL = "/product/detail"; export const GET_GOODS_DETAIL = "/douyin/goods/product/detail";
// 获取优惠券列表 // 获取优惠券列表
export const GET_COUPON_LIST = "/app/coupon/coupon_list"; export const GET_COUPON_LIST = "/app/coupon/coupon_list";
@ -227,7 +232,7 @@ export const UPDATE_CART_NUM = "/cart/update";
// 选择购物车商品 // 选择购物车商品
export const UPDATE_CART_SELECT = "/cart/select/all"; export const UPDATE_CART_SELECT = "/cart/select/all";
// 删除购物车 // 删除购物车
export const DELETE_CART = "/product/cart/delete"; export const DELETE_CART = "/douyin/goods/order/refund";
// 创建订单 // 创建订单
export const CREATE_ORDER_NEW = "/product/order/create"; export const CREATE_ORDER_NEW = "/product/order/create";
@ -238,7 +243,7 @@ export const PAY_ORDER_NEW = "/ttpay/jsapi";
// 订单列表 // 订单列表
export const SHOP_ORDER_LIST = "/douyin/goods/order/list"; export const SHOP_ORDER_LIST = "/douyin/goods/order/list";
// 订单详情 // 订单详情
export const SHOP_ORDER_DETAILS = "/product/order/show"; export const SHOP_ORDER_DETAILS = "/douyin/goods/order/show";
// 取消订单 // 取消订单
export const SHOP_ORDER_CANCEL = '/app/order/order_quxiao' export const SHOP_ORDER_CANCEL = '/app/order/order_quxiao'
// 提醒发货 // 提醒发货

View File

@ -3,8 +3,8 @@ export default {
appName: "Wagoo", appName: "Wagoo",
appShareName: "Wagoo", appShareName: "Wagoo",
appId: "wx00e2dcdc7c02b23a", appId: "wx00e2dcdc7c02b23a",
apiBaseUrl: "https://api.wagoo.me/api/v1", // 服务端测试地址 // apiBaseUrl: "https://api.wagoo.me/api/v1", // 服务端测试地址
// apiBaseUrl: "https://api.wagoo.pet/api/v1", // 服务端生产地址 apiBaseUrl: "https://api.wagoo.pet/api/v1", // 服务端生产地址
// apiBaseUrl: "http:192.168.30.79", //本地接口 // apiBaseUrl: "http:192.168.30.79", //本地接口
tencentMapKey: "WSBBZ-7OXK4-46QUC-KFB7B-4N3W7-M2BXM", tencentMapKey: "WSBBZ-7OXK4-46QUC-KFB7B-4N3W7-M2BXM",
tencentSecret: "vb7D0PGj7xUvmOLuJz2Jd7ykTMpjiWRJ", tencentSecret: "vb7D0PGj7xUvmOLuJz2Jd7ykTMpjiWRJ",

View File

@ -96,7 +96,7 @@
</view> --> </view> -->
</view> </view>
<view class="payBtn" @click.stop="writeOff"> <view class="payBtn" @click.stop="writeOff">
核销 预约
</view> </view>
</view> </view>
</view> </view>
@ -147,6 +147,12 @@ export default {
components: { components: {
InfoCell, InfoCell,
}, },
props: {
orderId: {
type: [String, Number],
default: null
}
},
data() { data() {
return { return {
imgPrefix, imgPrefix,
@ -156,7 +162,7 @@ export default {
ORDER_TYPE_RESERVATION, ORDER_TYPE_RESERVATION,
ORDER_TYPE_SITE, ORDER_TYPE_SITE,
orderType: ORDER_TYPE_RESERVATION, orderType: ORDER_TYPE_RESERVATION,
selectedPetType: PET_TYPE_CAT, // 默认选中“猫” selectedPetType: PET_TYPE_CAT, // 默认选中"猫"
petInfo: {}, // 兼容下游,取 selectedPets[0] petInfo: {}, // 兼容下游,取 selectedPets[0]
selectedPets: [], // 多选宠物列表 selectedPets: [], // 多选宠物列表
parkState: "", parkState: "",
@ -168,11 +174,24 @@ export default {
address: null, address: null,
catHtmlData: "", catHtmlData: "",
dogHtmlData: "", dogHtmlData: "",
tip: '' tip: '',
localOrderId: null, // 本地存储的 orderId
}; };
}, },
mounted() { mounted() {
this.initData(); this.initData();
// 如果有传入的 orderId保存到本地
if (this.orderId) {
this.localOrderId = this.orderId;
}
},
watch: {
// 监听 orderId 变化
orderId(newVal) {
if (newVal) {
this.localOrderId = newVal;
}
}
}, },
computed: { computed: {
// 展示价格discount_price 数组相加的总和 // 展示价格discount_price 数组相加的总和
@ -186,26 +205,87 @@ export default {
} }
}, },
methods: { methods: {
// 用于接收从父页面传递的数据
onShowFun(orderId) {
if (orderId) {
this.localOrderId = orderId;
}
},
// 核销按钮点击处理 // 核销按钮点击处理
async writeOff () { async writeOff () {
const data = { // 校验必填项
goods_order_id:1, //抖音商品订单id整数 if (Object.keys(this.reservationTime).length === 0) {
order_date:'2026-03-01', //预约日期2026-03-01格式 uni.showToast({
period_id:1, //预约时间段id整数 title: "请选择预约时间",
address_id:1, //地址id整数 icon: "none",
recipient_name:'用户姓名', //用户姓名 });
phone:123, //用户手机 return;
park_desc:123, //停车描述 }
note:'哈哈' // 备注 if (!this.address) {
uni.showToast({
title: "请选择服务地址",
icon: "none",
});
return;
}
if (!this.parkState) {
uni.showToast({
title: "请选择停车状况",
icon: "none",
});
return;
}
if (!this.localOrderId) {
uni.showToast({
title: "订单ID缺失",
icon: "none",
});
return;
}
const data = {
goods_order_id: Number(this.localOrderId), //抖音商品订单id整数
order_date: this.reservationTime.date, //预约日期('2026-03-01'格式)
period_id: Number(this.reservationTime.id), //预约时间段id整数
address_id: Number(this.address.id), //地址id整数
recipient_name: this.address.name || '用户姓名', //用户姓名
phone: this.address.phone || '', //用户手机
park_desc: this.parkState || '', //停车描述
note: this.otherParkState || '' // 备注
}; };
uni.showLoading({
title: "处理中",
mask: true,
});
getdouyReview(data).then((res) => { getdouyReview(data).then((res) => {
uni.hideLoading();
if (res.code == 0) { if (res.code == 0) {
uni.showToast({ uni.showToast({
title: "预约成功", title: "预约成功",
icon: "none", icon: "none",
}); });
} // 预约成功后清空数据并跳转
}); setTimeout(() => {
uni.reLaunch({
url: "/pages/client/index/index?activePageId=homePage",
});
}, 1500);
} else {
uni.showToast({
title: res.msg || "预约失败",
icon: "none",
});
}
}).catch((err) => {
uni.hideLoading();
console.error("预约失败:", err);
uni.showToast({
title: "预约失败,请重试",
icon: "none",
});
});
}, },
getPetShortName(pet) { getPetShortName(pet) {

View File

@ -301,7 +301,7 @@
"style": { "style": {
"navigationBarTitleText": "申请售后" "navigationBarTitleText": "申请售后"
} }
}, },
{ {
"path": "details", "path": "details",
"style": { "style": {
@ -322,7 +322,7 @@
"style": { "style": {
"navigationBarTitleText": "商城订单", "navigationBarTitleText": "商城订单",
"usingComponents": { "usingComponents": {
"pay-button-sdk": "tta5a3d31e3aecfb9b11://pay-button" "pay-button": "tta5a3d31e3aecfb9b11://pay-button"
} }
} }
}, },

View File

@ -1,17 +1,28 @@
<template> <template>
<view class="loginContainer"> <view class="loginContainer">
<view class="body"> <view class="body">
<image class="login-ground-img" :src="`${imgPrefix}loginGroundImg.png`" mode="widthFix" /> <image
class="login-ground-img"
:src="`${imgPrefix}loginGroundImg.png`"
mode="widthFix"
/>
<view class="btnConent"> <view class="btnConent">
<button v-show="!checked" class="loginBtn" @click="unCheckAndGetPhoneNumber"> <button
v-show="!checked"
class="loginBtn"
@click="unCheckAndGetPhoneNumber"
>
抖音用户信息授权登录 抖音用户信息授权登录
</button> </button>
<button v-show="checked" class="loginBtn" @tap="getPhoneNumber" > <button
抖音用户信息授权登录 v-show="checked"
class="loginBtn"
open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber"
>
抖音用户信息授权登录
</button> </button>
<view class="notLoginBtn" @click="goBack"> <view class="notLoginBtn" @click="goBack"> 暂不登录 </view>
暂不登录
</view>
</view> </view>
</view> </view>
@ -19,7 +30,11 @@
<view class="radioWrapper"> <view class="radioWrapper">
<view class="checkbox-wrapper" @click.stop="changeChecked"> <view class="checkbox-wrapper" @click.stop="changeChecked">
<view class="checkbox" :class="{ 'checkbox-checked': checked }"> <view class="checkbox" :class="{ 'checkbox-checked': checked }">
<image v-if="checked" class="check-icon" :src="require('@/static/images/y.png')" /> <image
v-if="checked"
class="check-icon"
:src="require('@/static/images/y.png')"
/>
</view> </view>
<!-- <view v-if="uncheckMessageDialog" class="tooltip"> <!-- <view v-if="uncheckMessageDialog" class="tooltip">
<view class="tooltip-content">请先勾选同意后再进行登录</view> <view class="tooltip-content">请先勾选同意后再进行登录</view>
@ -28,7 +43,9 @@
</view> </view>
<text class="radioText"> <text class="radioText">
请阅读并同意 请阅读并同意
<text class="color" @click.stop="ptfwxy">帮宠到家平台服务协议</text> <text class="color" @click.stop="ptfwxy"
>帮宠到家平台服务协议</text
>
<text class="color" @click.stop="ysxy">隐私协议</text> <text class="color" @click.stop="ysxy">隐私协议</text>
</text> </text>
</view> </view>
@ -54,6 +71,16 @@ export default {
}, },
onLoad(options) { onLoad(options) {
this.yaoqing_code = options.yaoqing_code this.yaoqing_code = options.yaoqing_code
tt.login({
provider: "toutiao",
success: function (loginRes) {
console.log('获取 login code 成功:', loginRes);
},
fail: function (err) {
console.log('获取 login code 失败:', err);
},
});
}, },
mounted() { }, mounted() { },
methods: { methods: {
@ -81,14 +108,14 @@ export default {
duration: 2000 duration: 2000
}); });
}, },
getmembeInfo(nickName,avatarUrl) { getmembeInfo(phone) {
const inviteCode = this.yaoqing_code ? this.yaoqing_code : getApp().globalData.inviteCode const inviteCode = this.yaoqing_code ? this.yaoqing_code : getApp().globalData.inviteCode
uni.showLoading({ uni.showLoading({
title: "登录中...", title: "登录中...",
icon: "none", icon: "none",
mask: true, mask: true,
}); });
login(nickName,avatarUrl).then((res) => { login(phone).then((res) => {
uni.hideLoading() uni.hideLoading()
this.$store.dispatch("user/setToken", res?.data?.token || ""); this.$store.dispatch("user/setToken", res?.data?.token || "");
this.$store.dispatch("user/setUserInfo", res?.data || {}); this.$store.dispatch("user/setUserInfo", res?.data || {});
@ -143,77 +170,128 @@ export default {
// }); // });
// }), // }),
async getPhoneNumber(e) { async getPhoneNumber(e) {
// console.log(e,'?') console.log('获取手机号事件:', e);
// return this.getmembeInfo(e.detail.code)
// 检查是否有 code
// if (!e.detail.code) { // // 检查是否有 code
// if (!e.detail || !e.detail.code) {
// uni.showToast({ // uni.showToast({
// title: "获取手机号失败,请重试", // title: "用户取消授权或获取手机号失败",
// icon: "none", // icon: "none",
// }); // });
// return; // return;
// } // }
// uni.showLoading({ // uni.showLoading({
// title: "获取手机号中...", // title: "登录中...",
// icon: "none", // icon: "none",
// mask: true, // mask: true,
// }); // });
try { // try {
let that = this // const that = this;
tt.getUserProfile({ // let loginCode = null;
success(res) { // // 先获取 login code
console.log(res,'???') // try {
// this.nickName = res.res.userInfo.nickName // loginCode = await new Promise((resolve, reject) => {
// this.avatarUrl = res.userInfo.avatarUrl // tt.login({
that.getmembeInfo(res.userInfo.nickName,res.userInfo.avatarUrl) // provider: "toutiao",
// console.log(res,'--=') // success: function (loginRes) {
}, // console.log('获取 login code 成功:', loginRes);
fail(res) { // resolve(loginRes.code);
console.log("getUserProfile 调用失败", res); // },
}, // fail: function (err) {
// console.log('获取 login code 失败:', err);
// reject(err);
// },
// });
// });
// } catch (loginErr) {
// throw new Error('获取登录凭证失败');
// }
}); // // 拿着 phone code 和 login code 获取手机号
// let phone = null;
// try {
// const codeRes = await getPhone(e.detail.code, loginCode);
// phone = codeRes?.data?.phoneNumber || "";
// console.log('获取手机号成功:', phone);
// } catch (phoneErr) {
// console.error('获取手机号失败:', phoneErr);
// throw new Error('获取手机号失败');
// }
// const inviteCode = this.yaoqing_code ? this.yaoqing_code : getApp().globalData.inviteCode // if (!phone) {
// console.log('获取到邀请码----->', inviteCode) // throw new Error('获取手机号失败,请重试');
// 手机号登录 // }
// 拿着code获取手机号
// const codeRes = await getPhone(e.detail.code);
// const phone = codeRes?.data?.phoneNumber || "";
// if (!phone) { // 获取用户信息
// uni.hideLoading(); // tt.getUserProfile({
// uni.showToast({ // success: async (profileRes) => {
// title: "获取手机号失败,请重试", // console.log('获取用户信息成功:', profileRes);
// icon: "none", // const nickName = profileRes.userInfo?.nickName || '';
// }); // const avatarUrl = profileRes.userInfo?.avatarUrl || '';
// return;
// } // try {
// const inviteCode = that.yaoqing_code ? that.yaoqing_code : getApp().globalData.inviteCode;
} catch (error) { // // 登录
uni.hideLoading(); // const loginRes = await login(nickName, avatarUrl, phone);
console.error("获取手机号失败:", error); // uni.hideLoading();
// 检查是否是 access_token 相关错误
const errorMsg = error?.message || error || ""; // that.$store.dispatch("user/setToken", loginRes?.data?.token || "");
if (errorMsg.includes("access_token") || errorMsg.includes("invalid credential")) { // that.$store.dispatch("user/setUserInfo", loginRes?.data || {});
uni.showToast({
title: "服务暂时不可用,请稍后重试", // var pages = getCurrentPages();
icon: "none", // if (inviteCode) {
duration: 3000, // uni.reLaunch({
}); // url: "/pages/client/index/index",
} else { // });
uni.showToast({ // return;
title: errorMsg || "获取手机号失败,请重试", // }
icon: "none", // if (pages.length === 1) {
}); // uni.reLaunch({
} // url: "/pages/client/index/index",
} // });
// return;
// }
// uni.navigateBack();
// } catch (loginErr) {
// uni.hideLoading();
// console.error('登录失败:', loginErr);
// uni.showToast({
// title: loginErr || "登录失败,请重试",
// icon: "none",
// });
// }
// },
// fail: (profileErr) => {
// uni.hideLoading();
// console.log("getUserProfile 调用失败", profileErr);
// uni.showToast({
// title: "获取用户信息失败",
// icon: "none",
// });
// },
// });
// } catch (error) {
// uni.hideLoading();
// console.error("获取手机号失败:", error);
// const errorMsg = error?.message || error || "";
// if (errorMsg.includes("access_token") || errorMsg.includes("invalid credential")) {
// uni.showToast({
// title: "服务暂时不可用,请稍后重试",
// icon: "none",
// duration: 3000,
// });
// } else {
// uni.showToast({
// title: errorMsg || "获取手机号失败,请重试",
// icon: "none",
// });
// }
// }
}, },
loginAction() { loginAction() {
if (!this.checked) { if (!this.checked) {
@ -300,8 +378,8 @@ export default {
.notLoginBtn { .notLoginBtn {
background-color: #fff; background-color: #fff;
color: #FF19A0; color: #ff19a0;
border: 1rpx solid #FF19A0; border: 1rpx solid #ff19a0;
border-radius: 300rpx; border-radius: 300rpx;
padding: 26rpx 0rpx; padding: 26rpx 0rpx;
text-align: center; text-align: center;
@ -333,13 +411,13 @@ export default {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: #FFFFFF; background: #ffffff;
transition: all 0.3s ease; transition: all 0.3s ease;
flex-shrink: 0; flex-shrink: 0;
&.checkbox-checked { &.checkbox-checked {
border-color: #FF19A0; border-color: #ff19a0;
background: #FF19A0; background: #ff19a0;
} }
.check-icon { .check-icon {
@ -362,7 +440,7 @@ export default {
.tooltip-content { .tooltip-content {
font-size: 24rpx; font-size: 24rpx;
color: #FFFFFF; color: #ffffff;
line-height: 1.4; line-height: 1.4;
} }
@ -385,7 +463,7 @@ export default {
color: #9b939a; color: #9b939a;
.color { .color {
color: #FF19A0; color: #ff19a0;
font-size: 22rpx; font-size: 22rpx;
} }
} }

View File

@ -1,196 +1,146 @@
<template> <template>
<view class="flex-row-start goods-item" @click.stop="$emit('clickCard', data)"> <view class="goods-item" >
<image class="goods-img" :src="data.product_pic" mode="aspectFill" /> <image @click.stop="handleBuyNow" class="goods-img" :src="data.product_pic" mode="aspectFill" />
<view class="goods-content"> <view class="fs-24 app-fc-main goods-name">
<view class="text-multi-ellipse fs-28 app-fc-main app-font-bold goods-name"> {{ data.product_name || "" }}
{{ data.product_name || "" }} </view>
</view> <view class="flex-row-start label">
<!-- <view class="flex-row-start label"> <image class="hot-icon" :src="`${imgPrefix}mall-hot.png`"></image>
<image class="hot-icon" :src="`${imgPrefix}mall-hot.png`"></image> <view class="fs-20 app-fc-main label-name">32人买过</view>
<view class="fs-20 app-fc-main label-name">{{data.sales}}人买过</view> </view>
</view> --> <view class="flex-row-between" style="margin-top: 12rpx; align-items: baseline;">
<view class="price-row"> <view class="price-wrapper">
<view class="flex-row-start"> <text class="fs-28" style="color: #FF19A0;">
<text class="fs-28 price-text"> ¥
¥ <text class="fs-28">{{
<text class="fs-28">{{data.prices[0].original_price / 100 || 0 }}</text> data.prices[0].original_price / 100 || 0
</text> }}</text>
<!-- <text class="fs-20 price-label">到手价</text> --> </text>
</view> </view>
<text class="fs-24 origin-price" v-if="minPrice.price_shichang"> <view class="buy-now-btn" @click.stop="handleBuyNow">
¥{{ minPrice.price_shichang || 0 }} 立即购买
</text> </view>
</view> </view>
<view class="buy-now-btn-wrapper" @click.stop="handleBuyNow"> </view>
<text class="buy-now-btn">立即购买</text>
</view>
</view>
</view>
</template> </template>
<script> <script>
import { imgPrefix } from '@/utils/common'; import {
imgPrefix
} from '@/utils/common';
export default { export default {
props: { props: {
index: { index: {
type: Number, type: Number,
default: 0, default: 0,
}, },
data: { data: {
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
}, },
data() { data() {
return { return {
imgPrefix, imgPrefix,
isAnimating: false, };
}; },
}, computed: {
computed: { labelList() {
// minPrice() { return (this.data?.label || "").split(",").filter((v) => !!v);
// let minPrice = {}; },
// let minPriceValue = 0; },
// this.data.price_list.map((v) => { mounted() {},
// if (!minPriceValue || minPriceValue > +v.price) { methods: {
// minPriceValue = +v.price; handleBuyNow() {
// minPrice = { ...v }; // 触发购买事件,通知父组件
// } this.$emit('addToCar', this.data);
// }); },
// return minPrice; triggerAddCartAnimation() {
// }, // 保留方法以兼容父组件调用
labelList() { }
return this.data.label.split(",").filter((v) => !!v); },
}, };
},
mounted() {},
methods: {
// 触发添加购物车动画
triggerAddCartAnimation() {
this.isAnimating = true;
setTimeout(() => {
this.isAnimating = false;
}, 600);
},
// 立即购买
handleBuyNow() {
this.$emit('buyNow', this.data);
},
},
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.goods-item { .goods-item {
background: #fff; background: #fff;
border-radius: 40rpx; border-radius: 16rpx;
width: 100%; padding: 20rpx;
padding: 20rpx; box-sizing: border-box;
box-sizing: border-box; margin-bottom: 22rpx;
margin-bottom: 20rpx; position: relative;
position: relative; width: 100%;
border-bottom: 1rpx solid #F5F5F5; min-width: 0;
overflow: hidden;
.goods-img { .goods-img {
border-radius: 20rpx; display: block;
width: 160rpx; width: 100%;
height: 160rpx; max-width: 100%;
} height: 260rpx;
border-radius: 16rpx;
background: #f5f5f5;
}
.goods-content { .goods-name {
flex: 1; margin: 16rpx 0 12rpx 0;
overflow: hidden; overflow: hidden;
margin-left: 20rpx; text-overflow: ellipsis;
} display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
word-wrap: break-word;
word-break: break-all;
font-size: 24rpx;
color: #3D3D3D;
}
.goods-name { .label {
margin: 0 0 12rpx; background-color: #ffecf3;
} display: inline-flex;
align-items: center;
border-radius: 4rpx;
padding: 4rpx 8rpx;
.label { .hot-icon {
background-color: #ffecf3; width: 28rpx;
display: inline-flex; height: 28rpx;
align-items: center; }
border-radius: 4rpx;
margin-bottom: 12rpx;
.hot-icon { .label-name {
width: 28rpx; padding-left: 4rpx;
height: 28rpx; color: #FF19A0;
margin-right: 6rpx; font-size: 20rpx;
} }
}
.label-name { .flex-row-between {
padding: 4rpx 8rpx; display: flex;
color: #FF19A0; justify-content: space-between;
} align-items: center;
} }
.price-row { .flex-row-start {
margin-top: 12rpx; display: flex;
display: flex; justify-content: flex-start;
flex-direction: column; align-items: center;
} }
.price-text { .price-wrapper {
color: #3D3D3D; display: flex;
} align-items: baseline;
}
.price-label { .buy-now-btn {
color: #999; background: #FF19A0;
margin-left: 8rpx; color: #fff;
} font-size: 20rpx;
padding: 10rpx 20rpx;
.origin-price { border-radius: 30rpx;
color: #999; font-weight: 500;
margin-top: 8rpx; flex-shrink: 0;
} }
}
.add-cart-icon { </style>
width: 44rpx;
height: 44rpx;
position: absolute;
bottom: 20rpx;
right: 20rpx;
transition: transform 0.3s ease;
&.add-cart-icon-animate {
animation: addCartBounce 0.6s ease;
}
}
.buy-now-btn-wrapper {
margin-top: 16rpx;
display: flex;
justify-content: flex-end;
.buy-now-btn {
background: linear-gradient(90deg, #FF19A0, #FF4DB8);
color: #FFFFFF;
font-size: 24rpx;
padding: 12rpx 32rpx;
border-radius: 24rpx;
font-weight: 500;
}
}
}
@keyframes addCartBounce {
0% {
transform: scale(1) rotate(0deg);
}
25% {
transform: scale(1.2) rotate(-10deg);
}
50% {
transform: scale(1.3) rotate(10deg);
}
75% {
transform: scale(1.1) rotate(-5deg);
}
100% {
transform: scale(1) rotate(0deg);
}
}
</style>

View File

@ -70,8 +70,14 @@
<scroll-view class="category-right" scroll-y :refresher-enabled="true" <scroll-view class="category-right" scroll-y :refresher-enabled="true"
:refresher-triggered="refreshTriggered" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore"> :refresher-triggered="refreshTriggered" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore">
<view class="goods-list"> <view class="goods-list">
<good-item v-for="good in goodsList" :key="good.product_id" :ref="`goodItem_${good.product_id}`" <view class="goods-list-item left">
:data="good" @addToCar="addToCar" @clickCard="jumpToDetail" /> <good-item v-for="(good, i) in leftColumnGoods" :key="2 * i" :ref="`goodItem_${good.product_id}`"
:data="good" @addToCar="addToCar" />
</view>
<view class="goods-list-item right">
<good-item v-for="(good, i) in rightColumnGoods" :key="2 * i + 1" :ref="`goodItem_${good.product_id}`"
:data="good" @addToCar="addToCar" />
</view>
</view> </view>
</scroll-view> </scroll-view>
</view> </view>
@ -109,6 +115,7 @@ import CategoryModal from "./components/CategoryModal.vue";
import { import {
getGoodsListData getGoodsListData
} from "../../../api/shop"; } from "../../../api/shop";
import { getCategoryGoodsWithCache } from "@/utils/goodsCache";
export default { export default {
components: { components: {
@ -152,6 +159,8 @@ export default {
total: 0, total: 0,
page: 1, page: 1,
size: 10, size: 10,
nextCursor: '',
hasMore: true,
refreshTriggered: false, refreshTriggered: false,
isLoading: false, isLoading: false,
petOrderId: '', petOrderId: '',
@ -179,6 +188,12 @@ export default {
) || {} ) || {}
); );
}, },
leftColumnGoods() {
return this.goodsList.filter((v, i) => i % 2 === 0);
},
rightColumnGoods() {
return this.goodsList.filter((v, i) => i % 2 === 1);
},
}, },
mounted() { mounted() {
this.getCategoryList(); this.getCategoryList();
@ -195,6 +210,10 @@ export default {
}, },
onShow() { onShow() {
this.getCartListData(); this.getCartListData();
// 页面显示时检查是否需要刷新商品数据
if (this.selectCategoryId) {
this.getShopList(false);
}
}, },
watch: { watch: {
selectCategoryId(val) { selectCategoryId(val) {
@ -224,27 +243,70 @@ export default {
} }
}); });
}, },
changeCateg(item) { changeCateg(item) {
this.changeId = item.id this.changeId = item.id
this.selectCategoryId = item.id; this.selectCategoryId = item.id;
this.page = 1; // 切换分类时重置分页
this.nextCursor = '';
this.hasMore = true;
this.goodsList = []; // 清空当前商品列表
this.showAllCategory = false; this.showAllCategory = false;
// console.log(item,'--') this.getShopList(true); // 切换分类时强制刷新
}, },
// 商品列表 // 商品列表
getShopList() { getShopList(forceRefresh = false) {
getGoodsListData({ // 如果不是第一页且没有更多数据,直接返回
if (this.page > 1 && !this.hasMore) {
return;
}
const params = {
type: this.changeId, type: this.changeId,
p: this.page, p: this.page,
num: this.size, num: this.size,
keyword: "", keyword: "",
is_tui: 0, is_tui: 0,
}) cursor: this.nextCursor,
};
// 第一页使用缓存,后续页直接请求
if (this.page > 1) {
getGoodsListData(params)
.then((res) => {
const list = res?.data.data?.products || res?.data || [];
const hasMore = res?.data.data?.has_more;
const nextCursor = res?.data.data?.next_cursor || '';
this.goodsList = [...this.goodsList, ...list];
this.hasMore = hasMore;
this.nextCursor = nextCursor;
this.total = res?.count || 0;
})
.finally(() => {
this.isLoading = false;
this.refreshTriggered = false;
});
return;
}
// 第一页使用缓存
getCategoryGoodsWithCache(params, forceRefresh)
.then((res) => { .then((res) => {
const list = res?.data || []; const list = res?.data?.products || res?.data || [];
this.goodsList = const hasMore = res?.data?.has_more;
this.page === 1 ? list : [...this.goodsList, ...list]; const nextCursor = res?.data?.next_cursor || '';
this.total = res?.count || 0;
// 只有当数据有变化时才更新商品列表,避免不必要的刷新
if (res.hasChanged !== false || this.goodsList.length === 0) {
this.goodsList = list;
}
this.hasMore = hasMore;
this.nextCursor = nextCursor;
this.total = res?.count || list.length || 0;
})
.catch((err) => {
console.error('获取商品列表失败', err);
}) })
.finally(() => { .finally(() => {
this.isLoading = false; this.isLoading = false;
@ -349,15 +411,17 @@ export default {
url: `/pages/client/shop/details?product_id=${details.product_id}&petOrderId=${this.petOrderId}&petOrderAddressId=${this.petOrderAddressId}`, url: `/pages/client/shop/details?product_id=${details.product_id}&petOrderId=${this.petOrderId}&petOrderAddressId=${this.petOrderAddressId}`,
}); });
}, },
onRefresh() { onRefresh() {
this.refreshTriggered = true; this.refreshTriggered = true;
this.page = 1; this.page = 1;
this.size = 10; this.size = 10;
this.total = 0; this.total = 0;
this.getShopList(); this.nextCursor = '';
this.hasMore = true;
this.getShopList(true); // 下拉刷新时强制刷新
}, },
onLoadMore() { onLoadMore() {
if (!this.isLoading && this.total > this.goodsList.length) { if (!this.isLoading && this.hasMore) {
this.page++; this.page++;
this.getShopList(); this.getShopList();
} }
@ -385,7 +449,7 @@ export default {
height: 100%; height: 100%;
align-items: stretch; align-items: stretch;
position: relative; position: relative;
padding-top: calc(var(--status-bar-height, 0px) + 88rpx + 20rpx); padding-top: 0;
.custom-navbar { .custom-navbar {
position: fixed; position: fixed;
@ -394,7 +458,7 @@ export default {
right: 0; right: 0;
z-index: 999; z-index: 999;
background: #ff19a0; background: #ff19a0;
border-radius: 0px 0px 16px 16px; border-radius: 0px;
.status-bar { .status-bar {
background: #ff19a0; background: #ff19a0;
@ -589,6 +653,24 @@ export default {
.category-right { .category-right {
flex: 1; flex: 1;
height: 100%; height: 100%;
.goods-list {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
margin-top: 20rpx;
padding: 0 20rpx 120rpx;
.goods-list-item {
flex: 1;
min-width: 0;
&.left {
margin-right: 20rpx;
}
}
}
} }
} }
} }

View File

@ -6,7 +6,7 @@
<!-- 可拖动联系客服组件 --> <!-- 可拖动联系客服组件 -->
<DraggableContact ref="draggableContact" :onClick="handleContactClick" /> <DraggableContact ref="draggableContact" :onClick="handleContactClick" />
<scroll-view class="homeContainer" scroll-y :show-scrollbar="false" :enhanced="true"> <scroll-view class="homeContainer" scroll-y :show-scrollbar="false" :enhanced="true" @scrolltolower="onLoadMore" :refresher-enabled="true" :refresher-triggered="refreshTriggered" @refresherrefresh="onRefresh">
<view class="swiperWrapper"> <view class="swiperWrapper">
<swiper indicator-dots="true" autoplay="true" interval="3000" duration="500" circular="true" <swiper indicator-dots="true" autoplay="true" interval="3000" duration="500" circular="true"
class="swiper"> class="swiper">
@ -48,6 +48,9 @@
<view class="loginBtn" @click="toLogin" v-if="!userInfo.userID"> <view class="loginBtn" @click="toLogin" v-if="!userInfo.userID">
注册/登陆 注册/登陆
</view> </view>
<view class="logoutBtn" @click="logout" v-if="userInfo.userID">
退出登录
</view>
</view> </view>
<view class="shadowBackground" /> <view class="shadowBackground" />
@ -59,11 +62,11 @@
</view> --> </view> -->
<view class="goods-list"> <view class="goods-list">
<view class="goods-list-item left"> <view class="goods-list-item left">
<good-item v-for="(good, i) in leftColumnGoods" :index="2 * i" :key="2 * i" :data="good" <good-item v-for="(good, i) in leftColumnGoods" :index="2 * i" :key="2 * i" :data="good" :isHome="true"
@addToCar="addToCar" /> @addToCar="addToCar" />
</view> </view>
<view class="goods-list-item right"> <view class="goods-list-item right">
<good-item v-for="(good, i) in rightColumnGoods" :index="2 * i + 1" :key="2 * i + 1" :data="good" <good-item v-for="(good, i) in rightColumnGoods" :index="2 * i + 1" :key="2 * i + 1" :data="good" :isHome="true"
@addToCar="addToCar" /> @addToCar="addToCar" />
</view> </view>
</view> </view>
@ -85,6 +88,9 @@ import {
import { import {
userWllet userWllet
} from "../../../api/login"; } from "../../../api/login";
import {
loginOut,
} from "../../../api/user";
import WeChatCopyModal from "@/components/WeChatCopyModal.vue"; import WeChatCopyModal from "@/components/WeChatCopyModal.vue";
import GoodItem from "../shop/components/GoodItem.vue"; import GoodItem from "../shop/components/GoodItem.vue";
import DraggableContact from "@/components/DraggableContact.vue"; import DraggableContact from "@/components/DraggableContact.vue";
@ -92,6 +98,7 @@ import {
getGoodsClassify, getGoodsClassify,
getGoodsListData getGoodsListData
} from "@/api/shop"; } from "@/api/shop";
import { getHomeGoodsWithCache } from "@/utils/goodsCache";
export default { export default {
name: "HomePage", name: "HomePage",
@ -107,6 +114,8 @@ export default {
goodsList: [], // 商品列表 goodsList: [], // 商品列表
goodsTotal: 0, // 商品总数 goodsTotal: 0, // 商品总数
goodPage: 1, // 当前页码 goodPage: 1, // 当前页码
nextCursor: '', // 下一页游标
hasMore: true, // 是否还有更多数据
isLoadingGoods: false, // 是否正在加载商品 isLoadingGoods: false, // 是否正在加载商品
refreshTriggered: false, // 刷新是否已触发 refreshTriggered: false, // 刷新是否已触发
cartCount: 0, // 购物车数量 cartCount: 0, // 购物车数量
@ -171,22 +180,68 @@ export default {
return this.cartCount > 9 ? "9+" : this.cartCount; return this.cartCount > 9 ? "9+" : this.cartCount;
} }
}, },
created() { created() {
this.goodPage = 1;
this.nextCursor = '';
this.hasMore = true;
this.goodsList = [];
this.getGoodsList() this.getGoodsList()
}, },
onShow() {
// 页面显示时检查是否需要刷新商品数据
this.getGoodsList(false);
},
methods: { methods: {
getGoodsList() { getGoodsList(forceRefresh = false) {
if (this.isLoadingGoods) return; if (this.isLoadingGoods) return;
if (!this.hasMore && this.goodPage > 1) return;
this.isLoadingGoods = true; this.isLoadingGoods = true;
const params = { const params = {
type: 0 type: 0,
cursor: this.nextCursor
} }
getGoodsListData(params)
// 加载更多时直接请求,不使用缓存
if (this.goodPage > 1) {
getGoodsListData(params)
.then((res) => {
const list = res?.data.data?.products || res?.data || [];
const hasMore = res?.data.data?.has_more;
const nextCursor = res?.data.data?.next_cursor || '';
this.goodsList = [...this.goodsList, ...list];
this.hasMore = hasMore;
this.nextCursor = nextCursor;
this.goodsTotal = res?.count || 0;
})
.catch((err) => {
console.error('获取商品列表失败', err);
})
.finally(() => {
this.isLoadingGoods = false;
this.refreshTriggered = false;
});
return;
}
// 第一页使用缓存
getHomeGoodsWithCache(params, forceRefresh)
.then((res) => { .then((res) => {
const list = res?.data.data.products || []; const list = res?.data.data?.products || res?.data.data || [];
this.goodsList = list; const hasMore = res?.data.data?.has_more;
console.log(this.goodsList,'???') const nextCursor = res?.data.data?.next_cursor || '';
this.goodsTotal = res?.count || 0;
// 只有当数据有变化时才更新商品列表,避免不必要的刷新
if (res.hasChanged !== false || this.goodsList.length === 0) {
this.goodsList = list;
}
this.hasMore = hasMore;
this.nextCursor = nextCursor;
this.goodsTotal = res?.count || list.length || 0;
})
.catch((err) => {
console.error('获取商品列表失败', err);
}) })
.finally(() => { .finally(() => {
this.isLoadingGoods = false; this.isLoadingGoods = false;
@ -198,6 +253,17 @@ export default {
url: "/pages/client/auth/index", url: "/pages/client/auth/index",
}); });
}, },
logout() {
loginOut();
// 清除Vuex中的用户状态
this.$store.dispatch('user/deleteToken');
this.$store.dispatch('user/clearUserInfo');
// 清除本地缓存
uni.clearStorageSync();
uni.reLaunch({
url: "/pages/client/auth/index",
});
},
// 统一的 token 检查方法,未登录时弹窗让用户自主选择 // 统一的 token 检查方法,未登录时弹窗让用户自主选择
async checkTokenAndExecute(callback) { async checkTokenAndExecute(callback) {
const token = uni.getStorageSync('token'); const token = uni.getStorageSync('token');
@ -335,24 +401,23 @@ export default {
console.error('Error parsing image_list:', e); console.error('Error parsing image_list:', e);
} }
} }
uni.navigateTo({ uni.navigateTo({
url: `/pages/client/order/create`, url: `/pages/client/order/create?product_id=${goodsData.product.product_id}`,
success: (res) => { // success: (res) => {
// 通过eventChannel向被打开页面传送数据 // // 通过eventChannel向被打开页面传送数据
res.eventChannel.emit("createOrder", { // res.eventChannel.emit("createOrder", {
goodList: [{ // goodList: [{
...this.goodsData, // ...this.goodsData,
goods_id:goodsData.product.out_id, // goods_id:goodsData.product.out_id,
// price_id:goodsData.prices[0].price_id, // // price_id:goodsData.prices[0].price_id,
product_pic: firstImageUrl, // product_pic: firstImageUrl,
number:1, // number:1,
goods_name: goodsData.product.product_name, // goods_name: goodsData.product.product_name,
price_name: goodsData?.product.product_name, // price_name: goodsData?.product.product_name,
goods_price: goodsData.sku.actual_amount / 100 // goods_price: goodsData.sku.actual_amount / 100
}, ], // }, ],
}); // });
}, // },
}); });
}, },
@ -376,6 +441,19 @@ export default {
}); });
} }
}, },
onLoadMore() {
if (!this.isLoadingGoods && this.hasMore) {
this.goodPage++;
this.getGoodsList();
}
},
onRefresh() {
this.refreshTriggered = true;
this.goodPage = 1;
this.nextCursor = '';
this.hasMore = true;
this.getGoodsList(true);
},
}, },
}; };
</script> </script>
@ -384,6 +462,7 @@ export default {
.home-page { .home-page {
height: 100vh; height: 100vh;
background-color: #ffecf3; background-color: #ffecf3;
padding-top: 0;
} }
.recommand-goods-wrapper { .recommand-goods-wrapper {
@ -396,7 +475,8 @@ export default {
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
align-items: flex-start; align-items: flex-start;
margin-top: 40rpx; margin-top: 50rpx;
padding: 0 20rpx;
.goods-list-item { .goods-list-item {
flex: 1; flex: 1;
@ -442,10 +522,13 @@ export default {
.swiperWrapper { .swiperWrapper {
position: relative; position: relative;
margin-top: -88rpx;
.swiper { .swiper {
height: 552rpx; height: 552rpx;
width: 100%; width: 100%;
padding-top: 88rpx;
box-sizing: content-box;
.swiper-img { .swiper-img {
width: 100%; width: 100%;
@ -587,6 +670,16 @@ export default {
margin-left: 8rpx; margin-left: 8rpx;
} }
} }
.logoutBtn {
background: #FF19A0;
border-radius: 218px;
color: #fff;
font-size: 23rpx;
padding: 16rpx 24rpx;
display: flex;
align-items: center;
}
} }
.shadowBackground { .shadowBackground {

View File

@ -21,6 +21,7 @@
<reservation <reservation
v-show="activePageId === 'reservationPage'" v-show="activePageId === 'reservationPage'"
ref="reservationPage" ref="reservationPage"
:orderId="orderId"
/> />
</view> </view>
<tab-bar <tab-bar
@ -60,6 +61,7 @@ export default {
path: "/pages/client/index/index", path: "/pages/client/index/index",
}, },
getUserInfoPromise: null, // 用于防止重复调用 getUserInfo getUserInfoPromise: null, // 用于防止重复调用 getUserInfo
orderId: null, // 存储从订单页面传递过来的 orderId
}; };
}, },
methods: { methods: {
@ -165,12 +167,17 @@ export default {
this.$store.dispatch('user/setReferrerID', Number(option.referrerID) || 0); this.$store.dispatch('user/setReferrerID', Number(option.referrerID) || 0);
} }
// 处理从订单页面传递的 orderId
if (option?.orderId) {
this.orderId = option.orderId;
}
// 处理页面跳转参数 // 处理页面跳转参数
if (option?.activePageId) { if (option?.activePageId) {
const targetPageId = option.activePageId; const targetPageId = option.activePageId;
// 如果目标页面与当前页面不同,触发切换 // 如果目标页面与当前页面不同,触发切换
if (this.activePageId !== targetPageId) { if (this.activePageId !== targetPageId) {
this.handleTabChange([targetPageId]); this.handleTabChange([targetPageId], option.orderId);
} else { } else {
this.activePageId = targetPageId; this.activePageId = targetPageId;
} }

View File

@ -14,12 +14,18 @@
{{ userInfo.userID && userInfo.username ? userInfo.username : '嗨,你好呀' }} {{ userInfo.userID && userInfo.username ? userInfo.username : '嗨,你好呀' }}
</view> </view>
</view> </view>
<view class="userPhone" v-if="userInfo.phone">
{{ userInfo.phone }}
</view>
<!-- <view class="vipWrapper"> <!-- <view class="vipWrapper">
<image class="lableImg" :src="`${imgPrefix}home-vipLabel.png`" mode=""></image> <image class="lableImg" :src="`${imgPrefix}home-vipLabel.png`" mode=""></image>
v{{ userInfo.vipLevel || 1 }}会员 v{{ userInfo.vipLevel || 1 }}会员
</view> --> </view> -->
</view> </view>
</view> </view>
<view class="logoutBtn" @click="showLogoutModal = true">
退出登录
</view>
<!-- <view class="userRight"> <!-- <view class="userRight">
<view class="userRgihtItemView" @click="jumpTo('/pages/client/recharge/index?tab=points')"> <view class="userRgihtItemView" @click="jumpTo('/pages/client/recharge/index?tab=points')">
<view class="num"> <view class="num">
@ -300,6 +306,10 @@ components: {
logout() { logout() {
loginOut(); loginOut();
this.showLogoutModal = false; this.showLogoutModal = false;
// 清除Vuex中的用户状态
this.$store.dispatch('user/deleteToken');
this.$store.dispatch('user/clearUserInfo');
// 清除本地缓存
uni.clearStorageSync(); uni.clearStorageSync();
uni.reLaunch({ uni.reLaunch({
url: "/pages/client/auth/index", url: "/pages/client/auth/index",
@ -330,13 +340,17 @@ title: '请添加客服号',
overflow-y: auto; overflow-y: auto;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
height: calc(100vh - #{$app_tabbar_height + 26}); height: calc(100vh - #{$app_tabbar_height + 26});
padding-top: 0;
.mineGroundImgView { .mineGroundImgView {
position: relative; position: relative;
margin-top: -88rpx;
.groundImg { .groundImg {
width: 100%; width: 100%;
height: 552rpx; height: 552rpx;
padding-top: 88rpx;
box-sizing: content-box;
} }
} }
@ -386,6 +400,21 @@ title: '请添加客服号',
font-size: 28rpx; font-size: 28rpx;
font-weight: 500; font-weight: 500;
} }
.userPhone {
font-size: 24rpx;
color: #999;
margin-top: 8rpx;
}
}
.logoutBtn {
background: #FF19A0;
color: #fff;
padding: 16rpx 32rpx;
border-radius: 50rpx;
font-size: 24rpx;
font-weight: 500;
} }
.userRight { .userRight {
@ -460,7 +489,7 @@ title: '请添加客服号',
width: calc(100vw - 40rpx); width: calc(100vw - 40rpx);
background-color: #fff; background-color: #fff;
margin: auto; margin: auto;
margin-top: 20rpx; margin-top:46rpx;
border-radius: 16rpx; border-radius: 16rpx;
margin-bottom: 26rpx; margin-bottom: 26rpx;

View File

@ -3,7 +3,7 @@
<view class="edit-content"> <view class="edit-content">
<view class="flex-row-between edit-cell"> <view class="flex-row-between edit-cell">
<text class="title">头像</text> <text class="title">头像</text>
<button class="flex-row-end user-avator" open-type="chooseAvatar" @chooseavatar="chooseavatar"> <button class="flex-row-end user-avator" @click="chooseavatar">
<image class="avator-icon" :src="userInfo.head_pic_url" /> <image class="avator-icon" :src="userInfo.head_pic_url" />
<!-- <image class="arrow-icon" :src="`${imgPrefix}right-arrow.png`" /> --> <!-- <image class="arrow-icon" :src="`${imgPrefix}right-arrow.png`" /> -->
</button> </button>
@ -174,30 +174,41 @@ export default {
}); });
}, },
// 更新头像 // 更新头像
async chooseavatar(e) { chooseavatar() {
const { let that = this;
avatarUrl
} = e.detail;
uni.showLoading({ uni.showLoading({
title: "上传中..." title: "上传中..."
}); });
try { uni.chooseImage({
const { url, objectKey } = await uploadImageToOSS_PUT(avatarUrl); count: 1, // 只选一张图片
console.log(url, objectKey, 'url, objectKey') sourceType: ['album', 'camera'], // 来源:相册、相机
this.userInfo.head_pic_url = url; // 同时更新 head_pic_url因为模板使用的是这个字段 success: async function(res) {
this.userInfo.head_pic = objectKey; try {
this.$forceUpdate(); const { url, objectKey } = await uploadImageToOSS_PUT(res.tempFilePaths[0]);
uni.hideLoading(); that.userInfo.head_pic_url = url; // 同时更新 head_pic_url因为模板使用的是这个字段
} catch (error) { that.userInfo.head_pic = objectKey;
console.error('头像上传失败:', error); that.$forceUpdate();
uni.hideLoading(); uni.hideLoading();
uni.showToast({ uni.showToast({
title: error?.message || "头像上传失败", title: "上传成功",
icon: "none" icon: "success",
}); });
} } catch (error) {
console.error('头像上传失败:', error);
uni.hideLoading();
uni.showToast({
title: error?.message || "头像上传失败",
icon: "none"
});
}
},
fail: function(err) {
console.log('选择图片失败:', err.errMsg);
uni.hideLoading();
}
});
}, },
onChange(value, key) { onChange(value, key) {
this.userInfo[key] = value; this.userInfo[key] = value;

View File

@ -5,14 +5,11 @@
<view class="good-content" @click="$emit('clickGoodInfo', data[0])"> <view class="good-content" @click="$emit('clickGoodInfo', data[0])">
<view class="goods-row-first"> <view class="goods-row-first">
<view class="goods-name">{{ data[0].goods_name || "" }}</view> <view class="goods-name">{{ data[0].goods_name || "" }}</view>
<text class="goods-price">¥{{ data[0].goods_price || actual_price }}</text> <view class="fs-28 app-fc-main goods-price">
</view> ¥{{ data[0].goods_price || data[0].product_actual_price }}
<view class="goods-row-second">
<view class="goods-spec">
{{ data[0].shuxing_name || "" }}{{ data[0].shuxing_name && data[0].price_name ? ";" : "" }}{{ data[0].price_name || "" }}
</view> </view>
<text class="goods-count">{{ data[0].number || 1 }}</text>
</view> </view>
<view class="fs-24 app-fc-normal">{{ data[0].number || 1 }}</view>
</view> </view>
</template> </template>
@ -24,7 +21,7 @@
<view class="good-info-right"> <view class="good-info-right">
<text class="good-total-price">¥{{ actual_price }}</text> <text class="good-total-price">¥{{ actual_price }}</text>
<text class="fs-24 app-fc-normal good-num"> <text class="fs-24 app-fc-normal good-num">
1 {{ totalCount }}
</text> </text>
</view> </view>
</view> </view>
@ -56,6 +53,11 @@
return sum + price * number; return sum + price * number;
}, 0).toFixed(2); }, 0).toFixed(2);
}, },
totalCount() {
return this.data.reduce((sum, item) => {
return sum + parseInt(item.number || 1);
}, 0);
},
}, },
mounted() { mounted() {
console.log(this.data,'--') console.log(this.data,'--')
@ -76,68 +78,43 @@
&.good-info { &.good-info {
padding-top: 20rpx; padding-top: 20rpx;
align-items: center; align-items: flex-start;
.good-icon { .good-icon {
width: 100rpx; width: 160rpx;
height: 100rpx; height: 160rpx;
border-radius: 8rpx; border-radius: 16rpx;
background: #f5f5f5;
margin-right: 20rpx; margin-right: 20rpx;
flex-shrink: 0; flex-shrink: 0;
} }
.good-content { .good-content {
flex: 1; flex: 1;
min-width: 0; height: 160rpx;
overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 100rpx; justify-content: space-between;
.goods-row-first { .goods-row-first {
display: flex; display: flex;
align-items: center;
justify-content: space-between; justify-content: space-between;
margin-bottom: 8rpx; align-items: flex-start;
width: 100%;
.goods-name { .goods-name {
flex: 1;
font-size: 28rpx; font-size: 28rpx;
color: #3D3D3D; color: #3D3D3D;
line-height: 40rpx; line-height: 40rpx;
margin-right: 16rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 20rpx;
} }
.goods-price { .goods-price {
flex-shrink: 0; flex-shrink: 0;
font-size: 28rpx;
color: #3D3D3D;
font-weight: 500;
}
}
.goods-row-second {
display: flex;
align-items: center;
justify-content: space-between;
.goods-spec {
flex: 1;
font-size: 24rpx;
color: #999;
margin-right: 20rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.goods-count {
flex-shrink: 0;
font-size: 24rpx;
color: #999;
} }
} }
} }

View File

@ -1,21 +1,30 @@
<template> <template>
<view class="order-item"> <view class="order-item">
<view class="flex-row-between order-title" @click="jumpToDetails"> <view class="flex-row-between order-title" @click="jumpToDetails">
<text class="fs-24 app-fc-normal"> <text class="fs-24 app-fc-normal">
订单编号{{ data.order_no || "-" }} 订单编号{{ data.order_no || "-" }}
</text> </text>
<!-- 待支付状态显示倒计时横幅 --> <!-- 待支付状态显示倒计时横幅 -->
<view v-if="data.status === SHOP_ORDER_UNPAY && !data.tui_status && countDownTime > 0" <view
class="order-status-banner"> v-if="
<view class="status-banner-left"> data.status === SHOP_ORDER_UNPAY &&
<text class="status-text">等待付款</text> !data.tui_status &&
</view> countDownTime > 0
<view class="status-banner-right"> "
<text class="countdown-text">{{ formatCountdown(countDownTime) }}</text> class="order-status-banner"
</view> >
</view> <view class="status-banner-left">
<!-- 其他状态显示原有样式 --> <text class="status-text">等待付款</text>
<view v-else-if=" </view>
<view class="status-banner-right">
<text class="countdown-text">{{
formatCountdown(countDownTime)
}}</text>
</view>
</view>
<!-- 其他状态:显示原有样式 -->
<view
v-else-if="
([ ([
SHOP_ORDER_CANCEL, SHOP_ORDER_CANCEL,
SHOP_ORDER_UNPAY, SHOP_ORDER_UNPAY,
@ -26,428 +35,548 @@
].includes(data.status) && ].includes(data.status) &&
!data.tui_status) || !data.tui_status) ||
[SHOP_ORDER_AFTERSALE_REJECT].includes(data.tui_status) [SHOP_ORDER_AFTERSALE_REJECT].includes(data.tui_status)
" class="flex-center fs-24 order-btn" :class="[ "
class="flex-center fs-24 order-btn"
:class="[
![SHOP_ORDER_DONE, SHOP_ORDER_CANCEL, SHOP_ORDER_UNREMARK].includes( ![SHOP_ORDER_DONE, SHOP_ORDER_CANCEL, SHOP_ORDER_UNREMARK].includes(
data.status data.status
) )
? 'app-fc-mark confirm' ? 'app-fc-mark confirm'
: 'cancel', : 'cancel',
]"> ]"
{{ orderStatus }} >
</view> {{ orderStatus }}
<view v-if=" </view>
<view
v-if="
[SHOP_ORDER_AFTERSALE, SHOP_ORDER_AFTERSALE_DONE].includes( [SHOP_ORDER_AFTERSALE, SHOP_ORDER_AFTERSALE_DONE].includes(
data.tui_status data.tui_status
) )
" class="flex-center fs-24 order-btn cancel"> "
{{ refundOrderStatus }} class="flex-center fs-24 order-btn cancel"
</view> >
</view> {{ refundOrderStatus }}
<view class="order-content" :class="{ 'split-border': showStatusBtn }" @click="jumpToDetails"> </view>
<good-info :data="data" :actual_price="data.actual_price" /> </view>
<view v-if="data.type && data.type !== 1" class="fs-24 app-fc-mark order-type"> <view
随车订单 class="order-content"
</view> :class="{ 'split-border': showStatusBtn }"
</view> @click="jumpToDetails"
<view v-if="showStatusBtn" class="order-btns"> >
<!-- 待支付 --> <good-info :data="data.items" :actual_price="data.actual_price" />
<template v-if="[SHOP_ORDER_UNPAY].includes(data.status)"> <view
<view class="flex-center fs-24 app-fc-main cancel-order-btn" @click.stop="$emit('cancelOrder', data)"> v-if="data.type && data.type !== 1"
取消订单 class="fs-24 app-fc-mark order-type"
</view> >
<view class="order-btns-right"> 随车订单
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)"> </view>
</view>
<view v-if="showStatusBtn" class="order-btns">
<!-- 待支付 -->
<template v-if="[SHOP_ORDER_UNPAY].includes(data.status)">
<view
class="flex-center fs-24 app-fc-main cancel-order-btn"
@click.stop="$emit('cancelOrder', data)"
>
取消订单
</view>
<view class="order-btns-right">
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
联系客服 联系客服
</view> --> </view> -->
<view class="flex-center fs-24 app-fc-white status-btn confirm" @click.stop="$emit('pay', data)"> <view
立即支付 class="flex-center fs-24 app-fc-white status-btn confirm"
</view> @click.stop="$emit('pay', data)"
</view> >
</template> 立即支付
</view>
</view>
</template>
<!-- 待预约 --> <!-- 待预约 -->
<template v-if="[SHOP_ORDER_UNSLIVER].includes(data.status)"> <template v-if="[SHOP_ORDER_UNSLIVER].includes(data.status)">
<view class="order-btns-right"> <view class="order-btns-right">
<view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('refund', data)"> <!-- <view
申请退款 class="flex-center fs-24 app-fc-main status-btn"
</view> @click.stop="remindRefund(data.order_id)"
<view class="flex-center fs-24 app-fc-white status-btn confirm" >
@click.stop="jumpToReservation"> 申请退款
立即预约 </view> -->
</view> <view
</view> class="flex-center fs-24 app-fc-white status-btn confirm"
</template> @click.stop="jumpToReservation(data.order_id)"
>
立即预约
</view>
</view>
</template>
<!-- 待收货 --> <!-- 待收货 -->
<template v-if="[SHOP_ORDER_UNRECEIVE].includes(data.status)"> <template v-if="[SHOP_ORDER_UNRECEIVE].includes(data.status)">
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)"> <!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
联系客服 联系客服
</view> --> </view> -->
<view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('afterSale', data)"> <!-- <view
申请售后 class="flex-center fs-24 app-fc-main status-btn"
</view> @click.stop="$emit('afterSale', data)"
<!-- 随车订单不显示物流 --> >
<!-- <view v-if="data.pay_type !== pay_type_BYCAR" class="flex-center fs-24 app-fc-main status-btn" 申请售后
</view> -->
<!-- 随车订单不显示物流 -->
<!-- <view v-if="data.pay_type !== pay_type_BYCAR" class="flex-center fs-24 app-fc-main status-btn"
@click.stop="$emit('checkSliver', data)"> @click.stop="$emit('checkSliver', data)">
查看物流 查看物流
</view> --> </view> -->
<view class="flex-center fs-24 app-fc-white status-btn confirm" <!-- <view
@click.stop="$emit('confirmSliver', data)"> class="flex-center fs-24 app-fc-white status-btn confirm"
确认收货 @click.stop="$emit('confirmSliver', data)"
</view> >
</template> 确认收货
</view> -->
</template>
<!-- 已签收未评价 --> <!-- 已签收未评价 -->
<template v-if="[SHOP_ORDER_UNREMARK].includes(data.status)"> <template v-if="[SHOP_ORDER_UNREMARK].includes(data.status)">
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)"> <!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
联系客服 联系客服
</view> --> </view> -->
<view class="flex-center fs-24 app-fc-white status-btn confirm" @click.stop="$emit('remark', data)"> <view
立即评价 class="flex-center fs-24 app-fc-white status-btn confirm"
</view> @click.stop="$emit('remark', data)"
</template> >
立即评价
</view>
</template>
<!-- 已完成 --> <!-- 已完成 -->
<template v-if="[SHOP_ORDER_DONE].includes(data.status)"> <template v-if="[SHOP_ORDER_DONE].includes(data.status)">
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)"> <!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
联系客服 联系客服
</view> --> </view> -->
<view class="flex-center fs-24 app-fc-white status-btn confirm " <view
style="visibility: hidden;" class="flex-center fs-24 app-fc-white status-btn confirm"
> style="visibility: hidden"
>
</view> </view>
</template> </template>
<template v-if="[SHOP_ORDER_DONE].includes(data.status)"> <template v-if="[SHOP_ORDER_DONE].includes(data.status)">
<!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)"> <!-- <view class="flex-center fs-24 app-fc-main status-btn" @click.stop="$emit('concactService', data)">
联系客服 联系客服
</view> --> </view> -->
<view class="flex-center fs-24 app-fc-white status-btn confirm" <view
@click.stop="$emit('remarkDetails', data)"> class="flex-center fs-24 app-fc-white status-btn confirm"
查看评价 @click.stop="$emit('remarkDetails', data)"
</view> >
</template> 查看评价
</view> </view>
</view> </template>
</view>
</view>
</template> </template>
<script> <script>
import PopUpModal from "@/components/PopUpModal.vue"; import PopUpModal from "@/components/PopUpModal.vue";
import GoodInfo from "./GoodInfo.vue"; import GoodInfo from "./GoodInfo.vue";
import { import { walletTransaction, cancelPetOrderMall } from "../../../../api/login";
SHOP_ORDER_STATUS,
SHOP_ORDER_UNPAY,
SHOP_ORDER_UNSLIVER,
SHOP_ORDER_UNRECEIVE,
SHOP_ORDER_DONE,
SHOP_ORDER_CANCEL,
SHOP_ORDER_AFTERSALE,
SHOP_ORDER_AFTERSALE_DONE,
SHOP_ORDER_AFTERSALE_REJECT,
SHOP_ORDER_AFTERSALE_STATUS,
SHOP_ORDER_UNREMARK,
pay_type_ADDRESS,
pay_type_BYCAR,
pay_type_BYPET
} from "@/constants/app.business";
export default { import {
props: { SHOP_ORDER_STATUS,
data: { SHOP_ORDER_UNPAY,
type: Object, SHOP_ORDER_UNSLIVER,
default: () => {}, SHOP_ORDER_UNRECEIVE,
}, SHOP_ORDER_DONE,
}, SHOP_ORDER_CANCEL,
data() { SHOP_ORDER_AFTERSALE,
return { SHOP_ORDER_AFTERSALE_DONE,
pay_type_ADDRESS, SHOP_ORDER_AFTERSALE_REJECT,
pay_type_BYCAR, SHOP_ORDER_AFTERSALE_STATUS,
pay_type_BYPET, SHOP_ORDER_UNREMARK,
SHOP_ORDER_STATUS, pay_type_ADDRESS,
SHOP_ORDER_UNPAY, pay_type_BYCAR,
SHOP_ORDER_UNSLIVER, pay_type_BYPET,
SHOP_ORDER_UNRECEIVE, } from "@/constants/app.business";
SHOP_ORDER_DONE,
SHOP_ORDER_CANCEL, export default {
SHOP_ORDER_AFTERSALE, props: {
SHOP_ORDER_AFTERSALE_DONE, data: {
SHOP_ORDER_AFTERSALE_REJECT, type: Object,
SHOP_ORDER_UNREMARK, default: () => {},
showCancelModal: false, },
countDownTime: 0, },
countDownTimer: null, data() {
}; return {
}, pay_type_ADDRESS,
components: { pay_type_BYCAR,
GoodInfo, pay_type_BYPET,
PopUpModal SHOP_ORDER_STATUS,
}, SHOP_ORDER_UNPAY,
options: { SHOP_ORDER_UNSLIVER,
styleIsolation: "shared", SHOP_ORDER_UNRECEIVE,
}, SHOP_ORDER_DONE,
computed: { SHOP_ORDER_CANCEL,
// 抖音退款组件需要的订单ID SHOP_ORDER_AFTERSALE,
orderId() { SHOP_ORDER_AFTERSALE_DONE,
return this.data.order_id || this.data.order_no || ""; SHOP_ORDER_AFTERSALE_REJECT,
}, SHOP_ORDER_UNREMARK,
// 抖音退款组件需要的订单状态1=待核销/待发货2=已核销/已发货) showCancelModal: false,
orderStatusForSDK() { countDownTime: 0,
// 根据你的业务状态映射到抖音SDK需要的状态 countDownTimer: null,
// SHOP_ORDER_UNSLIVER = 待发货 -> 映射为 1 };
if (this.data.status === SHOP_ORDER_UNSLIVER) { },
return 1; components: {
} GoodInfo,
// SHOP_ORDER_UNRECEIVE = 待收货 -> 映射为 2 PopUpModal,
if (this.data.status === SHOP_ORDER_UNRECEIVE) { },
return 2; options: {
} styleIsolation: "shared",
return 1; },
}, computed: {
// 订单总金额(单位:分) // 抖音退款组件需要的订单ID
orderTotalAmount() { orderId() {
const price = this.data.actual_price || 0; return this.data.order_id || this.data.order_no || "";
// 转换为分 },
return Math.round(parseFloat(price) * 100); // 抖音退款组件需要的订单状态1=待核销/待发货2=已核销/已发货)
}, orderStatusForSDK() {
// UniApp 需要传入的退款参数对象 // 根据你的业务状态映射到抖音SDK需要的状态
refundParams() { // SHOP_ORDER_UNSLIVER = 待发货 -> 映射为 1
return { if (this.data.status === SHOP_ORDER_UNSLIVER) {
reasonCode: [410], return 1;
note: '用户申请退款', }
applySource: 101, // SHOP_ORDER_UNRECEIVE = 待收货 -> 映射为 2
afterSaleType: 3, if (this.data.status === SHOP_ORDER_UNRECEIVE) {
needRefundPackFee: true return 2;
} }
}, return 1;
orderStatus() { },
return SHOP_ORDER_STATUS[this.data.status] || ""; // 订单总金额(单位:分)
}, orderTotalAmount() {
refundOrderStatus() { const price = this.data.actual_price || 0;
return SHOP_ORDER_AFTERSALE_STATUS[this.data.tui_status] || ""; // 转换为分
}, return Math.round(parseFloat(price) * 100);
showStatusBtn() { },
return ( // UniApp 需要传入的退款参数对象
([ refundParams() {
SHOP_ORDER_UNPAY, return {
SHOP_ORDER_UNSLIVER, reasonCode: [410],
SHOP_ORDER_UNRECEIVE, note: "用户申请退款",
SHOP_ORDER_DONE, applySource: 101,
SHOP_ORDER_UNREMARK, afterSaleType: 3,
].includes(this.data.status) && needRefundPackFee: true,
!this.data.tui_status) || [SHOP_ORDER_AFTERSALE_REJECT].includes(this.data.tui_status) };
); },
}, orderStatus() {
}, return SHOP_ORDER_STATUS[this.data.status] || "";
watch: { },
showCancelModal(val) { refundOrderStatus() {
this.$emit("disableScroll", val); return SHOP_ORDER_AFTERSALE_STATUS[this.data.tui_status] || "";
}, },
'data.daojishi'(newVal) { showStatusBtn() {
if (this.data.status === this.SHOP_ORDER_UNPAY && newVal) { return (
this.countDownTime = newVal; ([
this.startCountDown(); SHOP_ORDER_UNPAY,
} SHOP_ORDER_UNSLIVER,
}, SHOP_ORDER_UNRECEIVE,
'data.status'(newVal) { SHOP_ORDER_DONE,
if (newVal === this.SHOP_ORDER_UNPAY && this.data.daojishi) { SHOP_ORDER_UNREMARK,
this.countDownTime = this.data.daojishi; ].includes(this.data.status) &&
this.startCountDown(); !this.data.tui_status) ||
} else { [SHOP_ORDER_AFTERSALE_REJECT].includes(this.data.tui_status)
this.stopCountDown(); );
} },
}, },
}, watch: {
mounted() { showCancelModal(val) {
// console.log(this.data,'--=') this.$emit("disableScroll", val);
if (this.data.status === SHOP_ORDER_UNPAY && this.data.daojishi) { },
this.countDownTime = this.data.daojishi; "data.daojishi"(newVal) {
this.startCountDown(); if (this.data.status === this.SHOP_ORDER_UNPAY && newVal) {
} this.countDownTime = newVal;
}, this.startCountDown();
beforeDestroy() { }
this.stopCountDown(); },
}, "data.status"(newVal) {
methods: { if (newVal === this.SHOP_ORDER_UNPAY && this.data.daojishi) {
// 退款回调(组件触发) this.countDownTime = this.data.daojishi;
handleRefund(event) { this.startCountDown();
const { status, result, outOrderNo } = event.detail } else {
console.log('退款回调:', { status, result, outOrderNo }) this.stopCountDown();
}
if (status === 'success') { },
uni.showToast({ },
title: '退款申请已提交', mounted() {
icon: 'success' // console.log(this.data,'--=')
}) if (this.data.status === SHOP_ORDER_UNPAY && this.data.daojishi) {
this.$emit('refund', this.data) this.countDownTime = this.data.daojishi;
} else { this.startCountDown();
uni.showToast({ }
title: result?.errMsg || '退款失败,请稍后重试', },
icon: 'none' beforeDestroy() {
}) this.stopCountDown();
} },
}, methods: {
remindRefund(id) {
// 错误处理 const data = {
handleError(event) { id: Number(id),
console.error('退款组件报错:', event.detail) };
uni.showToast({ uni.showLoading({
title: '组件加载失败,请稍后重试', icon: "none",
icon: 'none' title: "处理中",
}) mask: true,
}, });
cancelPetOrderMall(data)
startCountDown() { .then((res) => {
this.stopCountDown(); uni.hideLoading();
if (this.countDownTime > 0) { const plugin = tt.requirePlugin("lifeServicePlugin");
this.countDownTimer = setInterval(() => { let res1 = {
if (this.countDownTime > 0) { code: 0,
this.countDownTime--; result: "success",
} else { msg: "success",
this.stopCountDown(); data: {
// 倒计时结束,可以触发刷新 outOrderNo: "DYG177556196373493862814838179",
this.$emit('countdownEnd', this.data); refundInfo: {
} reason: ["计划有变,暂时不需要了"]
}, 1000); },
} // refundInfo: {
}, // reason:["计划有变,暂时不需要了"],
stopCountDown() { // reasonCode:[401],
if (this.countDownTimer) { // },
clearInterval(this.countDownTimer); itemOrderList:[
this.countDownTimer = null; {
} "itemOrderId": "1093391915337944382",
}, "refundAmount": 9000
formatCountdown(seconds) { }],
const hour = Math.floor(seconds / 3600); // goodsList: [
const minutes = Math.floor((seconds - hour * 3600) / 60); // { goodsId: "7625832097692813354", goodsType: 1, quantity: 1 },
return `${hour}小时${minutes}分钟`; // ],
}, },
orderCancel() { };
this.showCancelModal = false; // console.log('applyRefund options', JSON.stringify(options))
}, console.log(res1.data,'??')
cancel() { plugin.applyRefund({
this.showCancelModal = false; itemOrderList:res1.data.itemOrderList,
}, // goodsList: res1.data.goodsList,
jumpToDetails() { outOrderNo: res1.data.outOrderNo,
this.$emit('jumpToDetails', this.data) refundInfo: res1.data.refundInfo,
}, success: (res) => {
jumpToReservation() { uni.showToast({
uni.reLaunch({ title: "退款申请已提交",
url: '/pages/client/index/index?activePageId=reservationPage' icon: "success",
}); });
}, this.$emit("refund", this.data);
}, },
}; fail: (err) => {
console.log("退款失败:", err);
// 处理session过期错误
if (err && err.errMsg && err.errMsg.includes("session已过期")) {
uni.showModal({
title: "提示",
content: "会话已过期,请重新登录后再试",
showCancel: false,
confirmText: "确定",
success: () => {
// 清除当前用户信息并跳转到登录页
this.$store.dispatch("user/deleteToken");
this.$store.dispatch("user/clearUserInfo");
uni.clearStorageSync();
uni.reLaunch({
url: "/pages/client/auth/index",
});
},
});
} else {
uni.showToast({
title: err?.errMsg || err?.msg || "退款失败,请重试",
icon: "none",
});
}
},
});
})
.catch((err) => {
uni.hideLoading();
console.error("获取退款信息失败:", err);
uni.showToast({
title: err?.msg || "获取退款信息失败",
icon: "none",
});
});
},
// 退款回调(组件触发)
handleRefund(event) {
const { status, result, outOrderNo } = event.detail;
console.log("退款回调:", { status, result, outOrderNo });
if (status === "success") {
uni.showToast({
title: "退款申请已提交",
icon: "success",
});
this.$emit("refund", this.data);
} else {
uni.showToast({
title: result?.errMsg || "退款失败,请稍后重试",
icon: "none",
});
}
},
// 错误处理
handleError(event) {
console.error("退款组件报错:", event.detail);
uni.showToast({
title: "组件加载失败,请稍后重试",
icon: "none",
});
},
startCountDown() {
this.stopCountDown();
if (this.countDownTime > 0) {
this.countDownTimer = setInterval(() => {
if (this.countDownTime > 0) {
this.countDownTime--;
} else {
this.stopCountDown();
// 倒计时结束,可以触发刷新
this.$emit("countdownEnd", this.data);
}
}, 1000);
}
},
stopCountDown() {
if (this.countDownTimer) {
clearInterval(this.countDownTimer);
this.countDownTimer = null;
}
},
formatCountdown(seconds) {
const hour = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds - hour * 3600) / 60);
return `${hour}小时${minutes}分钟`;
},
orderCancel() {
this.showCancelModal = false;
},
cancel() {
this.showCancelModal = false;
},
jumpToDetails() {
this.$emit("jumpToDetails", this.data);
},
jumpToReservation(orderId) {
uni.reLaunch({
url: `/pages/client/index/index?activePageId=reservationPage&orderId=${orderId}`,
});
},
},
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.order-item { .order-item {
width: calc(100vw - 40rpx); width: calc(100vw - 40rpx);
background: #fff; background: #fff;
margin: 20rpx; margin: 20rpx;
border-radius: 30rpx; border-radius: 30rpx;
box-sizing: border-box; box-sizing: border-box;
padding: 20rpx; padding: 20rpx;
margin-bottom: 0; margin-bottom: 0;
.order-title { .order-title {
padding-bottom: 20rpx; padding-bottom: 20rpx;
border-bottom: 1rpx solid #ececec; border-bottom: 1rpx solid #ececec;
.order-btn { .order-btn {
width: 104rpx; width: 104rpx;
height: 48rpx; height: 48rpx;
border-radius: 48rpx; border-radius: 48rpx;
&.confirm { &.confirm {
background: #fef6ff; background: #fef6ff;
} }
&.cancel { &.cancel {
background: #f7f7f7; background: #f7f7f7;
color: #afa5ae; color: #afa5ae;
} }
} }
.order-status-banner { .order-status-banner {
display: flex; display: flex;
align-items: center; align-items: center;
border-radius: 48rpx; border-radius: 48rpx;
overflow: hidden; overflow: hidden;
.status-banner-left { .status-banner-left {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: 100%; height: 100%;
background: #FF19A0; background: #ff19a0;
padding: 8rpx; padding: 8rpx;
flex-shrink: 0; flex-shrink: 0;
border-radius: 10px 0px 10px 10px; border-radius: 10px 0px 10px 10px;
.status-text { .status-text {
font-size: 20rpx; font-size: 20rpx;
color: #FFFFFF; color: #ffffff;
} }
} }
.status-banner-right { .status-banner-right {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: 100%; height: 100%;
background: #FFF0F8; background: #fff0f8;
padding: 8rpx; padding: 8rpx;
flex-shrink: 0; flex-shrink: 0;
.countdown-text {
font-size: 20rpx;
color: #ff19a0;
}
}
}
}
.countdown-text { .order-btns {
font-size: 20rpx; padding-top: 20rpx;
color: #FF19A0; display: flex;
} justify-content: flex-end;
} align-items: center;
}
}
.cancel-order-btn {
font-size: 24rpx;
color: #9b939a;
padding: 16rpx 0;
}
.order-btns { .order-btns-right {
padding-top: 20rpx; display: flex;
display: flex; align-items: center;
justify-content: flex-end; }
align-items: center;
.cancel-order-btn { .status-btn {
font-size: 24rpx; width: 70px;
color: #9B939A; height: 34px;
padding: 16rpx 0; // padding: 16rpx 20rpx;
} border-radius: 64rpx;
border: 1px solid #ff19a0;
margin-left: 20rpx;
.order-btns-right { &.confirm {
display: flex; color: $app_color_main;
align-items: center; border: 1px solid $app_color_main;
} }
}
}
.status-btn { ::v-deep {
width: 70px; .good-info-multi {
height: 34px; padding-top: 20rpx;
// padding: 16rpx 20rpx; }
border-radius: 64rpx; }
border: 1px solid #FF19A0;; }
margin-left: 20rpx;
&.confirm {
color: $app_color_main;
border: 1px solid $app_color_main;
}
}
}
::v-deep {
.good-info-multi {
padding-top: 20rpx;
}
}
}
</style> </style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@
class="flex-column-start news-item"> class="flex-column-start news-item">
<order-item :data="data" @disableScroll="disableScrollAction" @cancelOrder="cancelOrderAction" <order-item :data="data" @disableScroll="disableScrollAction" @cancelOrder="cancelOrderAction"
@refund="handleRefundFromItem" @concactService="jumpToWeChat" @confirmSliver="confirmSliver" @remindSilver="remindSliver" @pay="pay" @refund="handleRefundFromItem" @concactService="jumpToWeChat" @confirmSliver="confirmSliver" @remindSilver="remindSliver" @pay="pay"
@afterSale="afterSale" @checkSliver="checkSliver" @remark="remark" @remarkDetails="remarkDetails" @checkSliver="checkSliver" @remark="remark" @remarkDetails="remarkDetails"
@jumpToDetails="jumpToDetails" /> @jumpToDetails="jumpToDetails" />
</view> </view>
<uni-load-more v-if="isLoading || (!isLoading && total && total === list.length)" <uni-load-more v-if="isLoading || (!isLoading && total && total === list.length)"
@ -101,7 +101,7 @@ import ContactModal from "@/components/ContactModal.vue";
import SliverInfo from "./components/SliverInfo.vue"; import SliverInfo from "./components/SliverInfo.vue";
import WeChatCopyModal from "@/components/WeChatCopyModal.vue"; import WeChatCopyModal from "@/components/WeChatCopyModal.vue";
import DraggableContact from "@/components/DraggableContact.vue"; import DraggableContact from "@/components/DraggableContact.vue";
import { walletTransaction, cancelPetOrderRefund, cancelPetOrderMall } from "../../../api/login"; import { walletTransaction, cancelPetOrderRefund, cancelPetOrderMall,cancelUnpaid } from "../../../api/login";
import { import {
@ -250,14 +250,14 @@ export default {
mask: true, mask: true,
}); });
const data = { const data = {
id: this.orderInfo.order_id, id: Number(this.orderInfo.order_id),
// business_type:1 // business_type:1
} }
// 判断是取消订单还是退款 // 判断是取消订单还是退款
const apiPromise = this.cancelModalContent.includes('退款') const apiPromise = this.cancelModalContent.includes('退款')
? cancelPetOrderRefund(data) ? cancelUnpaid(data)
: cancelPetOrderMall(data); : cancelUnpaid(data);
apiPromise.then((res) => { apiPromise.then((res) => {
uni.hideLoading(); uni.hideLoading();
@ -303,23 +303,26 @@ export default {
}, },
// 支付 // 支付
pay(data) { pay(data) {
uni.showLoading({ uni.showLoading({
icon: "none", icon: "none",
title: "支付中", title: "支付中",
mask: true, mask: true,
}); });
console.log(data,'-?')
const plugin = tt.requirePlugin('tta5a3d31e3aecfb9b11'); const plugin = tt.requirePlugin('lifeServicePlugin');
// 先隐藏loading让插件显示自己的弹窗
uni.hideLoading();
plugin.continueToPay({ plugin.continueToPay({
orderId: "orderId", // 内部订单号 orderId:data.douyin_order_id, // 内部订单号
outOrderNo: "outOrderNo", // 外部订单号 2个订单号必填一个 // outOrderNo: "outOrderNo", // 外部订单号 2个订单号必填一个
success: (res) => { success: (res) => {
const { orderId, outOrderNo } = res; const { orderId, outOrderNo } = res;
console.log("success res", res); console.log("success res", res);
console.log("orderId", orderId, "outOrderNo", outOrderNo); console.log("orderId", orderId, "outOrderNo", outOrderNo);
this.reloadData();
}, },
fail: (res) => { fail: (res) => {
uni.hideLoading();
const { orderId, outOrderNo, errNo, errMsg, errLogId } = res; const { orderId, outOrderNo, errNo, errMsg, errLogId } = res;
if (errLogId) { if (errLogId) {
console.log("查询订单信息失败", errNo, errMsg, errLogId); console.log("查询订单信息失败", errNo, errMsg, errLogId);
@ -327,6 +330,11 @@ plugin.continueToPay({
if (orderId || outOrderNo) { if (orderId || outOrderNo) {
console.log("支付失败", errNo, errMsg, orderId, outOrderNo); console.log("支付失败", errNo, errMsg, orderId, outOrderNo);
} }
// 隐藏抖音的报错信息,显示自定义中文提示
uni.showToast({
title: "支付失败",
icon: "none",
});
}, },
}); });
// payOrder({ // payOrder({

View File

@ -1,6 +1,6 @@
<template> <template>
<view class="goods-item" > <view class="goods-item" :class="{ 'goods-item-home': isHome }">
<image class="goods-img" :src="getProductImage(data)" mode="aspectFill" /> <image @click.stop="handleBuyNow" class="goods-img" :src="getProductImage(data)" mode="aspectFill" />
<view class=" fs-24 app-fc-main goods-name"> <view class=" fs-24 app-fc-main goods-name">
{{ data.product.product_name || "" }} {{ data.product.product_name || "" }}
</view> </view>
@ -34,7 +34,7 @@
} from '@/utils/common'; } from '@/utils/common';
export default { export default {
props: { props: {
index: { index: {
type: Number, type: Number,
default: 0, default: 0,
@ -43,6 +43,10 @@
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
isHome: {
type: Boolean,
default: false,
},
}, },
data() { data() {
return { return {
@ -70,7 +74,6 @@
if (data.product_pic) { if (data.product_pic) {
return data.product_pic; return data.product_pic;
} }
// Try to parse image_list from attr_key_value_map // Try to parse image_list from attr_key_value_map
if (data.product?.attr_key_value_map?.image_list) { if (data.product?.attr_key_value_map?.image_list) {
try { try {
@ -94,7 +97,7 @@
.goods-item { .goods-item {
background: #fff; background: #fff;
border-radius: 16rpx; border-radius: 16rpx;
padding: 20rpx; padding: 2rpx;
box-sizing: border-box; box-sizing: border-box;
margin-bottom: 22rpx; margin-bottom: 22rpx;
position: relative; position: relative;
@ -102,15 +105,24 @@
min-width: 0; min-width: 0;
overflow: hidden; overflow: hidden;
&.goods-item-home {
padding: 20rpx;
}
.goods-img { .goods-img {
display: block; display: block;
// width: 260rpx;
height: 260rpx;
width: 100%; width: 100%;
max-width: 100%; // max-width: 100%;
height: 320rpx;
border-radius: 16rpx; border-radius: 16rpx;
background: #f5f5f5; background: #f5f5f5;
} }
&.goods-item-home .goods-img {
height: 145px;
}
.goods-name { .goods-name {
margin: 20rpx 0; margin: 20rpx 0;
// overflow: hidden; // overflow: hidden;

View File

@ -170,6 +170,8 @@ export default {
total: 0, total: 0,
page: 1, page: 1,
size: 10, size: 10,
nextCursor: '',
hasMore: true,
showModal: false, showModal: false,
addGoodInfo: null, addGoodInfo: null,
showContactModal: false, showContactModal: false,
@ -263,6 +265,9 @@ export default {
initData() { initData() {
this.getCategoryList(); this.getCategoryList();
this.page = 1; this.page = 1;
this.nextCursor = '';
this.hasMore = true;
this.goodsList = [];
this.getGoodsList(); this.getGoodsList();
this.getCartListData(); this.getCartListData();
}, },
@ -281,13 +286,25 @@ export default {
// 商品列表 // 商品列表
getGoodsList() { getGoodsList() {
if (this.isLoading) return; if (this.isLoading) return;
if (!this.hasMore && this.page > 1) return;
this.isLoading = true; this.isLoading = true;
getGoodsListData({ getGoodsListData({
type: this.selectCategoryId ? this.selectCategoryId : 0, type: this.selectCategoryId ? this.selectCategoryId : 0,
cursor: this.nextCursor,
}) })
.then((res) => { .then((res) => {
const list = res?.data.data.products || []; const list = res?.data.data.products || [];
this.goodsList = list; const hasMore = res?.data.data.has_more;
const nextCursor = res?.data.data.next_cursor || '';
if (this.page === 1) {
this.goodsList = list;
} else {
this.goodsList = [...this.goodsList, ...list];
}
this.hasMore = hasMore;
this.nextCursor = nextCursor;
this.total = res?.count || 0; this.total = res?.count || 0;
}) })
.finally(() => { .finally(() => {
@ -300,10 +317,12 @@ export default {
this.page = 1; this.page = 1;
this.size = 10; this.size = 10;
this.total = 0; this.total = 0;
this.nextCursor = '';
this.hasMore = true;
this.getGoodsList(); this.getGoodsList();
}, },
onLoadMore() { onLoadMore() {
if (!this.isLoading && this.total > this.goodsList.length) { if (!this.isLoading && this.hasMore) {
this.page++; this.page++;
this.getGoodsList(); this.getGoodsList();
} }
@ -378,20 +397,20 @@ export default {
} }
uni.navigateTo({ uni.navigateTo({
url: `/pages/client/order/create`, url: `/pages/client/order/create?product_id=${goodsData.product.product_id}`,
success: (res) => { // success: (res) => {
// 通过eventChannel向被打开页面传送数据 // // 通过eventChannel向被打开页面传送数据
res.eventChannel.emit("createOrder", { // res.eventChannel.emit("createOrder", {
goodList: [{ // goodList: [{
goods_id:goodsData.product.out_id, // goods_id:goodsData.product.out_id,
product_pic: firstImageUrl, // product_pic: firstImageUrl,
number:1, // number:1,
goods_name: goodsData.product.product_name, // goods_name: goodsData.product.product_name,
price_name: goodsData?.product.product_name, // price_name: goodsData?.product.product_name,
goods_price: goodsData.sku.actual_amount / 100 // goods_price: goodsData.sku.actual_amount / 100
}, ], // }, ],
}); // });
}, // },
}); });
}, },
// 购物车列表 // 购物车列表
@ -419,31 +438,31 @@ export default {
// 立即购买 // 立即购买
handleBuyNow(good) { handleBuyNow(good) {
uni.navigateTo({ uni.navigateTo({
url: `/pages/client/order/create`, url: `/pages/client/order/create?product_id=${good.product.product_id}`,
success: (res) => { // success: (res) => {
// 通过 eventChannel 向被打开页面传送数据 // // 通过 eventChannel 向被打开页面传送数据
res.eventChannel.emit("createOrder", { // res.eventChannel.emit("createOrder", {
goodList: [ // goodList: [
{ // {
goods_id: good.product_id, // goods_id: good.product_id,
item_type: good.type, // item_type: good.type,
price_id: good.prices?.[0]?.price_id, // price_id: good.prices?.[0]?.price_id,
price_desc: good.prices?.[0]?.price_desc, // price_desc: good.prices?.[0]?.price_desc,
item_name: good.product_name, // item_name: good.product_name,
item_price: good.prices?.[0]?.actual_price, // item_price: good.prices?.[0]?.actual_price,
item_pic: good.product_pic, // item_pic: good.product_pic,
number: 1, // number: 1,
product_id: good.product_id, // product_id: good.product_id,
product_name: good.product_name, // product_name: good.product_name,
product_pic: good.product_pic, // product_pic: good.product_pic,
goods_price: good.prices?.[0]?.actual_price / 100, // goods_price: good.prices?.[0]?.actual_price / 100,
product_price: good.prices?.[0]?.actual_price / 100, // product_price: good.prices?.[0]?.actual_price / 100,
original_price: good.prices?.[0]?.original_price, // original_price: good.prices?.[0]?.original_price,
yunfei: good.yunfei || 0, // yunfei: good.yunfei || 0,
}, // },
], // ],
}); // });
}, // },
}); });
}, },
}, },
@ -476,7 +495,7 @@ export default {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0 32rpx; padding: 0 32rpx;
height: 88rpx; height: 80rpx;
box-sizing: border-box; box-sizing: border-box;
margin-bottom: 20px; margin-bottom: 20px;
@ -643,7 +662,6 @@ export default {
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
align-items: flex-start; align-items: flex-start;
margin-top: 40rpx;
padding: 0 20rpx; padding: 0 20rpx;
.goods-list-item { .goods-list-item {

335
src/utils/goodsCache.js Normal file
View File

@ -0,0 +1,335 @@
/**
* 商品数据缓存工具
* 用于首页和分类页的商品列表缓存,避免重复请求相同数据
*/
import { getGoodsListData } from '@/api/shop'
// 缓存存储对象
const goodsCache = {
// 首页商品缓存
home: null,
// 分类页商品缓存key为分类ID
category: {}
}
// 缓存过期时间5分钟单位毫秒
const CACHE_EXPIRE_TIME = 5 * 60 * 1000
/**
* 计算数据的哈希值,用于判断数据是否有变化
* @param {Array} data - 商品数据列表
* @returns {string} 数据哈希值
*/
function calculateDataHash(data) {
if (!data || !Array.isArray(data) || data.length === 0) {
return 'empty'
}
// 使用商品ID、价格、更新时间等关键信息计算哈希
const keyData = data.map(item => {
const product = item.product || item
return `${product.product_id || product.id}-${product.updated_at || Date.now()}-${item.sku?.actual_amount || product.price || 0}`
}).join('|')
// 简单的字符串哈希函数
let hash = 0
for (let i = 0; i < keyData.length; i++) {
const char = keyData.charCodeAt(i)
hash = ((hash << 5) - hash) + char
hash = hash & hash // 转换为32位整数
}
return hash.toString()
}
/**
* 获取缓存的首页商品数据
* @returns {Object|null} 缓存的商品数据,包含数据、哈希值和时间戳
*/
export function getHomeGoodsCache() {
return goodsCache.home
}
/**
* 设置首页商品缓存
* @param {Array} data - 商品数据列表
*/
export function setHomeGoodsCache(data) {
goodsCache.home = {
data,
hash: calculateDataHash(data),
timestamp: Date.now()
}
}
/**
* 获取指定分类的商品缓存
* @param {Number|String} categoryId - 分类ID
* @returns {Object|null} 缓存的商品数据
*/
export function getCategoryGoodsCache(categoryId) {
return goodsCache.category[categoryId] || null
}
/**
* 设置指定分类的商品缓存
* @param {Number|String} categoryId - 分类ID
* @param {Array} data - 商品数据列表
*/
export function setCategoryGoodsCache(categoryId, data) {
goodsCache.category[categoryId] = {
data,
hash: calculateDataHash(data),
timestamp: Date.now()
}
}
/**
* 检查缓存是否有效
* @param {Object} cache - 缓存对象
* @returns {Boolean} 缓存是否有效
*/
function isCacheValid(cache) {
if (!cache) return false
const now = Date.now()
// 检查缓存是否过期
if (now - cache.timestamp > CACHE_EXPIRE_TIME) {
return false
}
return true
}
/**
* 获取首页商品数据(带缓存)
* @param {Object} params - 请求参数
* @param {Boolean} forceRefresh - 是否强制刷新
* @returns {Promise} 商品数据
*/
export function getHomeGoodsWithCache(params, forceRefresh = false) {
return new Promise((resolve, reject) => {
const cache = getHomeGoodsCache()
// 如果有有效缓存且不强制刷新,先返回缓存数据
if (!forceRefresh && isCacheValid(cache)) {
// 先从缓存返回数据
resolve({
data: { data: { products: cache.data } },
fromCache: true
})
}
// 无论是否有缓存,都请求最新数据进行对比
getGoodsListData(params).then(res => {
const newData = res?.data.data?.products || res?.data.data || []
const hasMore = res?.data.data?.has_more
const nextCursor = res?.data.data?.next_cursor || ''
const newHash = calculateDataHash(newData)
// 检查数据是否有变化
const hasChanged = !cache || cache.hash !== newHash
if (hasChanged || forceRefresh) {
// 更新缓存
setHomeGoodsCache(newData)
// 如果没有缓存或者数据有变化,返回最新数据
if (!cache || forceRefresh) {
resolve({
...res,
data: {
...res.data,
data: {
...res.data.data,
products: newData,
has_more: hasMore,
next_cursor: nextCursor
}
},
fromCache: false,
hasChanged: true
})
} else {
// 如果已有缓存但数据有变化,通知更新
resolve({
...res,
data: {
...res.data,
data: {
...res.data.data,
products: newData,
has_more: hasMore,
next_cursor: nextCursor
}
},
fromCache: false,
hasChanged: true
})
}
} else {
// 数据没有变化,更新缓存时间戳
cache.timestamp = Date.now()
resolve({
data: {
data: {
products: cache.data,
has_more: hasMore,
next_cursor: nextCursor
}
},
fromCache: true,
hasChanged: false
})
}
}).catch(err => {
// 如果请求失败但有缓存,返回缓存
if (cache) {
resolve({
data: {
data: {
products: cache.data,
has_more: false,
next_cursor: ''
}
},
fromCache: true,
error: err
})
} else {
reject(err)
}
})
})
}
/**
* 获取分类页商品数据(带缓存)
* @param {Object} params - 请求参数
* @param {Boolean} forceRefresh - 是否强制刷新
* @returns {Promise} 商品数据
*/
export function getCategoryGoodsWithCache(params, forceRefresh = false) {
const categoryId = params.type || params.category_id || ''
return new Promise((resolve, reject) => {
const cache = getCategoryGoodsCache(categoryId)
// 如果有有效缓存且不强制刷新,先返回缓存数据
if (!forceRefresh && isCacheValid(cache)) {
resolve({
data: cache.data,
count: cache.data.length,
fromCache: true
})
}
// 无论是否有缓存,都请求最新数据进行对比
getGoodsListData(params).then(res => {
const newData = res?.data.data?.products || res?.data || []
const hasMore = res?.data.data?.has_more
const nextCursor = res?.data.data?.next_cursor || ''
const newHash = calculateDataHash(newData)
// 检查数据是否有变化
const hasChanged = !cache || cache.hash !== newHash
if (hasChanged || forceRefresh) {
// 更新缓存
setCategoryGoodsCache(categoryId, newData)
if (!cache || forceRefresh) {
resolve({
...res,
data: {
...res.data,
data: {
...res.data.data,
products: newData,
has_more: hasMore,
next_cursor: nextCursor
}
},
fromCache: false,
hasChanged: true
})
} else {
resolve({
...res,
data: {
...res.data,
data: {
...res.data.data,
products: newData,
has_more: hasMore,
next_cursor: nextCursor
}
},
fromCache: false,
hasChanged: true
})
}
} else {
// 数据没有变化,更新缓存时间戳
cache.timestamp = Date.now()
resolve({
data: newData,
data: {
products: cache.data,
has_more: hasMore,
next_cursor: nextCursor
},
count: cache.data.length,
fromCache: true,
hasChanged: false
})
}
}).catch(err => {
// 如果请求失败但有缓存,返回缓存
if (cache) {
resolve({
data: cache.data,
data: {
products: cache.data,
has_more: false,
next_cursor: ''
},
count: cache.data.length,
fromCache: true,
error: err
})
} else {
reject(err)
}
})
})
}
/**
* 清除所有商品缓存
*/
export function clearGoodsCache() {
goodsCache.home = null
goodsCache.category = {}
}
/**
* 清除指定分类的商品缓存
* @param {Number|String} categoryId - 分类ID
*/
export function clearCategoryGoodsCache(categoryId) {
if (goodsCache.category[categoryId]) {
delete goodsCache.category[categoryId]
}
}
export default {
getHomeGoodsCache,
setHomeGoodsCache,
getCategoryGoodsCache,
setCategoryGoodsCache,
getHomeGoodsWithCache,
getCategoryGoodsWithCache,
clearGoodsCache,
clearCategoryGoodsCache
}