|
|
@@ -46,11 +46,26 @@
|
|
|
<text class="price">{{ formatYuan(currentPrice) }}</text>
|
|
|
<text class="per">/年</text>
|
|
|
</view>
|
|
|
- <view v-if="pricing.isSeedPrice" class="seed-badge">
|
|
|
- 种子价 · 仅剩 {{ pricing.remainingSeats }}/{{ pricing.seedLimit }} 个名额
|
|
|
- </view>
|
|
|
- <text v-else class="standard-label">标准定价</text>
|
|
|
- <text class="price-daily">每天仅 ¥{{ (currentPrice / 100 / 365).toFixed(2) }}</text>
|
|
|
+ <view v-if="pricing.isSeedPrice" class="seed-badge">
|
|
|
+ 种子价 · 仅剩 {{ pricing.remainingSeats }}/{{ pricing.seedLimit }} 个名额
|
|
|
+ </view>
|
|
|
+ <text v-else class="standard-label">标准定价</text>
|
|
|
+ <!-- US-4.4 Upgrade breakdown -->
|
|
|
+ <view v-if="isUpgrade" class="upgrade-info">
|
|
|
+ <view class="upgrade-row">
|
|
|
+ <text class="upgrade-label">原年费金额</text>
|
|
|
+ <text class="upgrade-value">¥{{ formatYuan(pricing.originalPaidAmount) }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="upgrade-row">
|
|
|
+ <text class="upgrade-label">年费剩余价值</text>
|
|
|
+ <text class="upgrade-value minus">-¥{{ formatYuan(pricing.remainingValue) }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="upgrade-row total">
|
|
|
+ <text class="upgrade-label">应付差价</text>
|
|
|
+ <text class="upgrade-value highlight">¥{{ formatYuan(pricing.upgradePrice) }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <text class="price-daily">每天仅 ¥{{ (currentPrice / 100 / 365).toFixed(2) }}</text>
|
|
|
</view>
|
|
|
<view class="plan-features">
|
|
|
<text class="feature">✦ 包含 C 端全部权益</text>
|
|
|
@@ -72,11 +87,15 @@
|
|
|
|
|
|
<script setup>
|
|
|
import { ref, computed, onMounted } from 'vue'
|
|
|
+import { useUserStore } from '@/stores/user'
|
|
|
import { payApi, pricingApi } from '@/utils/api'
|
|
|
|
|
|
+const userStore = useUserStore()
|
|
|
+
|
|
|
const paying = ref(false)
|
|
|
const loading = ref(true)
|
|
|
const product = ref('annual')
|
|
|
+const isUpgrade = ref(false)
|
|
|
const pricing = ref({
|
|
|
seedPrice: 131400,
|
|
|
standardPrice: 198600,
|
|
|
@@ -95,9 +114,11 @@ const pageDesc = computed(() => {
|
|
|
|
|
|
const currentPrice = computed(() => {
|
|
|
if (product.value === 'annual') return pricing.value.annualFee
|
|
|
+ if (isUpgrade.value) return pricing.value.upgradePrice
|
|
|
return pricing.value.isSeedPrice ? pricing.value.seedPrice : pricing.value.standardPrice
|
|
|
})
|
|
|
const priceLabel = computed(() => {
|
|
|
+ if (isUpgrade.value) return '升级价'
|
|
|
return pricing.value.isSeedPrice ? '种子价' : '标准价'
|
|
|
})
|
|
|
|
|
|
@@ -107,17 +128,38 @@ function formatYuan(cents) {
|
|
|
}
|
|
|
|
|
|
onMounted(async () => {
|
|
|
+ // Ensure user profile is loaded (vipType, etc.)
|
|
|
+ try {
|
|
|
+ await userStore.fetchProfile()
|
|
|
+ } catch (e) {
|
|
|
+ // ignore, use cached
|
|
|
+ }
|
|
|
+
|
|
|
// Read product param from URL
|
|
|
const pages = getCurrentPages()
|
|
|
const page = pages[pages.length - 1]
|
|
|
const params = page?.options || {}
|
|
|
product.value = params.product || 'annual'
|
|
|
|
|
|
- try {
|
|
|
- const res = await pricingApi.current()
|
|
|
- if (res) pricing.value = res
|
|
|
- } catch (e) {
|
|
|
- // Use defaults if API fails
|
|
|
+ // Determine upgrade scenario: annual member upgrading to practitioner
|
|
|
+ if (product.value === 'practitioner' && userStore.vipType === 'annual') {
|
|
|
+ isUpgrade.value = true
|
|
|
+ try {
|
|
|
+ const res = await pricingApi.upgrade()
|
|
|
+ if (res) pricing.value = { ...pricing.value, ...res }
|
|
|
+ } catch (e) {
|
|
|
+ // Fallback to normal pricing on error
|
|
|
+ isUpgrade.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!isUpgrade.value) {
|
|
|
+ try {
|
|
|
+ const res = await pricingApi.current()
|
|
|
+ if (res) pricing.value = res
|
|
|
+ } catch (e) {
|
|
|
+ // Use defaults if API fails
|
|
|
+ }
|
|
|
}
|
|
|
loading.value = false
|
|
|
})
|
|
|
@@ -127,7 +169,12 @@ async function onPay() {
|
|
|
try {
|
|
|
const totalFee = currentPrice.value
|
|
|
const productType = product.value
|
|
|
- await payApi.create({ totalFee, payType: 'wxpay', productType })
|
|
|
+ await payApi.create({
|
|
|
+ totalFee,
|
|
|
+ payType: 'wxpay',
|
|
|
+ productType,
|
|
|
+ isUpgrade: isUpgrade.value
|
|
|
+ })
|
|
|
uni.showToast({ title: '开通成功!', icon: 'success' })
|
|
|
setTimeout(() => uni.navigateBack(), 1500)
|
|
|
} catch (e) {
|
|
|
@@ -263,6 +310,44 @@ async function onPay() {
|
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|
|
}
|
|
|
|
|
|
+/* US-4.4 Upgrade breakdown */
|
|
|
+.upgrade-info {
|
|
|
+ margin-top: 12px;
|
|
|
+ padding: 10px 12px;
|
|
|
+ background: rgba(255,255,255,0.04);
|
|
|
+ border-radius: 8px;
|
|
|
+ border: 1px dashed rgba(255,255,255,0.15);
|
|
|
+}
|
|
|
+.upgrade-row {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ font-size: 13px;
|
|
|
+ margin-bottom: 6px;
|
|
|
+}
|
|
|
+.upgrade-row:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+}
|
|
|
+.upgrade-row.total {
|
|
|
+ margin-top: 6px;
|
|
|
+ padding-top: 6px;
|
|
|
+ border-top: 1px solid rgba(255,255,255,0.1);
|
|
|
+}
|
|
|
+.upgrade-label {
|
|
|
+ color: rgba(255,255,255,0.5);
|
|
|
+}
|
|
|
+.upgrade-value {
|
|
|
+ color: rgba(255,255,255,0.9);
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+.upgrade-value.minus {
|
|
|
+ color: #fbbf24;
|
|
|
+}
|
|
|
+.upgrade-value.highlight {
|
|
|
+ color: #a78bfa;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
.plan-features {
|
|
|
margin-top: 16px;
|
|
|
}
|