@@ -339,6 +338,14 @@
@click="buyNow"
>立即购买
+
@@ -374,6 +381,7 @@ import {
getECouponMaxBuyNum,
createEmptyPromotionMap,
resolveECouponCartType,
+ eCouponAfterSaleHint,
E_COUPON_CART_BUY_NOW,
E_COUPON_CART_PINTUAN,
E_COUPON_CART_POINTS,
@@ -476,6 +484,7 @@ export default {
},
},
methods: {
+ eCouponAfterSaleHint,
// 初始化video
initVideo(){
if(!this.goodsVideo ){
@@ -724,8 +733,8 @@ export default {
},
},
mounted() {
- // 用户登录才会判断是否收藏
- if (this.Cookies.getItem("userInfo")) {
+ // 用户登录才会判断是否收藏(须同时有 token,避免残留 userInfo 触发 401 跳转登录)
+ if (this.Cookies.getItem("accessToken") && this.Cookies.getItem("userInfo")) {
isCollection("GOODS", this.skuDetail.id).then((res) => {
if (res.success && res.result) {
this.isCollected = true;
@@ -1052,6 +1061,22 @@ export default {
border-top: 1px dotted $border_color;
}
+.e-coupon-after-sale-notice {
+ width: fit-content;
+ max-width: 420px;
+ flex: 0 0 auto;
+ padding: 4px 10px;
+
+ :deep(.el-alert__content) {
+ padding: 0;
+ }
+
+ :deep(.el-alert__title) {
+ font-size: 12px;
+ line-height: 1.5;
+ }
+}
+
.add-buy-car-row {
margin-top: 25px;
diff --git a/buyer/src/constants/goodsType.js b/buyer/src/constants/goodsType.js
index 73f3bfe6..70f27872 100644
--- a/buyer/src/constants/goodsType.js
+++ b/buyer/src/constants/goodsType.js
@@ -28,6 +28,14 @@ export function isECouponOrder(orderType) {
return orderType === E_COUPON_ORDER_TYPE;
}
+/** 详情页提示:卡密交付后不支持买家售后(FR-B-04) */
+export const E_COUPON_AFTER_SALE_HINT =
+ "卡密交付后不支持买家售后,请确认商品信息后再购买。";
+
+export function eCouponAfterSaleHint() {
+ return E_COUPON_AFTER_SALE_HINT;
+}
+
/** 可售库存:E_COUPON 读 SKU quantity(由卡池 syncSkuStock 同步) */
export function getECouponStock(sku) {
if (!sku) return 0;
@@ -132,23 +140,19 @@ function normalizeCardKeyFulfillLine(source) {
};
}
-/** 汇总 E_COUPON 主单行 + 满赠子单摘要的履约行 */
-export function collectCardKeyFulfillLines(orderItems = [], giftSummaries = []) {
+/** 汇总当前订单 orderItems 中的卡密履约行(不含满赠子单;赠品卡密在赠品子单详情单独查看) */
+export function collectCardKeyFulfillLines(orderItems = []) {
const lines = [];
(orderItems || []).forEach((item) => {
if (!isCardKeyFulfillApplicable(item)) return;
lines.push(normalizeCardKeyFulfillLine(item));
});
- (giftSummaries || []).forEach((gift) => {
- lines.push(normalizeCardKeyFulfillLine({ ...gift, isGift: true }));
- });
return lines;
}
-export function orderHasCardKeySection({ orderItems, giftSummaries, isECouponOrder } = {}) {
+export function orderHasCardKeySection({ orderItems, isECouponOrder } = {}) {
if (isECouponOrder) return true;
- if (Array.isArray(giftSummaries) && giftSummaries.length > 0) return true;
- if (collectCardKeyFulfillLines(orderItems, []).length > 0) return true;
+ if (collectCardKeyFulfillLines(orderItems).length > 0) return true;
return (orderItems || []).some(
(item) =>
item.goodsType === E_COUPON_GOODS_TYPE ||
@@ -167,15 +171,14 @@ export function flattenCardKeyFulfillLines(lines, withGoodsName = false) {
return rows;
}
-/** 订单是否含已交付或可展示的卡密行(含满赠 E_COUPON 子单行) */
-export function orderHasCardKeyContent(orderItems, giftSummaries) {
- const lines = collectCardKeyFulfillLines(orderItems, giftSummaries);
- return flattenCardKeyFulfillLines(lines).length > 0;
+/** 当前订单是否含已交付卡密(仅 orderItems,不含满赠子单) */
+export function orderHasCardKeyContent(orderItems) {
+ return flattenCardKeyFulfillLines(collectCardKeyFulfillLines(orderItems)).length > 0;
}
-/** 扁平化订单项卡密(仅 cardKeyDelivered 且含 cardKeys;兼容旧逻辑) */
-export function flattenOrderCardKeys(orderItems, withGoodsName = false, giftSummaries) {
- const lines = collectCardKeyFulfillLines(orderItems, giftSummaries);
+/** 扁平化当前订单 orderItems 卡密 */
+export function flattenOrderCardKeys(orderItems, withGoodsName = false) {
+ const lines = collectCardKeyFulfillLines(orderItems);
if (lines.some((line) => line.status)) {
return flattenCardKeyFulfillLines(lines, withGoodsName);
}
diff --git a/buyer/src/main.js b/buyer/src/main.js
index ebfcc214..7c284225 100644
--- a/buyer/src/main.js
+++ b/buyer/src/main.js
@@ -13,6 +13,7 @@ import storage from "@/plugins/storage";
import { fetchAndApplyTheme } from "@/utils/theme";
import { getThemeSetting } from "@/api/common.js";
import config from "@/config";
+import { openLink } from "@/utils/buyerLogin";
const { aMapSecurityJsCode, inputMaxLength } = config;
@@ -36,12 +37,7 @@ app.config.globalProperties.Cookies = storage;
app.config.globalProperties.$inputMaxLength = inputMaxLength;
app.config.globalProperties.linkTo = function (url) {
- if (!url) return;
- if (url.substr(0, 1) === "/") {
- window.open(location.origin + url, "_blank");
- } else {
- window.open(url, "_blank");
- }
+ openLink(url, router);
};
app.config.globalProperties.connectCs = function (
diff --git a/buyer/src/pages/GoodsDetail.vue b/buyer/src/pages/GoodsDetail.vue
index ecaf8060..cf281726 100644
--- a/buyer/src/pages/GoodsDetail.vue
+++ b/buyer/src/pages/GoodsDetail.vue
@@ -140,8 +140,8 @@ export default {
if (!this.goodsMsg.data.intro) {
this.goodsMsg.data.intro = ''
}
- // 判断是否收藏
- if (this.Cookies.getItem("userInfo")) {
+ // 判断是否收藏(须同时有 token,避免未登录/ token 失效时误跳登录页)
+ if (this.Cookies.getItem("accessToken") && this.Cookies.getItem("userInfo")) {
isStoreCollection("STORE", this.goodsMsg.data.storeId).then((res) => {
if (res.success && res.result) {
this.storeCollected = true;
diff --git a/buyer/src/pages/GoodsList.vue b/buyer/src/pages/GoodsList.vue
index 95445f80..8644a133 100644
--- a/buyer/src/pages/GoodsList.vue
+++ b/buyer/src/pages/GoodsList.vue
@@ -125,6 +125,7 @@ import { Message } from "@/utils/message";
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
import GoodsClassNav from "@/components/nav/GoodsClassNav";
import * as apiGoods from "@/api/goods";
+import { isBuyerLoggedIn, redirectToLogin } from "@/utils/buyerLogin";
export default {
name: "GoodsList",
beforeRouteEnter(to, from, next) {
@@ -201,12 +202,15 @@ export default {
this.getGoodsList();
},
goGoodsDetail(skuId, goodsId) {
- // 跳转商品详情
- let routeUrl = this.$router.resolve({
+ const location = {
path: "/goodsDetail",
query: { skuId, goodsId },
- });
- window.open(routeUrl.href, "_blank");
+ };
+ if (!isBuyerLoggedIn()) {
+ redirectToLogin(this.$router, location);
+ return;
+ }
+ window.open(this.$router.resolve(location).href, "_blank");
},
goShopPage(id) {
if (!id) {
diff --git a/buyer/src/pages/home/orderCenter/OrderDetail.vue b/buyer/src/pages/home/orderCenter/OrderDetail.vue
index 10f1fcb4..1ce29e45 100644
--- a/buyer/src/pages/home/orderCenter/OrderDetail.vue
+++ b/buyer/src/pages/home/orderCenter/OrderDetail.vue
@@ -132,7 +132,7 @@
未开发票
-
{{params.paymentMethod === 'ALIPAY' ? '支付宝支付' : '微信支付'}}
+
{{ payWayName }}支付
-
-
+
+
正在生成支付二维码...
+
+
{{ payError }}
+
您可点击「重新支付」重试,或返回选择其他支付方式
+
+
-
-
请使用{{params.paymentMethod === 'ALIPAY' ? '支付宝' : '微信'}}扫码付款
+
+ 请使用{{ payWayName }}扫码付款
支付成功后自动跳转,如未跳转请点击按钮手动跳转。。。
- 重新支付
+ 重新支付
支付成功
@@ -41,47 +53,79 @@ export default {
components: { vueQr, Iphone },
data () {
return {
- qrcode: '', // 二维码
- params: this.$route.query, // 参数
- interval: null, // 定时器
- num: 0 // 商品数
+ qrcode: '',
+ params: this.$route.query,
+ interval: null,
+ num: 0,
+ payLoading: false,
+ payError: '',
};
},
- methods: {
- // 获取支付二维码
- handlePay () {
- const params = this.$route.query;
- pay(params).then(res => {
- if (res.success) {
- this.qrcode = res.result;
- this.num = 0;
- this.interval = setInterval(this.callback, 5000);
- } else {
- Message.error(res.message)
- }
- });
+ computed: {
+ payWayName () {
+ return this.params.paymentMethod === 'ALIPAY' ? '支付宝' : '微信';
},
- callback () { // 支付回调接口
- this.num++;
- if (this.num >= 7) {
+ },
+ methods: {
+ formatPayError (rawMessage) {
+ const message = (rawMessage || '').trim();
+ if (!message || message === '支付业务异常,请稍后重试') {
+ return `${this.payWayName}支付暂不可用,请稍后重试或选择其他支付方式`;
+ }
+ return message;
+ },
+ clearPayPoll () {
+ if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
- let params = JSON.parse(JSON.stringify(this.$route.query));
+ },
+ handlePay () {
+ this.clearPayPoll();
+ const params = this.$route.query;
+ this.payLoading = true;
+ this.payError = '';
+ this.qrcode = '';
+ pay(params, { skipMessage: true })
+ .then((res) => {
+ this.payLoading = false;
+ if (res.success && res.result) {
+ this.qrcode = res.result;
+ this.num = 0;
+ this.interval = setInterval(this.callback, 5000);
+ return;
+ }
+ this.payError = this.formatPayError(res.message);
+ Message.error(this.payError);
+ })
+ .catch((err) => {
+ this.payLoading = false;
+ this.payError = this.formatPayError(err?.message || err?.data?.message);
+ Message.error(this.payError);
+ });
+ },
+ callback () {
+ this.num++;
+ if (this.num >= 7) {
+ this.clearPayPoll();
+ }
+ const params = JSON.parse(JSON.stringify(this.$route.query));
delete params.paymentMethod;
delete params.paymentClient;
- payCallback(params).then(res => {
+ payCallback(params).then((res) => {
if (res.result) {
- clearInterval(this.interval);
- this.interval = null;
- this.$router.push({path: '/payDone', query: {orderType: this.$route.query.orderType}});
+ this.clearPayPoll();
+ this.$router.push({ path: '/payDone', query: { orderType: this.$route.query.orderType } });
}
});
- }
+ },
},
mounted () {
this.handlePay();
- }
+ },
+ beforeUnmount () {
+ this.clearPayPoll();
+ },
};
diff --git a/buyer/src/pages/promotion/PointMall.vue b/buyer/src/pages/promotion/PointMall.vue
index 7fde0e01..8d390526 100644
--- a/buyer/src/pages/promotion/PointMall.vue
+++ b/buyer/src/pages/promotion/PointMall.vue
@@ -18,7 +18,7 @@
@click="goGoodsDetail(item.id)"
>
-
![]()
+
@@ -26,7 +26,7 @@
- {{ item.goodsSku.goodsName }}
+ {{ itemGoodsName(item) }}
已有
{{ item.commentNum || 0 }}人评价
@@ -70,6 +70,13 @@ export default {
this.getCate()
},
methods: {
+ /** 列表 API 返回 PointsGoods(thumbnail/goodsName 在顶层);详情 API 才有 goodsSku */
+ itemThumbnail (item) {
+ return item?.goodsSku?.thumbnail || item?.thumbnail || ''
+ },
+ itemGoodsName (item) {
+ return item?.goodsSku?.goodsName || item?.goodsName || ''
+ },
getList () { // 获取列表
pointGoods(this.params).then(res => {
if (res.success) {
diff --git a/buyer/src/plugins/request.js b/buyer/src/plugins/request.js
index f175af91..d1ac4296 100644
--- a/buyer/src/plugins/request.js
+++ b/buyer/src/plugins/request.js
@@ -70,7 +70,6 @@ service.interceptors.request.use(
const accessToken = Storage.getItem("accessToken");
if (accessToken && (config.needToken || config.optionalToken)) {
- config.headers["accessToken"] = accessToken;
try {
const jwtData = JSON.parse(
decodeURIComponent(
@@ -80,12 +79,23 @@ service.interceptors.request.use(
)
)
);
- if (jwtData.exp < Math.round(new Date() / 1000)) {
- refresh({ response: { config } });
+ const expired = jwtData.exp < Math.round(new Date() / 1000);
+ if (expired) {
+ if (config.needToken) {
+ return refresh({ response: { config } });
+ }
+ // 公开接口:丢弃过期 token,匿名继续请求,避免浏览商品时被引导登录
+ Storage.removeItem("accessToken");
+ Storage.removeItem("refreshToken");
+ } else {
+ config.headers["accessToken"] = accessToken;
}
} catch {
Storage.removeItem("accessToken");
Storage.removeItem("refreshToken");
+ if (config.needToken) {
+ return refresh({ response: { config } });
+ }
}
}
diff --git a/buyer/src/utils/buyerLogin.js b/buyer/src/utils/buyerLogin.js
new file mode 100644
index 00000000..9a8ffe5a
--- /dev/null
+++ b/buyer/src/utils/buyerLogin.js
@@ -0,0 +1,59 @@
+/**
+ * 买家端登录态与商品详情跳转校验
+ *
+ * @author Mike
+ * @date 2026-08-05
+ */
+import storage from "@/plugins/storage";
+
+export function isBuyerLoggedIn() {
+ const rawUserInfo = storage.getItem("userInfo");
+ if (rawUserInfo) {
+ try {
+ return !!JSON.parse(rawUserInfo).username;
+ } catch {
+ return false;
+ }
+ }
+ return !!storage.getItem("accessToken");
+}
+
+export function parseInternalLink(url) {
+ const [path, search = ""] = url.split("?");
+ const query = {};
+ if (search) {
+ new URLSearchParams(search).forEach((value, key) => {
+ query[key] = value;
+ });
+ }
+ return { path, query };
+}
+
+export function isGoodsDetailPath(path) {
+ return path === "/goodsDetail";
+}
+
+export function redirectToLogin(router, location) {
+ router.push({
+ path: "/login",
+ query: {
+ rePath: location.path,
+ query: JSON.stringify(location.query || {}),
+ },
+ });
+}
+
+/** 内部链接跳转;访问商品详情前校验登录(首页装修、列表等共用) */
+export function openLink(url, router) {
+ if (!url) return;
+ if (url.charAt(0) !== "/") {
+ window.open(url, "_blank");
+ return;
+ }
+ const location = parseInternalLink(url);
+ if (!isBuyerLoggedIn() && isGoodsDetailPath(location.path)) {
+ redirectToLogin(router, location);
+ return;
+ }
+ window.open(router.resolve(url).href, "_blank");
+}
diff --git a/manager/src/views/order/order/orderDetail.vue b/manager/src/views/order/order/orderDetail.vue
index e0fb7a85..7c9b9e73 100644
--- a/manager/src/views/order/order/orderDetail.vue
+++ b/manager/src/views/order/order/orderDetail.vue
@@ -236,7 +236,6 @@
style="width: 100%"
>
-
@@ -504,10 +503,7 @@ export default {
const rows = [];
(this.data || []).forEach((item) => {
(item.cardKeys || []).forEach((ck) => {
- rows.push({
- ...ck,
- goodsName: item.goodsName,
- });
+ rows.push({ ...ck });
});
});
return rows;
diff --git a/seller/src/constants/goodsType.js b/seller/src/constants/goodsType.js
index da5c4792..6f1d4ab4 100644
--- a/seller/src/constants/goodsType.js
+++ b/seller/src/constants/goodsType.js
@@ -92,22 +92,19 @@ function normalizeCardKeyFulfillLine(source) {
};
}
-export function collectCardKeyFulfillLines(orderItems = [], giftSummaries = []) {
+/** 汇总当前订单 orderItems 中的卡密履约行(不含满赠子单;赠品卡密在赠品子单详情单独查看) */
+export function collectCardKeyFulfillLines(orderItems = []) {
const lines = [];
(orderItems || []).forEach((item) => {
if (!isCardKeyFulfillApplicable(item)) return;
lines.push(normalizeCardKeyFulfillLine(item));
});
- (giftSummaries || []).forEach((gift) => {
- lines.push(normalizeCardKeyFulfillLine({ ...gift, isGift: true }));
- });
return lines;
}
-export function orderHasCardKeySection({ orderItems, giftSummaries, isECouponOrder } = {}) {
+export function orderHasCardKeySection({ orderItems, isECouponOrder } = {}) {
if (isECouponOrder) return true;
- if (Array.isArray(giftSummaries) && giftSummaries.length > 0) return true;
- if (collectCardKeyFulfillLines(orderItems, []).length > 0) return true;
+ if (collectCardKeyFulfillLines(orderItems).length > 0) return true;
return (orderItems || []).some(
(item) =>
item.goodsType === E_COUPON_GOODS_TYPE ||
@@ -126,13 +123,13 @@ export function flattenCardKeyFulfillLines(lines, withGoodsName = false) {
return rows;
}
-/** 订单是否含已交付卡密(含满赠子单) */
-export function orderHasCardKeyContent(orderItems, giftSummaries) {
- return flattenCardKeyFulfillLines(collectCardKeyFulfillLines(orderItems, giftSummaries)).length > 0;
+/** 当前订单是否含已交付卡密(仅 orderItems) */
+export function orderHasCardKeyContent(orderItems) {
+ return flattenCardKeyFulfillLines(collectCardKeyFulfillLines(orderItems)).length > 0;
}
-export function flattenOrderCardKeys(orderItems, withGoodsName = false, giftSummaries) {
- const lines = collectCardKeyFulfillLines(orderItems, giftSummaries);
+export function flattenOrderCardKeys(orderItems, withGoodsName = false) {
+ const lines = collectCardKeyFulfillLines(orderItems);
if (lines.some((line) => line.status)) {
return flattenCardKeyFulfillLines(lines, withGoodsName);
}
diff --git a/seller/src/views/goods/card-key/cardKeyPool.vue b/seller/src/views/goods/card-key/cardKeyPool.vue
index 7b3964af..398326fe 100644
--- a/seller/src/views/goods/card-key/cardKeyPool.vue
+++ b/seller/src/views/goods/card-key/cardKeyPool.vue
@@ -5,11 +5,29 @@
-