Initial commit of akmon project
This commit is contained in:
110
pages/mall/consumer/subscription/plan-list.uvue
Normal file
110
pages/mall/consumer/subscription/plan-list.uvue
Normal file
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<view class="sub-plan-list">
|
||||
<view class="header">
|
||||
<text class="title">软件订阅</text>
|
||||
</view>
|
||||
|
||||
<view class="plan-container" v-if="!loading && plans.length > 0">
|
||||
<view class="plan-card" v-for="p in plans" :key="p['id']" @click="goPlanDetail(p)">
|
||||
<view class="plan-header">
|
||||
<text class="plan-name">{{ p['name'] }}</text>
|
||||
<text v-if="p['billing_period'] === 'yearly'" class="badge">年付优惠</text>
|
||||
</view>
|
||||
<text class="plan-desc">{{ p['description'] || '适用于大部分使用场景' }}</text>
|
||||
<view class="price-row">
|
||||
<text class="price">¥{{ p['price'] }}</text>
|
||||
<text class="period">/{{ p['billing_period'] === 'yearly' ? '年' : '月' }}</text>
|
||||
</view>
|
||||
<view class="feature-list">
|
||||
<text class="feature-item" v-for="(v,k) in toFeatureArray(p['features'])" :key="k">• {{ v }}</text>
|
||||
</view>
|
||||
<view class="actions">
|
||||
<button class="primary" @click.stop="toCheckout(p)">立即订阅</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="!loading && plans.length === 0" class="empty">
|
||||
<text>暂无可用订阅方案</text>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="loading"><text>加载中...</text></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import supaClient from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
const loading = ref<boolean>(true)
|
||||
const plans = ref<Array<UTSJSONObject>>([])
|
||||
|
||||
const toFeatureArray = (features: any): Array<string> => {
|
||||
const arr: Array<string> = []
|
||||
if (features == null) return arr
|
||||
if (features instanceof UTSJSONObject) {
|
||||
const keys = Object.keys(features as any)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const k = keys[i]
|
||||
const v = (features as UTSJSONObject)[k]
|
||||
const vs = typeof v === 'string' ? v : JSON.stringify(v)
|
||||
arr.push(vs)
|
||||
}
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
const loadPlans = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await supaClient
|
||||
.from('ml_subscription_plans')
|
||||
.select('*', {})
|
||||
.eq('is_active', true)
|
||||
.order('sort_order', { ascending: true })
|
||||
.execute()
|
||||
if (Array.isArray(res.data)) {
|
||||
plans.value = res.data as Array<UTSJSONObject>
|
||||
} else {
|
||||
plans.value = []
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载订阅方案失败:', e)
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const goPlanDetail = (p: UTSJSONObject) => {
|
||||
const id = (p['id'] ?? '') as string
|
||||
uni.navigateTo({ url: `/pages/mall/consumer/subscription/plan-detail?id=${id}` })
|
||||
}
|
||||
|
||||
const toCheckout = (p: UTSJSONObject) => {
|
||||
const id = (p['id'] ?? '') as string
|
||||
uni.navigateTo({ url: `/pages/mall/consumer/subscription/subscribe-checkout?planId=${id}` })
|
||||
}
|
||||
|
||||
onMounted(loadPlans)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sub-plan-list { padding: 12px; }
|
||||
.header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
|
||||
.title { font-size: 18px; font-weight: 600; }
|
||||
.plan-container { display: flex; flex-direction: column; gap: 12px; }
|
||||
.plan-card { background: #fff; border-radius: 10px; padding: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
|
||||
.plan-header { display: flex; align-items: center; justify-content: space-between; }
|
||||
.plan-name { font-size: 16px; font-weight: 600; }
|
||||
.badge { font-size: 12px; color: #fff; background: #3cc51f; border-radius: 999px; padding: 2px 8px; }
|
||||
.plan-desc { color: #666; margin: 6px 0; line-height: 1.5; }
|
||||
.price-row { display: flex; align-items: baseline; gap: 4px; margin: 6px 0; }
|
||||
.price { font-size: 22px; color: #ff4d4f; font-weight: 700; }
|
||||
.period { color: #999; }
|
||||
.feature-list { color: #444; display: flex; flex-direction: column; gap: 2px; margin: 6px 0; }
|
||||
.feature-item { font-size: 12px; color: #555; }
|
||||
.actions { display: flex; justify-content: flex-end; margin-top: 8px; }
|
||||
.primary { background: #3cc51f; color: #fff; border-radius: 6px; padding: 8px 12px; }
|
||||
.loading, .empty { padding: 24px; text-align: center; color: #888; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user