Add landing page, referral system, usage quotas, search API management, and yearly pricing

- Separate workspace landing from login for better UX
- Referral system rewards both parties with Pro days
- Quota enforcement prevents abuse without breaking endpoints
- 7-day free trial with auto-downgrade on expiry
- Admin-managed search provider config (SearXNG, Bing)
- 15% discount on annual subscriptions
- MCP search server wrapping opencode search
- Fix discovery module field name mismatch causing 422
This commit is contained in:
TradeMate Dev
2026-05-26 11:40:13 +08:00
parent 52dba37f22
commit bed5c7abef
39 changed files with 1988 additions and 152 deletions
+15 -9
View File
@@ -1,24 +1,29 @@
<template>
<div>
<el-row :gutter="20">
<el-col :span="8" v-for="p in plans" :key="p.id">
<el-card shadow="hover" :class="{ 'plan-highlight': p.id === currentPlan }">
<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:#409eff;margin:12px 0">
¥{{ p.price || 0 }}<span style="font-size:14px;font-weight:400;color:#999">/</span>
<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="#67c23a" style="margin-right:6px"><Check /></el-icon>{{ f }}
<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 type="primary" :loading="loadingId === p.id" @click="upgrade(p.id)">升级</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="upgrade(p.id)">{{ p.price === 0 ? '当前套餐' : '升级' }}</el-button>
</div>
</el-card>
</el-col>
@@ -40,7 +45,7 @@ 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 || []
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
@@ -51,7 +56,7 @@ onMounted(async () => {
async function upgrade(planId) {
loadingId.value = planId
try {
const res = await createOrder(planId)
const res = await createOrder(planId, 'native')
ElMessage.success('订单已创建' + (res.pay_url ? ',正在跳转支付...' : ''))
if (res.pay_url) window.open(res.pay_url)
} catch (e) { ElMessage.error(e?.detail || '升级失败') }
@@ -60,5 +65,6 @@ async function upgrade(planId) {
</script>
<style scoped>
.plan-highlight { border: 2px solid #409eff; transform: scale(1.02); }
.plan-highlight { border: 2px solid #1890ff; transform: scale(1.02); }
.plan-yearly { border: 2px solid #52c41a; }
</style>