977 lines
23 KiB
Vue
977 lines
23 KiB
Vue
<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}`)
|
||
|
||
// 使用门槛(minAmount):0/空 => 无门槛
|
||
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> |