170 lines
6.9 KiB
Vue
170 lines
6.9 KiB
Vue
<script setup>
|
||
/**
|
||
* 使用弹窗(需求 §7.4)· 三态共用一个遮罩与卡片
|
||
* confirm 确认使用 → success 使用成功(回带权益信息 + 本次使用时间)→ fail 使用失败(仅一行说明)
|
||
* 点已领取的权益 → 弹窗确认 → 仅记录一次使用,权益保持可用(可多次使用,不失效)
|
||
*/
|
||
import { computed } from "vue";
|
||
import { BENEFIT_TYPE_LABEL } from "@/constant";
|
||
import { dateRangeText, dotDateTime, priceText, rateText } from "@/utils";
|
||
|
||
const props = defineProps({
|
||
visible: { type: Boolean, default: false },
|
||
benefit: { type: Object, default: null },
|
||
/** confirm 确认使用 | success 使用成功 | fail 使用失败 */
|
||
state: { type: String, default: "confirm" },
|
||
/** 本次使用时间(接口 used_at,RFC3339) */
|
||
usedAt: { type: String, default: "" },
|
||
/** 失败原因(接口 message;缺省走统一兜底文案) */
|
||
failText: { type: String, default: "" },
|
||
/** 提交中:确认按钮防重复点击 */
|
||
submitting: { type: Boolean, default: false },
|
||
});
|
||
|
||
const emit = defineEmits(["close", "confirm"]);
|
||
|
||
const b = computed(() => props.benefit || {});
|
||
/** 类型键与权益卡同源:接口给 section_key,本地数据给 type */
|
||
const typeKey = computed(() => b.value.section_key || b.value.type || "");
|
||
const typeLabel = computed(() => BENEFIT_TYPE_LABEL[typeKey.value] || "权益");
|
||
|
||
const priceLine = computed(() => {
|
||
const rate = rateText(b.value.price?.rate);
|
||
if (rate) return `门市价 ${rate}`;
|
||
const deal = priceText(b.value.price?.deal);
|
||
const original = priceText(b.value.price?.original);
|
||
if (!deal) return b.value.scope_desc || "";
|
||
return original ? `¥${deal} · 原价 ¥${original}` : `¥${deal}`;
|
||
});
|
||
|
||
const validLine = computed(() => {
|
||
const { valid_from: from, valid_until: until, valid_days: days } = b.value;
|
||
if (from && until) return dateRangeText(from, until);
|
||
if (days) return `领取后 ${days} 天内有效`;
|
||
return until ? dateRangeText("", until) : "";
|
||
});
|
||
|
||
const ruleText = computed(
|
||
() =>
|
||
b.value.description ||
|
||
b.value.rule_text ||
|
||
"该权益不绑定具体商品,可多次使用。本次操作仅记录一次使用,不会使权益失效。"
|
||
);
|
||
|
||
/** 确认弹窗四行对照:label + 值 */
|
||
const rows = computed(() => [
|
||
{ label: "优惠价格", value: priceLine.value, accent: true },
|
||
{ label: "适用规格", value: b.value.price?.spec || "—" },
|
||
{ label: "适用门店", value: b.value.merchant_name || b.value.scope_desc || "—" },
|
||
{ label: "有效期", value: validLine.value || "—" },
|
||
]);
|
||
|
||
/** 成功弹窗信息块:权益名 + 四行 */
|
||
const successRows = computed(() => [
|
||
{ label: "优惠价格", value: priceLine.value || "—" },
|
||
{ label: "适用规格", value: b.value.price?.spec || "—" },
|
||
{ label: "适用门店", value: b.value.merchant_name || b.value.scope_desc || "—" },
|
||
{ label: "本次使用", value: dotDateTime(props.usedAt) || "—" },
|
||
]);
|
||
</script>
|
||
|
||
<template>
|
||
<view
|
||
v-if="visible"
|
||
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')"
|
||
>
|
||
<view
|
||
class="w-full bg-white rounded-dialog shadow-dialog text-center"
|
||
:class="state === 'fail' ? 'pt-6 px-5 pb-[18px]' : 'pt-6 px-[22px] pb-[18px]'"
|
||
@click.stop
|
||
>
|
||
<!-- 一 · 确认使用 -->
|
||
<template v-if="state === 'confirm'">
|
||
<view class="w-[52px] h-[52px] rounded-full mx-auto flex items-center justify-center grad-brand shadow-glowbrand">
|
||
<AppIcon name="check" tone="white" :size="22" />
|
||
</view>
|
||
|
||
<view class="mt-[14px] text-title font-semibold text-ink">确认使用该权益?</view>
|
||
|
||
<view class="mt-[14px] p-3 text-left rounded-[10px] border-[0.5px] border-line2 grad-brand-soft">
|
||
<view class="text-[13.5px] font-semibold text-ink">{{ typeLabel }}</view>
|
||
<view v-for="row in rows" :key="row.label" class="flex justify-between gap-3 mt-[5px] text-[11.5px] text-ink2">
|
||
<text>{{ row.label }}</text>
|
||
<text class="text-right" :class="row.accent ? 'font-semibold text-accent' : ''">{{ row.value }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="mt-3 text-[11px] leading-[1.7] text-ink3 text-left">{{ ruleText }}</view>
|
||
|
||
<view class="flex gap-[10px] mt-[18px]">
|
||
<view
|
||
class="flex-1 h-11 rounded-[16px] flex items-center justify-center text-body bg-alt text-ink2 font-normal"
|
||
@click="emit('close')"
|
||
>
|
||
再想想
|
||
</view>
|
||
<view
|
||
class="flex-1 h-11 rounded-[16px] flex items-center justify-center text-body font-semibold grad-brand text-white shadow-glowbrand"
|
||
@click="emit('confirm')"
|
||
>
|
||
{{ submitting ? "使用中…" : "确认使用" }}
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<!-- 二 · 使用成功 -->
|
||
<template v-else-if="state === 'success'">
|
||
<view
|
||
class="w-[60px] h-[60px] rounded-full mx-auto flex items-center justify-center grad-brand shadow-[0_0_0_9px_rgba(0,181,120,0.075),0_12px_28px_rgba(0,181,120,0.3)]"
|
||
>
|
||
<AppIcon name="check" tone="white" :size="27" />
|
||
</view>
|
||
|
||
<view class="mt-4 text-[19px] font-semibold text-ink">使用成功</view>
|
||
<view class="mt-[7px] text-[12px] leading-[1.65] text-ink2">该权益仍可继续使用</view>
|
||
|
||
<view class="mt-4 p-[13px] pb-3 text-left rounded-[10px] border-[0.5px] border-line2 grad-brand-soft">
|
||
<view class="text-[13.5px] font-semibold text-ink">{{ typeLabel }}</view>
|
||
<view
|
||
v-for="row in successRows"
|
||
:key="row.label"
|
||
class="flex justify-between gap-3 mt-[7px] text-[11.5px] text-ink2"
|
||
>
|
||
<text>{{ row.label }}</text>
|
||
<text class="text-right font-semibold text-ink">{{ row.value }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="flex mt-4">
|
||
<view
|
||
class="flex-1 h-[46px] rounded-[16px] flex items-center justify-center text-body font-semibold grad-brand text-white shadow-glowbrand"
|
||
@click="emit('close')"
|
||
>
|
||
知道了
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<!-- 三 · 使用失败 -->
|
||
<template v-else>
|
||
<view class="w-11 h-11 rounded-full mx-auto flex items-center justify-center bg-[rgba(229,72,77,0.09)]">
|
||
<AppIcon name="close" tone="danger" :size="20" />
|
||
</view>
|
||
|
||
<view class="mt-3 text-[15.5px] font-semibold text-ink">使用失败</view>
|
||
<view class="mt-[7px] text-[11.5px] leading-[1.7] text-ink3">{{ failText || "网络异常,请稍后重试" }}</view>
|
||
|
||
<view class="flex mt-4">
|
||
<view
|
||
class="flex-1 h-[42px] rounded-[16px] flex items-center justify-center text-[14px] font-medium bg-alt text-ink2"
|
||
@click="emit('close')"
|
||
>
|
||
知道了
|
||
</view>
|
||
</view>
|
||
</template>
|
||
</view>
|
||
</view>
|
||
</template>
|