fix: 协议勾选控制+news详情页样式修复+支付页重做

This commit is contained in:
Yuzhiran Dev
2026-07-12 17:43:32 +08:00
parent 23de8bc86a
commit 5ac33dcd7f
5 changed files with 325 additions and 213 deletions
+2 -1
View File
@@ -148,7 +148,8 @@ function onNotify() {
}
function goNewsDetail(item) {
uni.switchTab({ url: '/pages/trend/trend' })
uni.setStorageSync('__news_item__', JSON.stringify(item))
uni.navigateTo({ url: '/pages/news/news' })
}
</script>
+8
View File
@@ -133,6 +133,10 @@ const BASE_URL = 'https://wdkj.yuzhiran.com.cn'
/** 发送邮箱验证码 */
async function handleSendCode() {
if (!agreed.value) {
uni.showToast({ title: '请先阅读并同意用户协议和隐私政策', icon: 'none' })
return
}
const email = registerForm.value.email
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
uni.showToast({ title: '请先输入正确的邮箱', icon: 'none' })
@@ -170,6 +174,10 @@ async function handleSendCode() {
}
const wechatLogin = async () => {
if (!loginAgreed.value) {
uni.showToast({ title: '请先阅读并同意用户协议和隐私政策', icon: 'none' })
return
}
try {
const loginRes = await new Promise((resolve, reject) => {
uni.login({ success: resolve, fail: reject })
+15 -14
View File
@@ -198,7 +198,7 @@ onMounted(() => {
.nav-back {
font-size: 28px;
color: #fff;
color: var(--text-primary);
padding: 0 8px;
line-height: 1;
}
@@ -209,7 +209,7 @@ onMounted(() => {
color: var(--text-primary);
}
.nav-share { font-size: 22px; color: rgba(255,255,255,0.5); }
.nav-share { font-size: 22px; color: var(--text-muted); }
.content {
flex: 1;
@@ -218,18 +218,19 @@ onMounted(() => {
// Hero
.news-hero {
height: 180px;
height: 160px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0 0 24px 24px;
margin: 0 20px;
}
.hero-emoji { font-size: 72px; }
.hero-emoji { font-size: 64px; }
// Category
.category-tag {
margin: 24px 20px 0;
margin: 20px 20px 0;
}
.tag-text {
@@ -270,19 +271,19 @@ onMounted(() => {
.meta-label {
font-size: 11px;
color: rgba(255,255,255,0.35);
color: var(--text-tertiary);
}
.meta-label.hot { font-size: 13px; }
.meta-value { font-size: 12px; color: rgba(255,255,255,0.7); }
.meta-value { font-size: 12px; color: var(--text-secondary); }
// Summary
.summary-card {
margin: 0 20px 20px;
padding: 16px;
background: linear-gradient(135deg, rgba(179,136,255,0.08), rgba(77,208,225,0.05));
border: 1px solid rgba(179,136,255,0.12);
background: linear-gradient(135deg, rgba(99,102,241,0.06), rgba(0,188,212,0.04));
border: 1px solid rgba(99,102,241,0.1);
border-radius: 12px;
}
@@ -297,7 +298,7 @@ onMounted(() => {
.summary-text {
font-size: 14px;
line-height: 1.7;
color: rgba(255,255,255,0.85);
color: var(--text-primary);
}
// Body
@@ -305,7 +306,7 @@ onMounted(() => {
margin: 0 20px 20px;
font-size: 14px;
line-height: 1.8;
color: rgba(255,255,255,0.7);
color: var(--text-secondary);
}
// Related tags
@@ -316,7 +317,7 @@ onMounted(() => {
.related-label {
display: block;
font-size: 12px;
color: rgba(255,255,255,0.4);
color: var(--text-tertiary);
margin-bottom: 10px;
}
@@ -329,7 +330,7 @@ onMounted(() => {
border-radius: 20px;
}
.tag-text { font-size: 12px; color: rgba(255,255,255,0.6); }
.tag-item .tag-text { font-size: 12px; color: var(--text-secondary); }
// Action bar
.action-bar {
@@ -351,7 +352,7 @@ onMounted(() => {
}
.action-icon { font-size: 18px; }
.action-text { font-size: 13px; color: rgba(255,255,255,0.7); }
.action-text { font-size: 13px; color: var(--text-secondary); }
.footer-spacer { height: 60px; }
</style>
+298 -196
View File
@@ -9,72 +9,111 @@
<view class="nav-placeholder"></view>
</view>
<!-- 头部 -->
<view class="payment-header">
<text class="header-title">解锁 AI 维度 Pro</text>
<text class="header-desc">无限 AI 问答 · 全量知识解锁 · 每日趋势优先推送</text>
</view>
<!-- 方案选择 -->
<view class="plan-selector">
<view
class="plan-tag"
:class="{ active: selectedPlan === 'pro' }"
@click="selectedPlan = 'pro'"
>
<text class="plan-name">Pro</text>
<text class="plan-price">¥19.9<span class="unit">/</span></text>
<text class="plan-badge">推荐</text>
<scroll-view scroll-y class="scroll-area">
<!-- 头部 -->
<view class="payment-header">
<view class="header-icon-wrap">
<text class="header-icon"></text>
</view>
<text class="header-title">解锁 AI 维度 Pro</text>
<text class="header-desc">无限 AI 问答 · 全量知识 · 趋势优先推送</text>
</view>
<view
class="plan-tag vip"
:class="{ active: selectedPlan === 'vip' }"
@click="selectedPlan = 'vip'"
>
<text class="plan-name">VIP</text>
<text class="plan-price">¥39.9<span class="unit">/</span></text>
<text class="plan-desc"> Pro 全部权益</text>
</view>
</view>
<!-- 权益对比 -->
<view class="benefits">
<view class="benefit-item" v-for="(item, idx) in benefits" :key="idx">
<text class="b-icon">{{ item.icon }}</text>
<text class="b-text">{{ item.text }}</text>
</view>
</view>
<!-- 套餐选择 -->
<view class="duration-section">
<text class="duration-title">选择套餐</text>
<view class="duration-list">
<!-- 方案选择卡片 -->
<view class="plan-selector">
<view
class="duration-item"
:class="{ active: selectedDuration === item.key }"
v-for="item in durations"
:key="item.key"
@click="selectedDuration = item.key"
class="plan-card"
:class="{ active: selectedPlan === 'pro' }"
@click="selectedPlan = 'pro'"
>
<text class="d-label">{{ item.label }}</text>
<text class="d-price">¥{{ item.price }}</text>
<text class="d-save" v-if="item.save"> {{ item.save }}</text>
<view class="plan-top">
<text class="plan-name">Pro</text>
<view class="plan-badge" v-if="selectedPlan === 'pro'">推荐</view>
</view>
<text class="plan-price">¥19.9</text>
<text class="plan-unit">/ </text>
<view class="plan-features">
<text class="plan-feature"> AI 问答无限次</text>
<text class="plan-feature"> 全量知识解锁</text>
<text class="plan-feature"> 收藏无限容量</text>
</view>
</view>
<view
class="plan-card vip"
:class="{ active: selectedPlan === 'vip' }"
@click="selectedPlan = 'vip'"
>
<view class="plan-top">
<text class="plan-name">VIP</text>
<view class="plan-badge vip-badge" v-if="selectedPlan === 'vip'">尊享</view>
</view>
<text class="plan-price vip-price">¥39.9</text>
<text class="plan-unit">/ </text>
<view class="plan-features">
<text class="plan-feature"> Pro 全部权益</text>
<text class="plan-feature"> 专属客服支持</text>
<text class="plan-feature"> 专属会员标识</text>
</view>
</view>
</view>
</view>
<!-- 支付按钮 -->
<view class="pay-section">
<button class="pay-btn" :loading="loading" @click="handlePay">
立即支付 ¥{{ currentPrice }}
</button>
<text class="pay-agreement">
点击支付即同意用户协议隐私政策
</text>
</view>
<!-- 套餐时长 -->
<view class="duration-section">
<text class="section-title">选择套餐</text>
<view class="duration-list">
<view
class="duration-item"
:class="{ active: selectedDuration === item.key }"
v-for="item in durationOptions"
:key="item.key"
@click="selectedDuration = item.key"
>
<text class="d-label">{{ item.label }}</text>
<text class="d-price">¥{{ item.price }}</text>
<text class="d-save" v-if="item.save"> {{ item.save }}</text>
</view>
</view>
</view>
<!-- 底部 -->
<text class="footer-text">如有问题请联系客服 · 支持 7 天无理由退款</text>
<!-- 权益清单 -->
<view class="benefits-section">
<text class="section-title">权益对比</text>
<view class="benefits-card">
<view
class="benefit-row"
v-for="(item, idx) in benefitList"
:key="idx"
>
<view class="benefit-left">
<text class="benefit-icon">{{ item.icon }}</text>
<text class="benefit-text">{{ item.text }}</text>
</view>
<view class="benefit-check">
<text class="check-mark" :class="{ 'pro-only': item.proOnly, 'vip-only': !item.proOnly }">
{{ item.proOnly ? 'Pro' : (item.hasPro ? 'Pro+VIP' : 'VIP') }}
</text>
</view>
</view>
</view>
</view>
<!-- 支付按钮 -->
<view class="pay-section">
<button
class="pay-btn"
:loading="loading"
:disabled="loading"
@click="handlePay"
>
<text class="pay-btn-text">立即支付 ¥{{ currentPrice }}</text>
</button>
<text class="pay-agreement">
点击支付即表示同意用户协议隐私政策
</text>
</view>
<view class="footer-spacer"></view>
</scroll-view>
</view>
</template>
@@ -85,32 +124,28 @@ import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
// 方案
const selectedPlan = ref('pro')
// 套餐时长
const durations = ref([
{ key: 'pro-monthly', label: 'Pro 月付', price: 19.9 },
{ key: 'pro-quarterly', label: 'Pro 季付', price: 49.9, save: '省 ¥10' },
{ key: 'vip-monthly', label: 'VIP 月付', price: 39.9 },
{ key: 'vip-quarterly', label: 'VIP 季付', price: 99.9, save: '省 ¥20' }
])
const selectedDuration = ref('pro-monthly')
// 权益
const benefits = ref([
{ icon: '🤖', text: 'AI 问答无限次' },
{ icon: '📚', text: '全量知识内容解锁' },
{ icon: '📡', text: '每日趋势优先推送' },
{ icon: '⭐', text: '收藏夹无限容量' },
{ icon: '🎁', text: '专属会员标识' },
{ icon: '💬', text: '专属客服支持' }
const durationOptions = computed(() => {
const isPro = selectedPlan.value === 'pro'
return [
{ key: isPro ? 'pro-monthly' : 'vip-monthly', label: '月付', price: isPro ? 19.9 : 39.9 },
{ key: isPro ? 'pro-quarterly' : 'vip-quarterly', label: '季付', price: isPro ? 49.9 : 99.9, save: isPro ? '¥10' : '¥20' }
]
})
const benefitList = ref([
{ icon: '🤖', text: 'AI 问答无限次', proOnly: true, hasPro: true },
{ icon: '📚', text: '全量知识内容解锁', proOnly: true, hasPro: true },
{ icon: '📡', text: '每日趋势优先推送', proOnly: true, hasPro: true },
{ icon: '⭐', text: '收藏夹无限容量', proOnly: true, hasPro: true },
{ icon: '💬', text: '专属客服支持', proOnly: false, hasPro: false },
{ icon: '🎁', text: '专属会员标识', proOnly: false, hasPro: false }
])
// 当前价格
const currentPrice = computed(() => {
const item = durations.value.find(d => d.key === selectedDuration.value)
const item = durationOptions.value.find(d => d.key === selectedDuration.value)
return item ? item.price : 19.9
})
@@ -122,24 +157,14 @@ async function handlePay() {
return
}
// 找到对应的 productId
const productMap = {
'pro-monthly': 'pro-monthly',
'pro-quarterly': 'pro-quarterly',
'vip-monthly': 'vip-monthly',
'vip-quarterly': 'vip-quarterly'
}
const productId = selectedDuration.value
loading.value = true
try {
// 调用后端创建订单
const data = await paymentApi.createOrder(productMap[selectedDuration.value], userStore.token)
// 获取微信支付参数
const data = await paymentApi.createOrder(productId, userStore.token)
const payParam = data.payParams || data.wechatParam || data.paymentData
if (payParam) {
// 调起微信支付
uni.requestPayment({
provider: 'wxpay',
timeStamp: payParam.timeStamp || payParam.timestamp,
@@ -154,7 +179,7 @@ async function handlePay() {
}, 1500)
},
fail: (err) => {
if (err.errMsg.includes('cancel')) {
if (err.errMsg && err.errMsg.includes('cancel')) {
uni.showToast({ title: '已取消支付', icon: 'none' })
} else {
uni.showToast({ title: '支付失败,请重试', icon: 'none' })
@@ -179,19 +204,20 @@ function goBack() {
<style lang="scss" scoped>
.page-payment {
min-height: 100vh;
padding: 0 20px;
height: 100vh;
display: flex;
flex-direction: column;
background: var(--bg-primary);
}
.status-bar-placeholder { height: 44px; }
// 导航
.nav-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 0;
padding: 12px 20px;
flex-shrink: 0;
}
.nav-back {
@@ -214,23 +240,45 @@ function goBack() {
width: 30px;
}
.scroll-area {
flex: 1;
padding: 0 20px;
}
// 头部
.payment-header {
text-align: center;
padding: 10px 0 20px;
padding: 10px 0 24px;
}
.header-icon-wrap {
width: 56px;
height: 56px;
border-radius: 16px;
background: linear-gradient(135deg, var(--dim1-color), var(--dim3-color));
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 16px;
box-shadow: 0 8px 24px rgba(99,102,241,0.25);
}
.header-icon {
font-size: 28px;
color: white;
}
.header-title {
font-size: 22px;
font-size: 24px;
font-weight: 700;
color: var(--text-primary);
display: block;
margin-bottom: 6px;
}
.header-desc {
font-size: 12px;
font-size: 13px;
color: var(--text-tertiary);
margin-top: 6px;
display: block;
}
@@ -238,181 +286,235 @@ function goBack() {
.plan-selector {
display: flex;
gap: 12px;
margin-bottom: 20px;
margin-bottom: 24px;
}
.plan-tag {
.plan-card {
flex: 1;
padding: 16px;
padding: 20px 16px;
background: var(--glass-bg);
border: 1px solid var(--glass-border);
border-radius: 14px;
text-align: center;
border: 1.5px solid var(--glass-border);
border-radius: 16px;
position: relative;
transition: all 0.3s;
&.active {
border-color: var(--dim1-color);
box-shadow: 0 0 0 1px rgba(99,102,241,0.2), 0 8px 24px rgba(99,102,241,0.1);
background: linear-gradient(135deg, rgba(99,102,241,0.06), rgba(99,102,241,0.02));
}
&.vip.active {
border-color: #ffa726;
box-shadow: 0 0 0 1px rgba(255,167,38,0.2), 0 8px 24px rgba(255,167,38,0.1);
background: linear-gradient(135deg, rgba(255,167,38,0.06), rgba(255,167,38,0.02));
}
}
.plan-tag.active {
background: linear-gradient(135deg, rgba(124, 77, 255, 0.15), rgba(0, 0, 0, 0.2));
border-color: rgba(124, 77, 255, 0.3);
}
.plan-tag.vip.active {
background: linear-gradient(135deg, rgba(255, 152, 0, 0.15), rgba(0, 0, 0, 0.2));
border-color: rgba(255, 152, 0, 0.3);
.plan-top {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.plan-name {
font-size: 18px;
font-size: 20px;
font-weight: 700;
color: var(--text-primary);
display: block;
margin-bottom: 6px;
}
.plan-price {
font-size: 24px;
font-weight: 700;
color: var(--dim1-color);
display: block;
}
.plan-tag.vip .plan-price {
color: #ffa726;
}
.unit {
font-size: 12px;
color: var(--text-tertiary);
font-weight: 400;
}
.plan-badge {
position: absolute;
top: -8px;
right: 12px;
background: linear-gradient(135deg, #ff9800, #ff5722);
color: white;
font-size: 10px;
font-weight: 600;
padding: 2px 10px;
border-radius: 10px;
background: linear-gradient(135deg, var(--dim1-color), #7c4dff);
color: white;
}
.plan-desc {
font-size: 11px;
color: var(--text-muted);
margin-top: 4px;
display: block;
.vip-badge {
background: linear-gradient(135deg, #ffa726, #ff7043);
}
// 权益
.benefits {
background: var(--glass-bg);
border: 1px solid var(--glass-border);
border-radius: 14px;
padding: 16px;
margin-bottom: 20px;
.plan-price {
font-size: 28px;
font-weight: 700;
color: var(--dim1-color);
display: inline;
}
.benefit-item {
.plan-card.vip .plan-price {
color: #ffa726;
}
.plan-unit {
font-size: 12px;
color: var(--text-tertiary);
margin-left: 2px;
}
.plan-features {
margin-top: 14px;
display: flex;
align-items: center;
gap: 10px;
padding: 8px 0;
flex-direction: column;
gap: 6px;
}
.benefit-item:not(:last-child) {
border-bottom: 1px solid var(--glass-border);
}
.b-icon { font-size: 16px; }
.b-text {
font-size: 13px;
.plan-feature {
font-size: 12px;
color: var(--text-secondary);
line-height: 1.5;
}
// 套餐时长
.duration-section {
margin-bottom: 20px;
margin-bottom: 24px;
}
.duration-title {
.section-title {
font-size: 14px;
color: rgba(255, 255, 255, 0.6);
margin-bottom: 10px;
font-weight: 600;
color: var(--text-primary);
display: block;
margin-bottom: 12px;
}
.duration-list {
display: flex;
gap: 8px;
gap: 10px;
}
.duration-item {
flex: 1;
padding: 12px 8px;
padding: 14px 12px;
background: var(--glass-bg);
border: 1px solid var(--glass-border);
border-radius: 12px;
border: 1.5px solid var(--glass-border);
border-radius: 14px;
text-align: center;
}
transition: all 0.3s;
.duration-item.active {
background: rgba(124, 77, 255, 0.12);
border-color: rgba(124, 77, 255, 0.3);
&.active {
border-color: var(--dim1-color);
background: linear-gradient(135deg, rgba(99,102,241,0.1), rgba(99,102,241,0.05));
}
}
.d-label {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
font-size: 13px;
color: var(--text-secondary);
display: block;
margin-bottom: 4px;
margin-bottom: 6px;
}
.d-price {
font-size: 16px;
font-weight: 600;
color: var(--dim1-color);
font-size: 18px;
font-weight: 700;
color: var(--text-primary);
display: block;
}
.d-save {
font-size: 10px;
font-size: 11px;
font-weight: 600;
color: #4db6ac;
display: block;
margin-top: 2px;
margin-top: 4px;
}
// 权益对比
.benefits-section {
margin-bottom: 24px;
}
.benefits-card {
background: var(--glass-bg);
border: 1px solid var(--glass-border);
border-radius: 14px;
overflow: hidden;
}
.benefit-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
&:not(:last-child) {
border-bottom: 1px solid var(--glass-border);
}
}
.benefit-left {
display: flex;
align-items: center;
gap: 10px;
}
.benefit-icon {
font-size: 16px;
width: 24px;
text-align: center;
}
.benefit-text {
font-size: 13px;
color: var(--text-secondary);
}
.benefit-check {
.check-mark {
font-size: 11px;
font-weight: 600;
padding: 2px 10px;
border-radius: 8px;
&.pro-only {
background: rgba(99,102,241,0.1);
color: var(--dim1-color);
}
&.vip-only {
background: rgba(255,167,38,0.1);
color: #ffa726;
}
}
}
// 支付
.pay-section {
margin-top: 20px;
margin-bottom: 24px;
}
.pay-btn {
width: 100%;
height: 50px;
height: 52px;
background: linear-gradient(135deg, var(--dim1-color), var(--dim3-color));
color: white;
border: none;
border-radius: 12px;
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 6px 20px rgba(99,102,241,0.3);
&[disabled] {
opacity: 0.6;
}
}
.pay-btn-text {
font-size: 16px;
font-weight: 600;
box-shadow: 0 4px 16px rgba(179, 136, 255, 0.3);
}
.pay-agreement {
font-size: 10px;
font-size: 11px;
color: var(--text-muted);
text-align: center;
margin-top: 10px;
margin-top: 12px;
display: block;
}
.footer-text {
font-size: 10px;
color: var(--text-muted);
text-align: center;
margin-top: 20px;
display: block;
}
</style>
.footer-spacer { height: 40px; }
</style>
+2 -2
View File
@@ -39,9 +39,9 @@ async function sendVerificationCode(to, code) {
}
try {
await transport.sendMail({
from: `"AI维度" <${SMTP_FROM}>`,
from: `"宇之然AI维度" <${SMTP_FROM}>`,
to,
subject: '【AI维度】邮箱验证码',
subject: '【宇之然AI维度】邮箱验证码',
html: `
<div style="max-width:480px;margin:0 auto;padding:24px;font-family:sans-serif;">
<h2 style="color:#6366f1;">AI 维度 - 邮箱验证</h2>