refactor: 项目架构重构 - 目录标准化 + 清理冗余
- 目录重命名: backend/wdkj-server/ → server/, frontend/ai-dimension/ → client/ - 删除 20+ 冗余文件(WDKJ 旧脚本、Windows 脚本、过时文档、设计稿) - 更新 package.json 元数据(移除 wdkj 命名) - 完善三级 .gitignore(根 + server + client) - 重写 README.md 和 CHANGELOG.md - 工具脚本移至 scripts/
This commit is contained in:
@@ -0,0 +1,418 @@
|
||||
<template>
|
||||
<view class="page-payment">
|
||||
<view class="status-bar-placeholder"></view>
|
||||
|
||||
<!-- 导航 -->
|
||||
<view class="nav-bar">
|
||||
<view class="nav-back" @click="goBack">‹</view>
|
||||
<text class="nav-title">订阅升级</text>
|
||||
<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>
|
||||
</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="duration-item"
|
||||
:class="{ active: selectedDuration === item.key }"
|
||||
v-for="item in durations"
|
||||
: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>
|
||||
|
||||
<!-- 支付按钮 -->
|
||||
<view class="pay-section">
|
||||
<button class="pay-btn" :loading="loading" @click="handlePay">
|
||||
立即支付 ¥{{ currentPrice }}
|
||||
</button>
|
||||
<text class="pay-agreement">
|
||||
点击支付即同意《用户协议》和《隐私政策》
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 底部 -->
|
||||
<text class="footer-text">如有问题请联系客服 · 支持 7 天无理由退款</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { paymentApi } from '@/utils/api'
|
||||
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 currentPrice = computed(() => {
|
||||
const item = durations.value.find(d => d.key === selectedDuration.value)
|
||||
return item ? item.price : 19.9
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
async function handlePay() {
|
||||
if (!userStore.isLoggedIn) {
|
||||
uni.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 找到对应的 productId
|
||||
const productMap = {
|
||||
'pro-monthly': 'pro-monthly',
|
||||
'pro-quarterly': 'pro-quarterly',
|
||||
'vip-monthly': 'vip-monthly',
|
||||
'vip-quarterly': 'vip-quarterly'
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
// 调用后端创建订单
|
||||
const data = await paymentApi.createOrder(productMap[selectedDuration.value], userStore.token)
|
||||
|
||||
// 获取微信支付参数
|
||||
const payParam = data.payParams || data.wechatParam || data.paymentData
|
||||
|
||||
if (payParam) {
|
||||
// 调起微信支付
|
||||
uni.requestPayment({
|
||||
provider: 'wxpay',
|
||||
timeStamp: payParam.timeStamp || payParam.timestamp,
|
||||
nonceStr: payParam.nonceStr,
|
||||
package: payParam.package || payParam.packAge,
|
||||
signType: payParam.signType || 'MD5',
|
||||
paySign: payParam.paySign,
|
||||
success: () => {
|
||||
uni.showToast({ title: '支付成功!', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
},
|
||||
fail: (err) => {
|
||||
if (err.errMsg.includes('cancel')) {
|
||||
uni.showToast({ title: '已取消支付', icon: 'none' })
|
||||
} else {
|
||||
uni.showToast({ title: '支付失败,请重试', icon: 'none' })
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
uni.showToast({ title: '获取支付参数失败', icon: 'none' })
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('创建订单失败:', err)
|
||||
uni.showToast({ title: err.message || '创建订单失败,请重试', icon: 'none' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-payment {
|
||||
min-height: 100vh;
|
||||
padding: 0 20px;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.status-bar-placeholder { height: 44px; }
|
||||
|
||||
// 导航
|
||||
.nav-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.nav-back {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.nav-placeholder {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
// 头部
|
||||
.payment-header {
|
||||
text-align: center;
|
||||
padding: 10px 0 20px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
margin-top: 6px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
// 方案选择
|
||||
.plan-selector {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.plan-tag {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 14px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.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-name {
|
||||
font-size: 18px;
|
||||
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;
|
||||
padding: 2px 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.plan-desc {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 4px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
// 权益
|
||||
.benefits {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
border-radius: 14px;
|
||||
padding: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.benefit-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.benefit-item:not(:last-child) {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
.b-icon { font-size: 16px; }
|
||||
|
||||
.b-text {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
// 套餐时长
|
||||
.duration-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.duration-title {
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.duration-list {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.duration-item {
|
||||
flex: 1;
|
||||
padding: 12px 8px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.duration-item.active {
|
||||
background: rgba(124, 77, 255, 0.12);
|
||||
border-color: rgba(124, 77, 255, 0.3);
|
||||
}
|
||||
|
||||
.d-label {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.d-price {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--dim1-color);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.d-save {
|
||||
font-size: 10px;
|
||||
color: #4db6ac;
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
// 支付
|
||||
.pay-section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.pay-btn {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background: linear-gradient(135deg, var(--dim1-color), var(--dim3-color));
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 4px 16px rgba(179, 136, 255, 0.3);
|
||||
}
|
||||
|
||||
.pay-agreement {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user