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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user