chuangte_bike_newxcx/pages/tuihuang.vue
2026-03-16 10:03:51 +08:00

977 lines
23 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.

<template>
<view class="page">
<u-navbar title="推广领取" :border-bottom="false" :background="bgc" back-icon-color="#111827" title-color="#111827"
title-size="34" title-bold height="44" id="navbar">
</u-navbar>
<!-- 顶部卡片 -->
<view class="hero">
<view class="hero-bg">
<view class="b b1"></view>
<view class="b b2"></view>
<view class="b b3"></view>
</view>
<view class="hero-content">
<view class="hero-title">专属福利已解锁</view>
<view class="hero-sub">扫码进入后可领取运营区会员卡并购买</view>
</view>
</view>
<!-- 列表 -->
<view class="list">
<view class="section-title">
<text class="st-main">可购买会员卡</text>
<text class="st-sub" v-if="areaName">运营区:{{ areaName }}</text>
</view>
<view class="loading" v-if="loading">
<view class="skeleton" v-for="n in 3" :key="n">
<view class="sk-line w60"></view>
<view class="sk-line w40"></view>
<view class="sk-btn"></view>
</view>
<text class="loading-text">加载中...</text>
</view>
<view v-else>
<view class="vip-card" v-for="item in vipList" :key="item.id">
<view class="vip-head">
<view class="vip-icon">
<u-icon name="level" size="34" color="#2563EB"></u-icon>
</view>
<view class="vip-head-main">
<view class="vip-title-row">
<text class="vip-title">{{ item.name || '会员卡' }}</text>
<view class="tag" v-if="item.discountValue">
<text>{{ item.discountValue }}折</text>
</view>
</view>
<view class="vip-desc">{{ formatVipMeta(item) }}</view>
</view>
</view>
<view class="vip-footer">
<view class="vf-left">
<template v-if="!isFree(item)">
<text class="vf-yen">¥</text>
<text class="vf-num">{{ item.price }}</text>
</template>
<template v-else>
<!-- 0元不展示价格 -->
<text class="vf-free">免费</text>
</template>
</view>
<view class="vf-btn" :class="[isFree(item) ? 'btn-free' : 'btn-buy']" @click.stop="openBuy(item)">
<text>{{ isFree(item) ? '领取' : '购买' }}</text>
</view>
</view>
</view>
<u-empty v-if="vipList.length === 0" mode="data" text="暂无可购买的会员卡" margin-top="120"></u-empty>
</view>
</view>
<!-- 购买弹窗 -->
<u-mask :show="showBuy" :z-index="100" />
<view class="sheet" v-if="showBuy">
<view class="sheet-hd">
<text class="sheet-title">{{ isFree(selectedVip) ? '确认领取' : '确认购买' }}</text>
<text class="sheet-close" @click="closeBuy">×</text>
</view>
<view class="sheet-card">
<view class="sc-top">
<view class="sc-name">
<text class="name">{{ selectedVip.name || '会员卡' }}</text>
<text class="badge" v-if="selectedVip.discountValue">{{ selectedVip.discountValue }}折</text>
</view>
<view class="sc-price">
<template v-if="isFree(selectedVip)">
<text class="free">免费</text>
</template>
<template v-else>
<text class="yen">¥</text>
<text class="num">{{ selectedVip.price }}</text>
</template>
</view>
</view>
<view class="sc-sub">
{{ formatVipMeta(selectedVip) }}
</view>
<view class="sc-desc" v-if="selectedVip.description">
{{ selectedVip.description }}
</view>
</view>
<view class="sheet-actions">
<view class="btn ghost" @click="closeBuy">取消</view>
<view class="btn primary" @click="payNow">
<text>{{ paying ? '处理中...' : (isFree(selectedVip) ? '立即领取' : '立即支付') }}</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
bgc: { backgroundColor: "#F7F8FA" },
promotionId: '',
rawQ: '',
loading: false,
detail: {},
vipList: [],
areaId: '',
areaName: '',
channelId: '',
showBuy: false,
selectedVip: {},
paying: false,
redirectVipId: '' // 从登录页带回的 vipId用于自动重新发起购买
}
},
onLoad(option) {
const id = this.extractPromotionId(option || {})
if (!id) {
uni.showToast({ title: '缺少推广参数', icon: 'none' })
return
}
this.promotionId = String(id)
if (option && option.vipId) this.redirectVipId = String(option.vipId)
uni.setStorageSync('areaPromotionId', this.promotionId)
this.getDetail()
},
methods: {
isFree(vip) {
if (!vip) return false
const p = Number(vip.price)
return !isNaN(p) && p <= 0
},
showCounts(item) {
if (!item) return false
return (item.perUserCount || item.perUserCount === 0) || (item.totalCount || item.totalCount === 0)
},
// 从小程序跳转参数里提取推广ID兼容i/id/scene/q
extractPromotionId(option) {
if (option.i || option.id) return option.i || option.id
// scene 兼容:可能是 "i=2" 或直接 "2"
if (option.scene) {
const sc = decodeURIComponent(String(option.scene))
return this.getQueryValue(sc, 'i') || this.getQueryValue(sc, 'id') || sc
}
// 微信扫码进入常见q=encodeURIComponent(完整URL)
if (option.q) {
const decoded = decodeURIComponent(String(option.q))
this.rawQ = decoded
return (
this.getQueryValue(decoded, 'i') ||
this.getQueryValue(decoded, 'id') ||
this.getQueryValue(decoded, 'promotionId') ||
this.getQueryValue(decoded, 'areaPromotionId')
)
}
return ''
},
// 从 URL 或 querystring 中取参数
getQueryValue(urlOrQuery, key) {
if (!urlOrQuery) return ''
let q = String(urlOrQuery)
// 允许传入完整URL
if (q.indexOf('?') > -1) q = q.split('?')[1] || ''
if (q.indexOf('#') > -1) q = q.split('#')[0] || ''
if (!q) return ''
const parts = q.split('&')
for (let i = 0; i < parts.length; i++) {
const kv = parts[i].split('=')
if (!kv.length) continue
const k = decodeURIComponent(kv[0] || '').trim()
if (k !== key) continue
return decodeURIComponent(kv.slice(1).join('=') || '').trim()
}
return ''
},
copyPromotionId() {
if (!this.promotionId) return
uni.setClipboardData({
data: this.promotionId,
success: () => uni.showToast({ title: '已复制', icon: 'success' })
})
},
getDetail() {
if (!this.promotionId) return
this.loading = true
this.$u.get(`/app/areaPromotion/detail?id=${this.promotionId}`).then(res => {
this.loading = false
if (res.code !== 200) {
uni.showToast({ title: res.msg || '获取详情失败', icon: 'none' })
return
}
const data = res.data || {}
this.detail = data
let list = []
if (Array.isArray(data)) list = data
else if (Array.isArray(data.list)) list = data.list
else if (Array.isArray(data.rows)) list = data.rows
else if (Array.isArray(data.vipList)) list = data.vipList
else if (Array.isArray(data.couponList)) list = data.couponList
else if (Array.isArray(data.data)) list = data.data
this.vipList = list || []
// areaId用于获取支付通道如果接口没给就从列表项兜底
this.areaId =
data.areaId ||
data.vipAreaId ||
(this.vipList[0] && (this.vipList[0].areaId || this.vipList[0].vipAreaId)) ||
''
// 运营区名称:用于展示
this.areaName =
data.areaName ||
data.vipAreaName ||
(this.vipList[0] && (this.vipList[0].areaName || this.vipList[0].vipAreaName)) ||
''
if (this.areaId) this.getChannelId()
// 从登录页带回:自动打开该会员卡并重新发起购买
if (this.redirectVipId) {
const vip = this.vipList.find(v => String(v.id) === String(this.redirectVipId))
const vid = this.redirectVipId
this.redirectVipId = ''
if (vip) {
this.openBuy(vip)
setTimeout(() => this.payNow(), 800)
}
}
}).catch(() => {
this.loading = false
uni.showToast({ title: '网络错误', icon: 'none' })
})
},
// 列表中展示的简短描述,避免拥挤
formatVipMeta(item) {
if (!item) return ''
const parts = []
if (item.validDays || item.validDays === 0) parts.push(`有效期${item.validDays}天`)
if (item.limitTotal || item.limitTotal === 0) parts.push(`共${item.limitTotal}次`)
if (item.enableLimit) {
const r = item.limitRound
const c = item.limitCount
if ((r || r === 0) && (c || c === 0)) parts.push(`每${r}天${c}次`)
} else if (item.enableLimit === false) {
parts.push('无频率限制')
}
if (item.perUserCount || item.perUserCount === 0) parts.push(`每人限购${item.perUserCount}`)
// 使用门槛minAmount0/空 => 无门槛
const min = Number(item.minAmount)
if (!isNaN(min) && min > 0) parts.push(`使用门槛¥${item.minAmount}`)
else parts.push('无门槛')
return parts.length ? parts.join(' · ') : '—'
},
getChannelId() {
if (!this.areaId) return
const appId = this.$store && this.$store.state ? this.$store.state.appid : ''
if (!appId) return
this.$u.get(`/app/channel/list?appId=${appId}&areaId=${this.areaId}&bstType=3&appType=1`).then(res => {
if (res.code === 200 && res.data && res.data.length) {
this.channelId = res.data[0].id
}
})
},
openBuy(item) {
this.selectedVip = item || {}
this.showBuy = true
// 弹出时尽量提前准备支付通道
if (!this.channelId && this.areaId) this.getChannelId()
},
closeBuy() {
this.showBuy = false
this.selectedVip = {}
},
// 静默登录(仅用 wx.login 的 code 换 token
silentLogin() {
return new Promise((resolve) => {
// #ifdef MP-WEIXIN
wx.login({
success: (res) => {
if (!res.code) {
resolve(false)
return
}
const data = { loginCode: res.code, appId: this.$store.state.appid }
this.$u.post('/wxLogin', data).then((loginRes) => {
if (loginRes.code === 200) {
uni.setStorageSync('token', loginRes.token)
resolve(true)
}else if(res.code == 401){
resolve(false)
} else {
resolve(false)
}
}).catch(() => resolve(false))
},
fail: () => resolve(false)
})
// #endif
// #ifndef MP-WEIXIN
resolve(false)
// #endif
})
},
// 跳转登录页,登录成功后带参数回到本页重新购买
goLoginWithRedirect() {
const backUrl = '/pages/tuihuang?i=' + encodeURIComponent(this.promotionId) + '&vipId=' + encodeURIComponent(this.selectedVip.id)
uni.navigateTo({
url: '/pages/login/login?redirect=' + encodeURIComponent(backUrl)
})
},
payNow() {
if (this.paying) return
if (!this.selectedVip || !this.selectedVip.id) {
uni.showToast({ title: '缺少会员卡信息', icon: 'none' })
return
}
if (!this.channelId) {
uni.showToast({ title: '支付通道未准备好,请稍后重试', icon: 'none' })
if (this.areaId) this.getChannelId()
return
}
const appId = this.$store && this.$store.state ? this.$store.state.appid : ''
if (!appId) {
uni.showToast({ title: '缺少appId配置', icon: 'none' })
return
}
this.paying = true
uni.showLoading({ title: '加载中...', mask: true })
const vip = this.selectedVip
const data = {
channelId: this.channelId,
appId,
appType: 1,
vipId: vip.id,
vip
}
const doPayResult = (res) => {
if (res.data && res.data.needPay) {
uni.requestPayment({
provider: 'wxpay',
timeStamp: res.data.payParams.timeStamp,
nonceStr: res.data.payParams.nonceStr,
package: res.data.payParams.packageVal,
signType: res.data.payParams.signType,
paySign: res.data.payParams.paySign,
success: () => {
this.$u.put(`/app/pay/refreshPayResult?no=${res.data.pay.no}`).then(() => {
this.paying = false
this.showBuy = false
uni.hideLoading()
uni.showToast({ title: '购买成功', icon: 'success', duration: 2000 })
}).catch(() => {
this.paying = false
uni.hideLoading()
uni.showToast({ title: '支付结果刷新失败', icon: 'none' })
})
},
fail: () => {
this.paying = false
uni.hideLoading()
uni.showToast({ title: '已取消支付', icon: 'none' })
}
})
} else {
this.paying = false
this.showBuy = false
uni.hideLoading()
uni.showToast({ title: '购买成功', icon: 'success', duration: 2000 })
}
}
this.$u.post(`/app/vipOrder`, data).then(res => {
if (res.code === 401) {
this.silentLogin().then((silentOk) => {
if (silentOk) {
// 静默登录成功,用新 token 再请求一次并直接调起支付
this.$u.post(`/app/vipOrder`, data).then(res2 => {
if (res2.code !== 200) {
this.paying = false
uni.hideLoading()
uni.showToast({ title: res2.msg || '下单失败', icon: 'none', duration: 5000 })
return
}
doPayResult(res2)
}).catch(() => {
this.paying = false
uni.hideLoading()
uni.showToast({ title: '网络错误', icon: 'none', duration: 5000 })
})
} else {
this.paying = false
uni.hideLoading()
this.goLoginWithRedirect()
}
})
return
}
if (res.code !== 200) {
this.paying = false
uni.hideLoading()
uni.showToast({ title: res.msg || '下单失败', icon: 'none' ,duration:5000})
return
}
doPayResult(res)
}).catch(() => {
this.paying = false
uni.hideLoading()
uni.showToast({ title: '网络错误', icon: 'none',duration:5000 })
})
}
}
}
</script>
<style lang="scss">
page {
background: #F7F8FA;
min-height: 100vh;
}
.page {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.hero {
margin: 18rpx 24rpx 8rpx;
border-radius: 28rpx;
overflow: hidden;
position: relative;
background: linear-gradient(135deg, #EAF2FF 0%, #F7FBFF 40%, #FFFFFF 100%);
border: 1rpx solid rgba(37, 99, 235, 0.12);
box-shadow: 0 18rpx 40rpx rgba(17, 24, 39, 0.08);
}
.hero-bg {
position: absolute;
inset: 0;
.b {
position: absolute;
border-radius: 50%;
filter: blur(40rpx);
opacity: 0.5;
}
.b1 { width: 360rpx; height: 360rpx; background: #60A5FA; top: -140rpx; right: -120rpx; }
.b2 { width: 280rpx; height: 280rpx; background: #34D399; bottom: -140rpx; left: -120rpx; opacity: 0.35; }
.b3 { width: 240rpx; height: 240rpx; background: #A78BFA; top: 40rpx; left: 120rpx; opacity: 0.22; }
}
.hero-content {
position: relative;
padding: 30rpx 28rpx 26rpx;
}
.hero-title {
font-size: 38rpx;
font-weight: 800;
color: #111827;
letter-spacing: 1rpx;
}
.hero-sub {
margin-top: 10rpx;
font-size: 26rpx;
color: #4B5563;
}
.hero-meta {
margin-top: 18rpx;
display: flex;
gap: 12rpx;
flex-wrap: wrap;
}
.meta-pill {
display: flex;
align-items: center;
gap: 10rpx;
padding: 12rpx 16rpx;
border-radius: 999rpx;
background: rgba(37, 99, 235, 0.08);
border: 1rpx solid rgba(37, 99, 235, 0.12);
.meta-text {
font-size: 24rpx;
color: #1F2937;
}
&.ghost {
background: rgba(255, 255, 255, 0.7);
}
}
.list {
padding: 14rpx 28rpx 90rpx;
}
.section-title {
display: flex;
align-items: baseline;
justify-content: space-between;
margin: 14rpx 6rpx 10rpx;
.st-main {
font-size: 30rpx;
font-weight: 700;
color: #111827;
}
.st-sub {
font-size: 22rpx;
color: #9CA3AF;
}
}
.vip-card {
background: linear-gradient(180deg, #FFFFFF 0%, #FBFDFF 100%);
border-radius: 28rpx;
padding: 32rpx 28rpx 26rpx;
margin-bottom: 22rpx;
box-shadow: 0 18rpx 44rpx rgba(17, 24, 39, 0.08);
border: 1rpx solid rgba(37, 99, 235, 0.08);
overflow: hidden;
position: relative;
&:active { transform: scale(0.99); }
}
.vip-card::after {
content: '';
position: absolute;
right: -120rpx;
top: -120rpx;
width: 240rpx;
height: 240rpx;
border-radius: 50%;
background: radial-gradient(circle at 30% 30%, rgba(66, 151, 243, 0.18), rgba(66, 151, 243, 0));
}
.vip-head {
display: flex;
gap: 18rpx;
align-items: center;
position: relative;
z-index: 1;
}
.vip-icon {
width: 84rpx;
height: 84rpx;
border-radius: 26rpx;
background: linear-gradient(135deg, rgba(37, 99, 235, 0.14) 0%, rgba(99, 102, 241, 0.12) 100%);
border: 1rpx solid rgba(37, 99, 235, 0.16);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.vip-left { display: none; }
.vip-right { display: none; }
.vip-head-main { flex: 1; min-width: 0; }
.vip-name-row { display: none; }
.vip-title-row {
display: flex;
align-items: center;
gap: 12rpx;
.tag {
padding: 4rpx 12rpx;
border-radius: 999rpx;
background: rgba(245, 158, 11, 0.12);
color: #B45309;
font-size: 22rpx;
font-weight: 600;
border: 1rpx solid rgba(245, 158, 11, 0.18);
}
}
.vip-title {
font-size: 36rpx;
font-weight: 900;
color: #0F172A;
max-width: 520rpx;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
letter-spacing: 1rpx;
}
.vip-desc {
margin-top: 10rpx;
font-size: 26rpx;
color: #64748B;
line-height: 1.6;
word-break: break-all;
}
.vip-footer {
margin-top: 18rpx;
height: 96rpx;
border-radius: 22rpx;
background: #F8FAFC;
border: 1rpx solid rgba(15, 23, 42, 0.08);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 18rpx 0 22rpx;
position: relative;
z-index: 1;
}
.vf-left {
display: flex;
align-items: baseline;
min-width: 220rpx;
}
.vf-yen {
font-size: 24rpx;
font-weight: 900;
color: #EF4444;
margin-right: 6rpx;
}
.vf-num {
font-size: 44rpx;
font-weight: 900;
color: #EF4444;
font-family: 'DIN Alternate', sans-serif;
letter-spacing: 1rpx;
}
.vf-free {
font-size: 30rpx;
font-weight: 900;
color: #16A34A;
background: rgba(22, 163, 74, 0.10);
border: 1rpx solid rgba(22, 163, 74, 0.16);
padding: 10rpx 16rpx;
border-radius: 999rpx;
}
.vf-btn {
height: 72rpx;
min-width: 176rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 0 26rpx;
font-size: 30rpx;
font-weight: 900;
letter-spacing: 2rpx;
color: #FFFFFF;
box-shadow: 0 12rpx 24rpx rgba(37, 99, 235, 0.25);
}
.btn-buy {
background: linear-gradient(135deg, #3B82F6 0%, #2563EB 55%, #1D4ED8 100%);
}
.btn-free {
background: linear-gradient(135deg, #22C55E 0%, #16A34A 60%, #15803D 100%);
box-shadow: 0 12rpx 24rpx rgba(22, 163, 74, 0.22);
}
.vip-counts { display: none; }
.vip-chips {
margin-top: 16rpx;
display: flex;
gap: 12rpx;
flex-wrap: wrap;
position: relative;
z-index: 1;
}
.count-pill { display: none; }
.chip {
display: flex;
align-items: center;
gap: 8rpx;
padding: 10rpx 14rpx;
border-radius: 999rpx;
background: rgba(2, 132, 199, 0.06);
border: 1rpx solid rgba(2, 132, 199, 0.12);
.chip-label {
font-size: 22rpx;
color: #475569;
}
.chip-val {
font-size: 22rpx;
font-weight: 800;
color: #0F172A;
}
}
.vip-note {
margin-top: 10rpx;
background: #F9FAFB;
border-radius: 14rpx;
padding: 14rpx 14rpx;
.note-label { font-size: 22rpx; color: #9CA3AF; }
.note-text {
font-size: 22rpx;
color: #4B5563;
line-clamp: 2;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
.buy-btn { display: none; }
.buy-price { display: none; }
.buy-split { display: none; }
.buy-text { display: none; }
.vip-action {
margin-top: 18rpx;
height: 96rpx;
border-radius: 24rpx;
padding: 0 22rpx;
display: flex;
align-items: center;
justify-content: space-between;
background: linear-gradient(135deg, #3B82F6 0%, #2563EB 55%, #1D4ED8 100%);
box-shadow: 0 16rpx 34rpx rgba(37, 99, 235, 0.30);
position: relative;
overflow: hidden;
}
.vip-action::before {
content: '';
position: absolute;
left: -80rpx;
top: -80rpx;
width: 200rpx;
height: 200rpx;
border-radius: 50%;
background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0));
}
.va-left, .va-right {
position: relative;
z-index: 1;
display: flex;
align-items: baseline;
color: #fff;
}
.va-yen {
font-size: 24rpx;
font-weight: 900;
margin-right: 6rpx;
opacity: 0.95;
}
.va-num {
font-size: 42rpx;
font-weight: 900;
font-family: 'DIN Alternate', sans-serif;
letter-spacing: 1rpx;
}
.va-free {
font-size: 34rpx;
font-weight: 900;
letter-spacing: 2rpx;
}
.va-right {
gap: 10rpx;
align-items: center;
}
.va-text {
font-size: 30rpx;
font-weight: 900;
letter-spacing: 2rpx;
}
.loading {
padding: 10rpx 0 0;
.loading-text {
display: block;
text-align: center;
margin-top: 18rpx;
font-size: 24rpx;
color: #9CA3AF;
}
}
.skeleton {
background: #FFFFFF;
border-radius: 24rpx;
padding: 26rpx 24rpx;
margin-bottom: 18rpx;
box-shadow: 0 10rpx 28rpx rgba(17, 24, 39, 0.04);
border: 1rpx solid rgba(17, 24, 39, 0.06);
position: relative;
overflow: hidden;
&::after {
content: '';
position: absolute;
top: 0;
left: -40%;
width: 40%;
height: 100%;
background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.6) 50%, rgba(255,255,255,0) 100%);
animation: sk 1.2s infinite;
}
.sk-line {
height: 26rpx;
border-radius: 999rpx;
background: #F3F4F6;
margin-bottom: 14rpx;
}
.w60 { width: 60%; }
.w40 { width: 40%; }
.sk-btn {
width: 160rpx;
height: 64rpx;
border-radius: 999rpx;
background: #E5E7EB;
margin-top: 10rpx;
}
}
@keyframes sk {
0% { left: -40%; }
100% { left: 120%; }
}
.sheet {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 101;
background: #FFFFFF;
border-radius: 32rpx 32rpx 0 0;
padding: 22rpx 24rpx 24rpx;
padding-bottom: calc(24rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
box-shadow: 0 -14rpx 36rpx rgba(17, 24, 39, 0.18);
}
.sheet-hd {
display: flex;
align-items: center;
justify-content: space-between;
.sheet-title {
font-size: 32rpx;
font-weight: 800;
color: #111827;
}
.sheet-close {
font-size: 52rpx;
color: #9CA3AF;
line-height: 1;
padding: 0 8rpx;
}
}
.sheet-card {
margin-top: 18rpx;
border-radius: 24rpx;
padding: 22rpx 22rpx 20rpx;
background: linear-gradient(180deg, #FFFFFF 0%, #F9FBFF 100%);
border: 1rpx solid rgba(66, 151, 243, 0.12);
.sc-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 16rpx;
}
.sc-name {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
gap: 12rpx;
.name {
font-size: 32rpx;
font-weight: 800;
color: #111827;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.badge {
font-size: 22rpx;
color: #D97706;
background: rgba(217, 119, 6, 0.12);
padding: 4rpx 12rpx;
border-radius: 999rpx;
border: 1rpx solid rgba(217, 119, 6, 0.16);
}
}
.sc-price {
display: flex;
align-items: baseline;
color: #EF4444;
.yen { font-size: 22rpx; font-weight: 800; margin-right: 4rpx; }
.num { font-size: 44rpx; font-weight: 900; font-family: 'DIN Alternate', sans-serif; }
}
.sc-sub {
margin-top: 12rpx;
font-size: 26rpx;
color: #64748B;
line-height: 1.7;
word-break: break-all;
}
.sc-desc {
margin-top: 14rpx;
font-size: 24rpx;
color: #4B5563;
background: #F9FAFB;
border-radius: 16rpx;
padding: 14rpx 14rpx;
line-height: 1.6;
}
}
.sheet-actions {
margin-top: 18rpx;
display: flex;
gap: 16rpx;
.btn {
flex: 1;
height: 92rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
font-weight: 800;
}
.ghost {
background: #FFFFFF;
color: #374151;
border: 2rpx solid #E5E7EB;
}
.primary {
background: linear-gradient(135deg, #4297F3 0%, #2B76E5 100%);
color: #FFFFFF;
box-shadow: 0 10rpx 22rpx rgba(66, 151, 243, 0.28);
}
}
</style>