Files
longli-mp/src/components/BenefitDialog/index.vue
T

170 lines
6.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>