Files
trade-assistant/user-frontend/src/views/Upgrade.vue
T
TradeMate Dev d2736d1ef6 feat: AI routing DB-driven, payment gateway full integration, WeChat mini-program CI/CD
- AI routing rules now stored in system_configs DB table instead of hardcoded config
- Multi-model support via name|model composite key for same-provider routing
- UnifiedPayService with HMAC-SHA256 gateway integration (alipay/wechat)
- Admin payment panel: list, stats, search, filter, refund
- WeChat mini-program CI/CD via miniprogram-ci (v1.0.9)
- Translation quota extended to LLM provider tier
- SearchService with DB-driven provider config (bing/google_cse/searxng)
- Footer cleanup across admin/workspace/uni-app
- Private key excluded from git tracking
2026-06-09 17:19:45 +08:00

138 lines
7.4 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>
<div>
<el-row :gutter="20">
<el-col :xs="24" :sm="8" v-for="p in plans" :key="p.id">
<el-card shadow="hover" :class="{ 'plan-highlight': p.id === currentPlan, 'plan-yearly': p.period === 'year' }">
<template #header>
<div style="text-align:center">
<el-tag v-if="p.period === 'year'" type="success" size="small" style="margin-bottom:8px">年付省 {{ (p.original_price || p.price * 12) - p.price }} </el-tag>
<h3 style="margin:0">{{ p.name }}</h3>
<p style="font-size:28px;font-weight:700;color:#1890ff;margin:12px 0">
¥{{ p.price }}<span style="font-size:14px;font-weight:400;color:#999">/{{ p.period === 'year' ? '年' : '月' }}</span>
</p>
<p v-if="p.original_price" style="font-size:12px;color:#999;margin:-8px 0 0">
<del>¥{{ p.original_price }}/</del>{{ Math.round((1 - p.price / p.original_price) * 100) }}% 优惠
</p>
</div>
</template>
<div>
<p v-for="f in p.features || []" :key="f" style="font-size:13px;color:#666;margin:8px 0">
<el-icon color="#52c41a" style="margin-right:6px"><Check /></el-icon>{{ f }}
</p>
</div>
<div style="text-align:center;margin-top:16px">
<el-button v-if="p.id === currentPlan" type="default" disabled>当前套餐</el-button>
<el-button v-else-if="p.id === 'free'" @click="handleFree">当前套餐</el-button>
<el-button v-else type="primary" :loading="loadingId === p.id" @click="showPayDialog(p.id)">{{ p.price === 0 ? '当前套餐' : '升级' }}</el-button>
</div>
</el-card>
</el-col>
</el-row>
<el-empty v-if="!plans.length" description="暂无套餐信息" />
<el-dialog v-model="payDialog.visible" title="选择支付方式" width="400px" :close-on-click-modal="false">
<div style="text-align:center;padding:20px 0" v-if="!payDialog.orderCreated">
<el-radio-group v-model="payDialog.payType" style="margin-bottom:24px">
<el-radio-button value="alipay">
<span style="display:flex;align-items:center;gap:6px;padding:0 20px">
<svg viewBox="0 0 24 24" width="20" height="20" fill="#1677ff"><path d="M21.422 15.358c-3.22-1.386-6.847-2.408-10.564-2.828 1.102-2.279 2.38-4.49 3.735-6.59H9.878c-.185-.413-.262-.912-.04-1.436.454-1.072 1.92-1.348 1.92-1.348s.162-.09.026-.207c-.137-.117-1.866-.313-2.666-.363-2.348-.155-4.99.22-5.733 1.181-1.14 1.48.067 2.925.401 3.337.337.412 1.256.498 1.256.498s-1.466.536-1.992 1.2c-.525.665-.264 1.383.13 1.664.394.281.756.388 1.07.482.707.21 1.818.431 2.795.555 1.454.184 2.957.1 4.312-.184 1.408-2.06 2.83-4.017 4.285-5.907l3.192 1.558c.289.142.66.028.827-.256a.63.63 0 0 0-.086-.74L15.734 7.56c.7-.878 1.426-1.727 2.18-2.537 1.938-2.083 4.298-3.876 6.377-4.707a12.29 12.29 0 0 0-6.648-1.99c-6.427 0-11.66 4.996-11.66 11.116 0 1.49.294 2.913.825 4.215-.374.314-.707.674-.99 1.075-2.316 3.277-.477 6.101 1.046 7.247 1.518 1.144 4.464 1.772 7.155.875 2.798-.93 5.256-3.103 6.822-5.531 1.654-2.563 2.549-5.435 2.549-8.367a12.9 12.9 0 0 0-.316-2.81c-1.178-.022-3.226.306-5.354 1.522z"/></svg>
支付宝
</span>
</el-radio-button>
<el-radio-button value="wechat">
<span style="display:flex;align-items:center;gap:6px;padding:0 20px">
<svg viewBox="0 0 24 24" width="20" height="20" fill="#07c160"><path d="M8.691 2.188C3.891 2.188 0 5.476 0 9.53c0 2.212 1.17 4.203 3.002 5.55a.59.59 0 0 1 .213.665l-.39 1.48c-.019.07-.048.141-.048.213 0 .163.13.295.29.295a.326.326 0 0 0 .167-.054l1.903-1.114a.864.864 0 0 1 .717-.098 10.16 10.16 0 0 0 2.837.403c.276 0 .543-.027.811-.05-.857-2.578.157-4.972 1.932-6.446 1.703-1.415 3.882-1.98 5.853-1.838-.576-3.583-4.196-6.348-8.596-6.348zM5.785 5.991c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178A1.17 1.17 0 0 1 4.623 7.17c0-.651.52-1.18 1.162-1.18zm5.813 0c.642 0 1.162.529 1.162 1.18a1.17 1.17 0 0 1-1.162 1.178 1.17 1.17 0 0 1-1.162-1.178c0-.651.52-1.18 1.162-1.18zm5.34 2.867c-1.797-.052-3.746.512-5.28 1.786-1.72 1.428-2.687 3.72-1.78 6.22.942 2.453 3.666 4.229 6.884 4.229.826 0 1.622-.12 2.361-.336a.722.722 0 0 1 .598.082l1.584.926a.271.271 0 0 0 .14.045c.134 0 .24-.11.24-.245 0-.06-.024-.12-.04-.178l-.325-1.233a.49.49 0 0 1 .178-.553C23.028 18.125 24 16.539 24 14.711c0-3.396-3.637-6.02-7.062-5.853zm-2.06 1.964c.535 0 .968.44.968.982a.975.975 0 0 1-.968.983.975.975 0 0 1-.969-.983c0-.542.434-.982.969-.982zm4.844 0c.535 0 .969.44.969.982a.975.975 0 0 1-.969.983.975.975 0 0 1-.968-.983c0-.542.433-.982.968-.982z"/></svg>
微信支付
</span>
</el-radio-button>
</el-radio-group>
<div>
<el-button type="primary" size="large" :loading="payDialog.loading" @click="handleUpgrade">立即支付</el-button>
</div>
</div>
<div style="text-align:center;padding:20px 0" v-else>
<div v-if="payDialog.codeUrl">
<p style="margin-bottom:16px;color:#666">请使用微信扫描下方二维码支付</p>
<img :src="payDialog.codeUrl" style="width:200px;height:200px;border:1px solid #eee;border-radius:8px" />
<p style="margin-top:12px;font-size:12px;color:#999">支付成功后自动生效</p>
</div>
<div v-else-if="payDialog.payUrl">
<p style="margin-bottom:16px;color:#666">正在跳转支付宝...</p>
<el-button type="primary" @click="openPayUrl">前往支付</el-button>
</div>
<el-button style="margin-top:16px" @click="payDialog.visible = false">关闭</el-button>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { getPlans, getSubscription, createOrder } from '@/api'
import { ElMessage } from 'element-plus'
const plans = ref([])
const currentPlan = ref(null)
const loadingId = ref(null)
const payDialog = reactive({
visible: false,
planId: null,
payType: 'alipay',
loading: false,
orderCreated: false,
payUrl: '',
codeUrl: '',
})
onMounted(async () => {
try {
const [plansRes, subRes] = await Promise.all([getPlans(), getSubscription().catch(() => null)])
const pd = plansRes.data || plansRes
plans.value = (pd.plans || pd.items || pd || []).filter(p => p.id !== 'free')
if (subRes) {
const sd = subRes.data || subRes
currentPlan.value = sd.plan_id || sd.plan
}
} catch { /* ignore */ }
})
function showPayDialog(planId) {
payDialog.planId = planId
payDialog.payType = 'alipay'
payDialog.orderCreated = false
payDialog.payUrl = ''
payDialog.codeUrl = ''
payDialog.visible = true
}
async function handleUpgrade() {
payDialog.loading = true
try {
const res = await createOrder(payDialog.planId, payDialog.payType)
payDialog.orderCreated = true
if (res.code_url) {
payDialog.codeUrl = res.code_url
} else if (res.pay_url) {
payDialog.payUrl = res.pay_url
window.open(res.pay_url)
} else {
ElMessage.success('订单已创建,请稍后查看')
}
} catch (e) {
ElMessage.error(e?.detail || '下单失败')
} finally {
payDialog.loading = false
}
}
function openPayUrl() {
if (payDialog.payUrl) window.open(payDialog.payUrl)
}
</script>
<style scoped>
.plan-highlight { border: 2px solid #1890ff; transform: scale(1.02); }
.plan-yearly { border: 2px solid #52c41a; }
</style>