feat: 微信分享钩子全页面接入 + 订阅信息结构化映射 + JWT 错误处理加强
- client: 全部页面接入 onShareAppMessage/onShareTimeline 分享钩子 - client: login 页增强、profile 订阅展示优化 - client/stores: 订阅字段扁平结构映射为 subInfo(plan/expiresAt/startDate) - server: auth 路由 JWT 无效/过期统一返回 401 未授权响应 - server: models/tests 同步更新
This commit is contained in:
@@ -80,10 +80,35 @@
|
||||
|
||||
<view class="footer-spacer"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- H5 支付失败/备选方案 -->
|
||||
<view v-if="showH5Fallback" class="h5-fallback-mask">
|
||||
<view class="h5-fallback-content">
|
||||
<view class="fallback-icon">📱</view>
|
||||
<text class="fallback-title">请使用微信完成支付</text>
|
||||
<text class="fallback-desc">
|
||||
订单号:{{ h5OrderNo }}
|
||||
金额:¥{{ currentPrice }}
|
||||
</text>
|
||||
<view class="fallback-actions">
|
||||
<button class="fallback-btn primary" @click="retryH5Pay">
|
||||
再次跳转微信支付
|
||||
</button>
|
||||
<button class="fallback-btn secondary" @click="copyH5Url">
|
||||
复制链接到微信打开
|
||||
</button>
|
||||
<button class="fallback-btn text-btn" @click="closeH5Fallback">
|
||||
稍后再说
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
|
||||
import { ref, computed } from 'vue'
|
||||
import { paymentApi } from '@/utils/api'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
@@ -102,6 +127,21 @@ const currentPrice = computed(() => PLAN_MAP[selectedPlan.value].price)
|
||||
const currentProductId = computed(() => PLAN_MAP[selectedPlan.value].productId)
|
||||
|
||||
const loading = ref(false)
|
||||
// H5 支付相关状态
|
||||
const h5PayUrl = ref('')
|
||||
const showH5Fallback = ref(false)
|
||||
const h5OrderNo = ref('')
|
||||
|
||||
// 检测是否运行在 H5 环境
|
||||
function isH5() {
|
||||
// #ifdef H5
|
||||
return true
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
return false
|
||||
// #endif
|
||||
return false
|
||||
}
|
||||
|
||||
async function handlePay() {
|
||||
if (!userStore.isLoggedIn) {
|
||||
@@ -119,6 +159,39 @@ async function handlePay() {
|
||||
const amount = plan.price
|
||||
|
||||
loading.value = true
|
||||
|
||||
// ===== H5 环境:使用微信支付 H5 跳转链接 =====
|
||||
if (isH5()) {
|
||||
try {
|
||||
const data = await paymentApi.createH5Order(productId, window.location.hostname, userStore.token)
|
||||
h5OrderNo.value = data.orderNo
|
||||
|
||||
if (data.h5Url) {
|
||||
h5PayUrl.value = data.h5Url
|
||||
showH5Fallback.value = false
|
||||
|
||||
// 尝试直接跳转微信支付页面
|
||||
// #ifdef H5
|
||||
window.location.href = data.h5Url
|
||||
// #endif
|
||||
|
||||
// H5 环境下跳转后不会返回,这里设置一个超时提示
|
||||
setTimeout(() => {
|
||||
showH5Fallback.value = true
|
||||
}, 5000)
|
||||
} else {
|
||||
uni.showToast({ title: '获取支付链接失败', icon: 'none' })
|
||||
loading.value = false
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('H5创建订单失败:', err)
|
||||
uni.showToast({ title: err.message || '创建订单失败,请重试', icon: 'none' })
|
||||
loading.value = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ===== 微信小程序环境:使用原生支付 =====
|
||||
try {
|
||||
const data = await paymentApi.createOrder(productId, userStore.token)
|
||||
const payParam = data.payParams || data.wechatParam || data.paymentData
|
||||
@@ -167,15 +240,52 @@ async function handlePay() {
|
||||
}
|
||||
}
|
||||
|
||||
/** H5 支付:复制链接到微信打开 */
|
||||
function copyH5Url() {
|
||||
// #ifdef H5
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(h5PayUrl.value).then(() => {
|
||||
uni.showToast({ title: '链接已复制,请到微信中打开', icon: 'none' })
|
||||
}).catch(() => {
|
||||
// fallback
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = h5PayUrl.value
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
uni.showToast({ title: '链接已复制,请到微信中打开', icon: 'none' })
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
/** H5 支付:再次跳转 */
|
||||
function retryH5Pay() {
|
||||
if (h5PayUrl.value) {
|
||||
// #ifdef H5
|
||||
window.location.href = h5PayUrl.value
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
|
||||
/** H5 支付:关闭窗口返回 */
|
||||
function closeH5Fallback() {
|
||||
showH5Fallback.value = false
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
// 微信小程序分享
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareAppMessage(() => ({
|
||||
title: 'AI 维度 - 你的AI知识助手',
|
||||
path: '/pages/index/index'
|
||||
}))
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -384,4 +494,86 @@ onShareAppMessage(() => ({
|
||||
}
|
||||
|
||||
.footer-spacer { height: 40px; }
|
||||
|
||||
// ===== H5 支付备选方案 =====
|
||||
.h5-fallback-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.h5-fallback-content {
|
||||
background: var(--bg-primary);
|
||||
border-radius: 20px;
|
||||
padding: 32px 24px;
|
||||
width: 85%;
|
||||
max-width: 380px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.fallback-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.fallback-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.fallback-desc {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.8;
|
||||
margin-bottom: 8px;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.fallback-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.fallback-btn {
|
||||
padding: 12px 20px;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&.primary {
|
||||
background: linear-gradient(135deg, #07C160, #06AE56);
|
||||
color: white;
|
||||
box-shadow: 0 4px 16px rgba(7, 193, 96, 0.3);
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
background: var(--glass-bg);
|
||||
border: 1.5px solid var(--glass-border);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
&.text-btn {
|
||||
background: transparent;
|
||||
color: var(--text-tertiary);
|
||||
font-size: 13px;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user