From 059572f6f3fba716f43bf6bfde32183493a61608 Mon Sep 17 00:00:00 2001 From: Zhang Chengdong Date: Fri, 18 Sep 2026 12:43:00 +0000 Subject: [PATCH] feat(mall): live points mall with address-aware redemption The points page loads the published catalog and the customer's live points through the shared client, redeems with a chosen saved address, refreshes the balance, and lists the customer's redemption history with its fulfilment state. The INTEGRAL_PRODUCTS and USER_STATS imports leave the page; the fixed-data adapter still serves the points domain as the rollback path. --- apps/mall/locales/marketing.ts | 36 +++++ apps/mall/pages/integral.vue | 233 +++++++++++++++++++++++++++++---- 2 files changed, 244 insertions(+), 25 deletions(-) diff --git a/apps/mall/locales/marketing.ts b/apps/mall/locales/marketing.ts index 3315b30..4290d8b 100644 --- a/apps/mall/locales/marketing.ts +++ b/apps/mall/locales/marketing.ts @@ -37,6 +37,24 @@ export default { login: "Sign in", register: "Create account", noIntegralProducts: "No rewards available right now.", + addressLabel: "Ship to", + noAddress: "No saved address.", + addAddress: "Add one in your account", + addressRequired: "Add a shipping address before redeeming.", + qty: "Qty", + redeemSuccess: "Redeemed as {no}.", + redeemConflict: "Unable to redeem: not enough points or stock.", + redeemFailed: "Unable to redeem right now.", + redemptionHistory: "My redemptions", + orderNo: "Order no.", + reward: "Reward", + pointsCost: "Points", + createdAt: "Date", + redemptionStatus: { + pending_fulfillment: "Pending", + fulfilled: "Fulfilled", + cancelled: "Cancelled", + }, }, }, zh: { @@ -77,6 +95,24 @@ export default { login: "登录", register: "注册", noIntegralProducts: "当前暂无积分商品。", + addressLabel: "收货地址", + noAddress: "暂无收货地址。", + addAddress: "前往账户添加", + addressRequired: "请先添加收货地址。", + qty: "数量", + redeemSuccess: "兑换成功,订单号 {no}。", + redeemConflict: "兑换失败:积分或库存不足。", + redeemFailed: "兑换失败,请稍后重试。", + redemptionHistory: "我的兑换", + orderNo: "订单号", + reward: "兑换商品", + pointsCost: "积分", + createdAt: "日期", + redemptionStatus: { + pending_fulfillment: "待发货", + fulfilled: "已发货", + cancelled: "已取消", + }, }, }, } as const; diff --git a/apps/mall/pages/integral.vue b/apps/mall/pages/integral.vue index dd1ba10..15115d7 100644 --- a/apps/mall/pages/integral.vue +++ b/apps/mall/pages/integral.vue @@ -1,15 +1,35 @@ @@ -86,26 +165,86 @@ onMounted(() => {

{{ t("marketing.recommendation") }}

-
-
+ +
+ +
+ +

{{ notice }}

+ +
{{ t("common.loading") }}
+
+
- +

{{ pick(item.name, locale) }}

- {{ t("marketing.points", { n: item.points }) }} - + {{ t("marketing.points", { n: item.points_price }) }}

{{ t("marketing.stock", { n: item.stock }) }}

- -

{{ t("marketing.mockNotice") }}

+ +
@@ -218,6 +357,50 @@ onMounted(() => { font-size: 17px; font-weight: 500; } +.redeem-bar { + display: flex; + align-items: center; + gap: 12px; + padding: 14px 18px; + border: 1px solid var(--mall-line); + border-top: none; + background: #fffafa; +} +.redeem-address { + display: flex; + align-items: center; + gap: 10px; + font-size: 13px; + color: var(--mall-muted); +} +.redeem-error { + margin: 12px 0 0; + padding: 10px 14px; + color: #b42318; + background: #fff1f0; + border: 1px solid #ffd6d2; +} +.redeem-notice { + margin: 12px 0 0; + padding: 10px 14px; + color: #067647; + background: #ecfdf3; + border: 1px solid #abefc6; +} +.qty-line { + display: flex; + align-items: center; + gap: 8px; + margin: 8px 0 11px; + color: var(--mall-faint); + font-size: 12px; +} +.qty-input { + width: 72px; +} +.history-heading { + margin-top: 26px; +} .integral-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));