diff --git a/scripts/build-icons.mjs b/scripts/build-icons.mjs
index f1fb80f..60f85d6 100644
--- a/scripts/build-icons.mjs
+++ b/scripts/build-icons.mjs
@@ -77,7 +77,7 @@ const MATRIX = {
ticket: ["ink3", "brand", "accent", "white", "ink2", "accentDeep"],
user: ["ink3", "brand", "white", "ink2"],
check: ["white", "brand"],
- close: ["ink3", "white"],
+ close: ["ink3", "white", "danger"],
doc: ["ink2", "brand"],
shield: ["ink2", "white", "brand"],
info: ["ink2", "brand", "ink3"],
diff --git a/src/components/BenefitDialog/index.vue b/src/components/BenefitDialog/index.vue
index 597d709..a6731a0 100644
--- a/src/components/BenefitDialog/index.vue
+++ b/src/components/BenefitDialog/index.vue
@@ -1,15 +1,24 @@
@@ -57,37 +74,96 @@ const rows = computed(() => [
class="fixed inset-0 z-[200] flex items-center justify-center px-[34px] bg-[rgba(3,20,15,0.52)] backdrop-blur-[7px]"
@click="emit('close')"
>
-
-
-
-
-
- 确认使用该权益?
-
-
- {{ typeLabel }}
-
- {{ row.label }}
- {{ row.value }}
+
+
+
+
+
-
- {{ ruleText }}
+ 确认使用该权益?
-
+
+ {{ typeLabel }}
+
+ {{ row.label }}
+ {{ row.value }}
+
+
+
+ {{ ruleText }}
+
+
+
+ 再想想
+
+
+ {{ submitting ? "使用中…" : "确认使用" }}
+
+
+
+
+
+
- 再想想
+
-
- 确认使用
+
+ 使用成功
+ 该权益仍可继续使用
+
+
+ {{ typeLabel }}
+
+ {{ row.label }}
+ {{ row.value }}
+
-
+
+
+
+ 知道了
+
+
+
+
+
+
+
+
+
+
+ 使用失败
+ {{ failText || "网络异常,请稍后重试" }}
+
+
+
+ 知道了
+
+
+
diff --git a/src/pages/benefit/index.vue b/src/pages/benefit/index.vue
index e06be14..d4d04d8 100644
--- a/src/pages/benefit/index.vue
+++ b/src/pages/benefit/index.vue
@@ -38,6 +38,14 @@ const store = useBenefitStore();
const seg = ref("center");
const dialogVisible = ref(false);
const activeBenefit = ref(null);
+/** 使用弹窗三态:confirm 确认使用 → success 成功(带权益信息)/ fail 失败(仅一行说明) */
+const dialogState = ref("confirm");
+/** 本次使用时间(接口 used_at,RFC3339) */
+const usedAt = ref("");
+/** 失败说明(接口业务 message;为空走统一兜底文案) */
+const failText = ref("");
+/** 提交中:确认按钮防重复点击 */
+const submitting = ref(false);
const centerList = computed(() => store.filteredCenter);
const mineList = computed(() => store.filteredMine);
@@ -77,6 +85,24 @@ const onClaim = async (benefit) => {
}, 600);
};
+/** 接口错误 → 失败说明:只有后端给了业务 message 才用它,其余走兜底文案 */
+const failMessage = (e) => {
+ const d = (e && e.data) || {};
+ return d.message || d.msg || (d.error && d.error.message) || "";
+};
+
+const resetDialog = () => {
+ dialogState.value = "confirm";
+ usedAt.value = "";
+ failText.value = "";
+ submitting.value = false;
+};
+
+const closeDialog = () => {
+ dialogVisible.value = false;
+ resetDialog();
+};
+
/** 我的权益:点击卡片 → 使用确认弹窗(能否使用以接口 can_use 为准) */
const onCardTap = (benefit) => {
if (benefit.can_use === false) {
@@ -84,6 +110,7 @@ const onCardTap = (benefit) => {
return;
}
if (!requireLogin()) return;
+ resetDialog();
activeBenefit.value = benefit;
dialogVisible.value = true;
};
@@ -91,10 +118,18 @@ const onCardTap = (benefit) => {
/** 确认使用:入参是 user_benefit_id(已领取实例 id),仅记录一次,权益保持可用 */
const onConfirmUse = async () => {
const target = activeBenefit.value;
- dialogVisible.value = false;
- if (!target) return;
- await store.use(target.user_benefit_id || target.id);
- uni.showToast({ title: "已记录本次使用", icon: "none" });
+ if (!target || submitting.value) return;
+ submitting.value = true;
+ try {
+ const res = await store.use(target.user_benefit_id || target.id);
+ usedAt.value = (res && res.used_at) || new Date().toISOString();
+ dialogState.value = "success";
+ } catch (e) {
+ failText.value = failMessage(e);
+ dialogState.value = "fail";
+ } finally {
+ submitting.value = false;
+ }
};
@@ -202,8 +237,8 @@ const onConfirmUse = async () => {
-
-
+
+
diff --git a/src/store/modules/benefit.js b/src/store/modules/benefit.js
index 11b36ec..75bba59 100644
--- a/src/store/modules/benefit.js
+++ b/src/store/modules/benefit.js
@@ -104,10 +104,11 @@ export const useBenefitStore = defineStore("benefit", {
await this.loadAll(true);
},
- /** 记录一次使用:入参 user_benefit_id(已领取实例 id) */
+ /** 记录一次使用:入参 user_benefit_id(已领取实例 id),返回 UsageRecord(used_at 供成功弹窗展示) */
async use(userBenefitId) {
- await benefitApi.useBenefit(userBenefitId);
+ const res = await benefitApi.useBenefit(userBenefitId);
await this.loadAll(true);
+ return res;
},
reset() {
diff --git a/src/styles/icons.scss b/src/styles/icons.scss
index dfc2d6b..538bc64 100644
--- a/src/styles/icons.scss
+++ b/src/styles/icons.scss
@@ -39,6 +39,7 @@
.ai-check.t-brand{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCcgZmlsbD0nbm9uZScgc3Ryb2tlPScjMDBCNTc4JyBzdHJva2Utd2lkdGg9JzEuOScgc3Ryb2tlLWxpbmVjYXA9J3JvdW5kJyBzdHJva2UtbGluZWpvaW49J3JvdW5kJz48cGF0aCBkPSJNNS41IDEyLjYgMTAgMTdsOC41LTkiLz48L3N2Zz4=")}
.ai-close.t-ink3{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCcgZmlsbD0nbm9uZScgc3Ryb2tlPScjOUNBM0FGJyBzdHJva2Utd2lkdGg9JzEuOScgc3Ryb2tlLWxpbmVjYXA9J3JvdW5kJyBzdHJva2UtbGluZWpvaW49J3JvdW5kJz48cGF0aCBkPSJNNi41IDYuNWwxMSAxMU0xNy41IDYuNWwtMTEgMTEiLz48L3N2Zz4=")}
.ai-close.t-white{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCcgZmlsbD0nbm9uZScgc3Ryb2tlPScjRkZGRkZGJyBzdHJva2Utd2lkdGg9JzEuOScgc3Ryb2tlLWxpbmVjYXA9J3JvdW5kJyBzdHJva2UtbGluZWpvaW49J3JvdW5kJz48cGF0aCBkPSJNNi41IDYuNWwxMSAxMU0xNy41IDYuNWwtMTEgMTEiLz48L3N2Zz4=")}
+.ai-close.t-danger{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCcgZmlsbD0nbm9uZScgc3Ryb2tlPScjRTU0ODREJyBzdHJva2Utd2lkdGg9JzEuOScgc3Ryb2tlLWxpbmVjYXA9J3JvdW5kJyBzdHJva2UtbGluZWpvaW49J3JvdW5kJz48cGF0aCBkPSJNNi41IDYuNWwxMSAxMU0xNy41IDYuNWwtMTEgMTEiLz48L3N2Zz4=")}
.ai-doc.t-ink2{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCcgZmlsbD0nbm9uZScgc3Ryb2tlPScjNkI3MjgwJyBzdHJva2Utd2lkdGg9JzEuOScgc3Ryb2tlLWxpbmVjYXA9J3JvdW5kJyBzdHJva2UtbGluZWpvaW49J3JvdW5kJz48cGF0aCBkPSJNNi41IDMuNWg3LjVsNC41IDQuNXYxMi41aC0xMnoiLz48cGF0aCBkPSJNMTQgMy41VjhoNC41Ii8+PHBhdGggZD0iTTkgMTNoNk05IDE2LjVoNC41Ii8+PC9zdmc+")}
.ai-doc.t-brand{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCcgZmlsbD0nbm9uZScgc3Ryb2tlPScjMDBCNTc4JyBzdHJva2Utd2lkdGg9JzEuOScgc3Ryb2tlLWxpbmVjYXA9J3JvdW5kJyBzdHJva2UtbGluZWpvaW49J3JvdW5kJz48cGF0aCBkPSJNNi41IDMuNWg3LjVsNC41IDQuNXYxMi41aC0xMnoiLz48cGF0aCBkPSJNMTQgMy41VjhoNC41Ii8+PHBhdGggZD0iTTkgMTNoNk05IDE2LjVoNC41Ii8+PC9zdmc+")}
.ai-shield.t-ink2{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCcgZmlsbD0nbm9uZScgc3Ryb2tlPScjNkI3MjgwJyBzdHJva2Utd2lkdGg9JzEuOScgc3Ryb2tlLWxpbmVjYXA9J3JvdW5kJyBzdHJva2UtbGluZWpvaW49J3JvdW5kJz48cGF0aCBkPSJNMTIgMy41IDE5LjUgNnY1LjZjMCA0LjYtMy42IDcuNi03LjUgOC45LTMuOS0xLjMtNy41LTQuMy03LjUtOC45VjZ6Ii8+PHBhdGggZD0iTTkuMiAxMi4ybDIuMiAyLjIgMy42LTMuNiIvPjwvc3ZnPg==")}
diff --git a/src/utils/index.js b/src/utils/index.js
index d70c0a4..2bd9350 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -72,6 +72,15 @@ export const uid = () =>
/** 2026-09-12 / 2026-09-12T02:00:00Z → 2026.09.12(接口时间为 RFC3339 UTC,只取日期段) */
export const dotDate = (str) => String(str || "").slice(0, 10).replace(/-/g, ".");
+/** 使用时间文案:RFC3339 → 2026.09.18 14:32(接口为 UTC,按设备时区还原) */
+export const dotDateTime = (str) => {
+ if (!str) return "";
+ const d = new Date(String(str));
+ if (Number.isNaN(d.getTime())) return dotDate(str);
+ const p = (n) => String(n).padStart(2, "0");
+ return `${d.getFullYear()}.${p(d.getMonth() + 1)}.${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
+};
+
/** 有效期区间文案:2026.09.12 - 09.18 */
export const dateRangeText = (from, to) => {
if (!from && !to) return "";
@@ -93,5 +102,6 @@ export default {
cnDate,
uid,
dotDate,
+ dotDateTime,
dateRangeText,
};