|
|
@@ -0,0 +1,667 @@
|
|
|
+<template>
|
|
|
+ <view class="page-gradient coupon-detail">
|
|
|
+ <view v-if="loading" class="card empty-card">
|
|
|
+ <text class="muted">加载中…</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <template v-else-if="coupon">
|
|
|
+ <!-- 顶部券卡 -->
|
|
|
+ <view class="detail-hero" :class="{ 'detail-hero--dimmed': isDimmed }">
|
|
|
+ <view class="detail-hero__left">
|
|
|
+ <text class="detail-hero__amount">{{ heroAmount }}</text>
|
|
|
+ <text class="detail-hero__unit">{{ heroUnit }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-hero__body">
|
|
|
+ <view class="detail-hero__name-row">
|
|
|
+ <text class="detail-hero__name">{{ displayName }}</text>
|
|
|
+ <text v-if="displayType" class="detail-hero__type">{{ displayType }}</text>
|
|
|
+ </view>
|
|
|
+ <text v-if="displayThreshold" class="detail-hero__threshold">{{ displayThreshold }}</text>
|
|
|
+ <view v-if="displayStatus" class="detail-hero__status" :class="statusClass">{{ displayStatus }}</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 优惠信息 -->
|
|
|
+ <view class="card detail-section">
|
|
|
+ <text class="section-title">优惠信息</text>
|
|
|
+ <view class="detail-list">
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-row__label">券类型</text>
|
|
|
+ <text class="detail-row__value">{{ displayType || '-' }}</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="displayDiscountType" class="detail-row">
|
|
|
+ <text class="detail-row__label">优惠方式</text>
|
|
|
+ <text class="detail-row__value">{{ displayDiscountType }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-row__label">优惠值</text>
|
|
|
+ <text class="detail-row__value">{{ displayDiscountValue }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-row__label">使用门槛</text>
|
|
|
+ <text class="detail-row__value">{{ displayThreshold || '无门槛' }}</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="displayMaxDiscount" class="detail-row">
|
|
|
+ <text class="detail-row__label">最大优惠</text>
|
|
|
+ <text class="detail-row__value">¥{{ displayMaxDiscount }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 有效期 -->
|
|
|
+ <view class="card detail-section">
|
|
|
+ <text class="section-title">有效期</text>
|
|
|
+ <view class="detail-list">
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-row__label">有效期</text>
|
|
|
+ <text class="detail-row__value">{{ displayValidPeriod || '-' }}</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="displayIssuePeriod" class="detail-row">
|
|
|
+ <text class="detail-row__label">发放周期</text>
|
|
|
+ <text class="detail-row__value">{{ displayIssuePeriod }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 领取信息(mode=mine) -->
|
|
|
+ <view v-if="mode === 'mine' && mineCoupon" class="card detail-section">
|
|
|
+ <text class="section-title">领取信息</text>
|
|
|
+ <view class="detail-list">
|
|
|
+ <view v-if="mineCoupon.getTypeName" class="detail-row">
|
|
|
+ <text class="detail-row__label">领取方式</text>
|
|
|
+ <text class="detail-row__value">{{ mineCoupon.getTypeName }}</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="mineCoupon.getDate" class="detail-row">
|
|
|
+ <text class="detail-row__label">领取时间</text>
|
|
|
+ <text class="detail-row__value">{{ formatDate(String(mineCoupon.getDate)) }}</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="mineCoupon.exchangePoints != null && mineCoupon.exchangePoints > 0" class="detail-row">
|
|
|
+ <text class="detail-row__label">兑换积分</text>
|
|
|
+ <text class="detail-row__value">{{ mineCoupon.exchangePoints }} 积分</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="mineCoupon.couponSn" class="detail-row">
|
|
|
+ <text class="detail-row__label">券编号</text>
|
|
|
+ <text class="detail-row__value">{{ mineCoupon.couponSn }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 使用范围 -->
|
|
|
+ <view class="card detail-section">
|
|
|
+ <text class="section-title">使用范围</text>
|
|
|
+ <view class="detail-list">
|
|
|
+ <view v-if="displaySceneName" class="detail-row">
|
|
|
+ <text class="detail-row__label">使用场景</text>
|
|
|
+ <text class="detail-row__value">{{ displaySceneName }}</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="displayScopeName" class="detail-row">
|
|
|
+ <text class="detail-row__label">适用范围</text>
|
|
|
+ <text class="detail-row__value">{{ displayScopeName }}</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="displayScopeLabels && displayScopeLabels.length > 0" class="detail-row">
|
|
|
+ <text class="detail-row__label">适用标签</text>
|
|
|
+ <view class="detail-row__tags">
|
|
|
+ <text v-for="(label, idx) in displayScopeLabels" :key="idx" class="detail-scope-tag">{{ label }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view v-if="displayStackRule" class="detail-row">
|
|
|
+ <text class="detail-row__label">叠加规则</text>
|
|
|
+ <text class="detail-row__value">{{ displayStackRule }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 使用记录(mode=mine 且已使用) -->
|
|
|
+ <view v-if="mode === 'mine' && mineCoupon && mineCoupon.status === 1" class="card detail-section">
|
|
|
+ <text class="section-title">使用记录</text>
|
|
|
+ <view class="detail-list">
|
|
|
+ <view v-if="mineCoupon.useDate" class="detail-row">
|
|
|
+ <text class="detail-row__label">使用时间</text>
|
|
|
+ <text class="detail-row__value">{{ formatDate(String(mineCoupon.useDate)) }}</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="mineCoupon.useAmount != null" class="detail-row">
|
|
|
+ <text class="detail-row__label">抵扣金额</text>
|
|
|
+ <text class="detail-row__value">¥{{ mineCoupon.useAmount }}</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="mineCoupon.useOrderNo" class="detail-row">
|
|
|
+ <text class="detail-row__label">订单编号</text>
|
|
|
+ <text class="detail-row__value">{{ mineCoupon.useOrderNo }}</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="mineCoupon.orderAmount != null" class="detail-row">
|
|
|
+ <text class="detail-row__label">订单金额</text>
|
|
|
+ <text class="detail-row__value">¥{{ mineCoupon.orderAmount }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 冻结/退回信息 -->
|
|
|
+ <view v-if="mode === 'mine' && mineCoupon && mineCoupon.status === 3 && mineCoupon.freezeReason" class="card detail-section">
|
|
|
+ <text class="section-title">冻结信息</text>
|
|
|
+ <view class="detail-list">
|
|
|
+ <view v-if="mineCoupon.freezeDate" class="detail-row">
|
|
|
+ <text class="detail-row__label">冻结时间</text>
|
|
|
+ <text class="detail-row__value">{{ formatDate(String(mineCoupon.freezeDate)) }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-row__label">冻结原因</text>
|
|
|
+ <text class="detail-row__value">{{ mineCoupon.freezeReason }}</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="mineCoupon.freezeOrderId" class="detail-row">
|
|
|
+ <text class="detail-row__label">冻结订单</text>
|
|
|
+ <text class="detail-row__value">{{ mineCoupon.freezeOrderId }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-if="mode === 'mine' && mineCoupon && mineCoupon.isReturned === 1" class="card detail-section">
|
|
|
+ <text class="section-title">退回信息</text>
|
|
|
+ <view class="detail-list">
|
|
|
+ <view v-if="mineCoupon.returnDate" class="detail-row">
|
|
|
+ <text class="detail-row__label">退回时间</text>
|
|
|
+ <text class="detail-row__value">{{ formatDate(String(mineCoupon.returnDate)) }}</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="mineCoupon.returnReason" class="detail-row">
|
|
|
+ <text class="detail-row__label">退回原因</text>
|
|
|
+ <text class="detail-row__value">{{ mineCoupon.returnReason }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 规则说明 -->
|
|
|
+ <view v-if="displayRules && displayRules.length > 0" class="card detail-section">
|
|
|
+ <text class="section-title">使用规则</text>
|
|
|
+ <view class="detail-rules">
|
|
|
+ <view v-for="(rule, idx) in displayRules" :key="idx" class="detail-rule-item">
|
|
|
+ <text class="detail-rule-dot">·</text>
|
|
|
+ <text class="detail-rule-text">{{ rule }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-if="displayVerifyTip" class="card detail-section">
|
|
|
+ <text class="section-title">核销提示</text>
|
|
|
+ <text class="detail-verify-tip">{{ displayVerifyTip }}</text>
|
|
|
+ </view>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <view v-else class="card empty-card">
|
|
|
+ <view class="empty-block">
|
|
|
+ <text class="muted">优惠券信息加载失败</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 底部操作栏 -->
|
|
|
+ <view v-if="showBottomBar" class="detail-bottom">
|
|
|
+ <button v-if="mode === 'mine' && canShowVerify" class="primary-btn detail-bottom__btn" @click="goVerifyCode">
|
|
|
+ 查看核销码
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ v-else-if="mode === 'available' && canReceive"
|
|
|
+ class="primary-btn detail-bottom__btn"
|
|
|
+ :loading="receiving"
|
|
|
+ @click="handleReceive"
|
|
|
+ >
|
|
|
+ 立即领取
|
|
|
+ </button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { computed, ref } from 'vue'
|
|
|
+import { onLoad } from '@dcloudio/uni-app'
|
|
|
+import {
|
|
|
+ fetchCouponDetail,
|
|
|
+ receiveMarketingCoupon,
|
|
|
+ type MarketingCouponItem,
|
|
|
+ type MemberCouponDetailVO
|
|
|
+} from '@/api/modules/marketing'
|
|
|
+import { isLoggedIn, requireLoginNavigate } from '@/utils/auth'
|
|
|
+
|
|
|
+const loading = ref(true)
|
|
|
+const receiving = ref(false)
|
|
|
+const mode = ref<'mine' | 'available'>('mine')
|
|
|
+const tenantCode = ref('')
|
|
|
+const memberCouponId = ref('')
|
|
|
+
|
|
|
+const mineCoupon = ref<MemberCouponDetailVO | null>(null)
|
|
|
+const availableCoupon = ref<MarketingCouponItem | null>(null)
|
|
|
+
|
|
|
+const coupon = computed(() => (mode.value === 'mine' ? mineCoupon.value : availableCoupon.value))
|
|
|
+
|
|
|
+onLoad((options) => {
|
|
|
+ const m = (options?.mode as 'mine' | 'available') || 'mine'
|
|
|
+ mode.value = m
|
|
|
+ tenantCode.value = options?.tenantCode || ''
|
|
|
+ memberCouponId.value = options?.memberCouponId || ''
|
|
|
+
|
|
|
+ if (m === 'mine' && memberCouponId.value) {
|
|
|
+ loadDetail()
|
|
|
+ } else if (m === 'available') {
|
|
|
+ // 通过 eventChannel 接收可领券数据
|
|
|
+ const pages = getCurrentPages()
|
|
|
+ const currentPage = pages[pages.length - 1] as any
|
|
|
+ const eventChannel = currentPage?.getOpenerEventChannel?.()
|
|
|
+ if (eventChannel) {
|
|
|
+ eventChannel.on('couponData', (data: { item: MarketingCouponItem }) => {
|
|
|
+ availableCoupon.value = data.item
|
|
|
+ loading.value = false
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+async function loadDetail() {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const res = await fetchCouponDetail(memberCouponId.value, tenantCode.value)
|
|
|
+ mineCoupon.value = res.data ?? null
|
|
|
+ } catch (e) {
|
|
|
+ uni.showToast({ title: (e as Error).message || '加载失败', icon: 'none' })
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function formatDate(value: string) {
|
|
|
+ return value.length >= 10 ? value.slice(0, 10) : value
|
|
|
+}
|
|
|
+
|
|
|
+// ---- 显示计算 ----
|
|
|
+
|
|
|
+const isDimmed = computed(() => {
|
|
|
+ if (mode.value === 'mine' && mineCoupon.value) {
|
|
|
+ const status = mineCoupon.value.status
|
|
|
+ return status === 1 || status === 2 || status === 3 || status === 4
|
|
|
+ }
|
|
|
+ return false
|
|
|
+})
|
|
|
+
|
|
|
+const displayName = computed(() => {
|
|
|
+ const c = coupon.value
|
|
|
+ return (c?.name || c?.couponName || '优惠券') as string
|
|
|
+})
|
|
|
+
|
|
|
+const displayType = computed(() => {
|
|
|
+ const c = coupon.value
|
|
|
+ return (c?.couponTypeName || c?.couponType || '') as string
|
|
|
+})
|
|
|
+
|
|
|
+const displayThreshold = computed(() => {
|
|
|
+ const c = coupon.value
|
|
|
+ if (c?.threshold) return c.threshold as string
|
|
|
+ const min = c?.minConsumeAmount ?? c?.minOrderAmount
|
|
|
+ if (min != null && Number(min) > 0) return `满${min}元可用`
|
|
|
+ return ''
|
|
|
+})
|
|
|
+
|
|
|
+const displayStatus = computed(() => {
|
|
|
+ if (mode.value === 'mine' && mineCoupon.value) {
|
|
|
+ return mineCoupon.value.statusName || ''
|
|
|
+ }
|
|
|
+ if (mode.value === 'available' && availableCoupon.value) {
|
|
|
+ return (availableCoupon.value.status || '可领取') as string
|
|
|
+ }
|
|
|
+ return ''
|
|
|
+})
|
|
|
+
|
|
|
+const statusClass = computed(() => {
|
|
|
+ const s = displayStatus.value
|
|
|
+ if (s === '已使用' || s === '已过期' || s === '已作废' || s === '已冻结') return 'detail-hero__status--muted'
|
|
|
+ return 'detail-hero__status--active'
|
|
|
+})
|
|
|
+
|
|
|
+const heroAmount = computed(() => {
|
|
|
+ const c = coupon.value
|
|
|
+ if (c?.face) return c.face as string
|
|
|
+ const amount = c?.discountValue ?? c?.discountAmount
|
|
|
+ return amount != null ? String(amount) : '惠'
|
|
|
+})
|
|
|
+
|
|
|
+const heroUnit = computed(() => {
|
|
|
+ const c = coupon.value
|
|
|
+ if (c?.face) return ''
|
|
|
+ return '元'
|
|
|
+})
|
|
|
+
|
|
|
+const displayDiscountType = computed(() => {
|
|
|
+ const c = coupon.value
|
|
|
+ return (c?.discountTypeName || '') as string
|
|
|
+})
|
|
|
+
|
|
|
+const displayDiscountValue = computed(() => {
|
|
|
+ const c = coupon.value
|
|
|
+ const v = c?.discountValue
|
|
|
+ if (v == null) return '-'
|
|
|
+ const dt = c?.discountType
|
|
|
+ if (dt === 2) return `${v}折`
|
|
|
+ return `¥${v}`
|
|
|
+})
|
|
|
+
|
|
|
+const displayMaxDiscount = computed(() => {
|
|
|
+ const c = coupon.value
|
|
|
+ const v = c?.maxDiscountAmount
|
|
|
+ if (v == null || Number(v) <= 0) return ''
|
|
|
+ return String(v)
|
|
|
+})
|
|
|
+
|
|
|
+const displayValidPeriod = computed(() => {
|
|
|
+ const c = coupon.value
|
|
|
+ if (mode.value === 'available') {
|
|
|
+ const from = c?.validFrom ? String(c.validFrom).slice(0, 10) : ''
|
|
|
+ const to = c?.validTo ? String(c.validTo).slice(0, 10) : ''
|
|
|
+ if (from && to) return `${from} ~ ${to}`
|
|
|
+ if (to) return `至 ${to}`
|
|
|
+ return ''
|
|
|
+ }
|
|
|
+ const start = c?.validStartDate ? formatDate(String(c.validStartDate)) : ''
|
|
|
+ const end = c?.validEndDate ? formatDate(String(c.validEndDate)) : ''
|
|
|
+ if (start && end) return `${start} ~ ${end}`
|
|
|
+ if (end) return `至 ${end}`
|
|
|
+ return ''
|
|
|
+})
|
|
|
+
|
|
|
+const displayIssuePeriod = computed(() => {
|
|
|
+ return (coupon.value?.issuePeriod || '') as string
|
|
|
+})
|
|
|
+
|
|
|
+const SCENE_CODE_MAP: Record<string, string> = {
|
|
|
+ MALL: '商城',
|
|
|
+ HOTEL: '酒店',
|
|
|
+ FOOD: '美食',
|
|
|
+ SCENIC: '门票',
|
|
|
+ STUDY: '研学',
|
|
|
+ YEARCARD: '年卡',
|
|
|
+ FOOD_MEAL: '个人订餐',
|
|
|
+ STORE_OFFLINE: '店铺线下到店'
|
|
|
+}
|
|
|
+
|
|
|
+function resolveSceneName(code?: string, name?: string) {
|
|
|
+ if (name) return name
|
|
|
+ if (!code) return ''
|
|
|
+ return SCENE_CODE_MAP[String(code).trim().toUpperCase()] || code
|
|
|
+}
|
|
|
+
|
|
|
+const displaySceneName = computed(() => {
|
|
|
+ return resolveSceneName(coupon.value?.sceneCode as string, coupon.value?.sceneName as string)
|
|
|
+})
|
|
|
+
|
|
|
+const displayScopeName = computed(() => {
|
|
|
+ return (coupon.value?.useScopeName || '') as string
|
|
|
+})
|
|
|
+
|
|
|
+const displayScopeLabels = computed(() => {
|
|
|
+ const c = coupon.value
|
|
|
+ return (c?.scope as string[]) || []
|
|
|
+})
|
|
|
+
|
|
|
+const displayStackRule = computed(() => {
|
|
|
+ return (coupon.value?.stackRule || '') as string
|
|
|
+})
|
|
|
+
|
|
|
+const displayRules = computed(() => {
|
|
|
+ const c = coupon.value
|
|
|
+ return (c?.rules as string[]) || []
|
|
|
+})
|
|
|
+
|
|
|
+const displayVerifyTip = computed(() => {
|
|
|
+ return (coupon.value?.verifyTip || '') as string
|
|
|
+})
|
|
|
+
|
|
|
+// ---- 底部操作栏 ----
|
|
|
+
|
|
|
+const showBottomBar = computed(() => {
|
|
|
+ if (loading.value || !coupon.value) return false
|
|
|
+ if (mode.value === 'mine') return canShowVerify.value
|
|
|
+ return canReceive.value
|
|
|
+})
|
|
|
+
|
|
|
+const canShowVerify = computed(() => {
|
|
|
+ return mode.value === 'mine' && mineCoupon.value?.status === 0
|
|
|
+})
|
|
|
+
|
|
|
+const canReceive = computed(() => {
|
|
|
+ if (mode.value !== 'available') return false
|
|
|
+ const status = availableCoupon.value?.status
|
|
|
+ return !status || status === '可领取'
|
|
|
+})
|
|
|
+
|
|
|
+function goVerifyCode() {
|
|
|
+ if (!memberCouponId.value || !tenantCode.value) return
|
|
|
+ uni.navigateTo({
|
|
|
+ url: `/package-views/pages/marketing/verify-code?memberCouponId=${encodeURIComponent(memberCouponId.value)}&tenantCode=${encodeURIComponent(tenantCode.value)}`
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+async function handleReceive() {
|
|
|
+ if (!isLoggedIn()) {
|
|
|
+ requireLoginNavigate('/pages/home/index')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!tenantCode.value || !availableCoupon.value) return
|
|
|
+ const templateId = String(
|
|
|
+ availableCoupon.value.templateId || availableCoupon.value.couponId || availableCoupon.value.id || ''
|
|
|
+ )
|
|
|
+ if (!templateId) return
|
|
|
+ receiving.value = true
|
|
|
+ try {
|
|
|
+ await receiveMarketingCoupon({ tenantCode: tenantCode.value, templateId })
|
|
|
+ uni.showToast({ title: '领取成功', icon: 'none' })
|
|
|
+ setTimeout(() => uni.navigateBack(), 1200)
|
|
|
+ } catch (e) {
|
|
|
+ uni.showToast({ title: (e as Error).message || '领取失败', icon: 'none' })
|
|
|
+ } finally {
|
|
|
+ receiving.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.coupon-detail {
|
|
|
+ padding: 24rpx 24rpx 160rpx;
|
|
|
+ min-height: 100vh;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-hero {
|
|
|
+ display: flex;
|
|
|
+ align-items: stretch;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 28rpx;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: 0 12rpx 40rpx rgba(255, 107, 53, 0.12);
|
|
|
+ margin-bottom: 24rpx;
|
|
|
+
|
|
|
+ &--dimmed {
|
|
|
+ .detail-hero__left {
|
|
|
+ background: linear-gradient(160deg, #c4b5a5, #a89888);
|
|
|
+ }
|
|
|
+ .detail-hero__name {
|
|
|
+ color: #8c7b6a;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.detail-hero__left {
|
|
|
+ width: 200rpx;
|
|
|
+ background: linear-gradient(160deg, #ff9a3c, #ff5722);
|
|
|
+ color: #fff;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 32rpx 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-hero__amount {
|
|
|
+ font-size: 52rpx;
|
|
|
+ font-weight: 800;
|
|
|
+ line-height: 1.1;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-hero__unit {
|
|
|
+ margin-top: 6rpx;
|
|
|
+ font-size: 24rpx;
|
|
|
+ opacity: 0.92;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-hero__body {
|
|
|
+ flex: 1;
|
|
|
+ padding: 28rpx 24rpx;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-hero__name-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-hero__name {
|
|
|
+ font-size: 34rpx;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #2c1810;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-hero__type {
|
|
|
+ flex-shrink: 0;
|
|
|
+ padding: 2rpx 12rpx;
|
|
|
+ border-radius: 6rpx;
|
|
|
+ background: #fff3e6;
|
|
|
+ color: #ff7a1a;
|
|
|
+ font-size: 20rpx;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-hero__threshold {
|
|
|
+ margin-top: 10rpx;
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #8c7b6a;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-hero__status {
|
|
|
+ display: inline-block;
|
|
|
+ margin-top: 14rpx;
|
|
|
+ padding: 6rpx 18rpx;
|
|
|
+ border-radius: 999rpx;
|
|
|
+ font-size: 22rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ align-self: flex-start;
|
|
|
+
|
|
|
+ &--active {
|
|
|
+ background: #e8f8ef;
|
|
|
+ color: #1f9d55;
|
|
|
+ }
|
|
|
+
|
|
|
+ &--muted {
|
|
|
+ background: #f5f0ea;
|
|
|
+ color: #a89888;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.detail-section {
|
|
|
+ margin-bottom: 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-list {
|
|
|
+ margin-top: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ padding: 12rpx 0;
|
|
|
+ border-bottom: 1rpx solid rgba(0, 0, 0, 0.04);
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ border-bottom: none;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.detail-row__label {
|
|
|
+ width: 160rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #8c7b6a;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-row__value {
|
|
|
+ flex: 1;
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #2c1810;
|
|
|
+ word-break: break-all;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-row__tags {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-scope-tag {
|
|
|
+ padding: 4rpx 14rpx;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ background: #fff3e6;
|
|
|
+ color: #ff7a1a;
|
|
|
+ font-size: 22rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-rules {
|
|
|
+ margin-top: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-rule-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 8rpx;
|
|
|
+ padding: 6rpx 0;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-rule-dot {
|
|
|
+ color: #ff7a1a;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 1.6;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-rule-text {
|
|
|
+ flex: 1;
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #8c7b6a;
|
|
|
+ line-height: 1.6;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-verify-tip {
|
|
|
+ display: block;
|
|
|
+ margin-top: 14rpx;
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #8c7b6a;
|
|
|
+ line-height: 1.6;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-card {
|
|
|
+ margin-bottom: 24rpx;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-bottom {
|
|
|
+ position: fixed;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ padding: 20rpx 24rpx calc(20rpx + env(safe-area-inset-bottom));
|
|
|
+ background: rgba(255, 255, 255, 0.95);
|
|
|
+ backdrop-filter: blur(12px);
|
|
|
+ box-shadow: 0 -8rpx 24rpx rgba(44, 24, 16, 0.06);
|
|
|
+}
|
|
|
+
|
|
|
+.detail-bottom__btn {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+</style>
|