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:
@@ -76,6 +76,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { userApi } from '@/utils/api'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
@@ -113,8 +115,7 @@ const progressPercent = computed(() => {
|
||||
|
||||
onMounted(() => {
|
||||
if (!userStore.isLoggedIn) {
|
||||
uni.showToast({ title: '请先登录', icon: 'none' })
|
||||
uni.navigateTo({ url: '/pages/login/login' })
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
loadAchievements()
|
||||
@@ -158,10 +159,12 @@ function formatDate(dateStr) {
|
||||
}
|
||||
|
||||
// 微信小程序分享
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareAppMessage(() => ({
|
||||
title: 'AI 维度 - 你的AI知识助手',
|
||||
path: '/pages/index/index'
|
||||
}))
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -90,6 +90,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const type = ref('terms')
|
||||
@@ -114,10 +116,12 @@ function onLoad(fn) {
|
||||
}
|
||||
|
||||
// 微信小程序分享
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareAppMessage(() => ({
|
||||
title: 'AI 维度 - 你的AI知识助手',
|
||||
path: '/pages/index/index'
|
||||
}))
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -78,6 +78,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
|
||||
import { ref, computed } from 'vue'
|
||||
import { aiChatApi } from '@/utils/api'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
@@ -197,10 +199,12 @@ function goShare() {
|
||||
}
|
||||
|
||||
// 微信小程序右上角三点分享
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareAppMessage(() => ({
|
||||
title: 'AI 维度 - AI 问答',
|
||||
path: '/pages/chat/chat'
|
||||
}))
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -93,6 +93,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { knowledgeApi } from '@/utils/api'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
@@ -163,7 +165,18 @@ async function fetchDetail() {
|
||||
|
||||
async function toggleCollect() {
|
||||
if (!userStore.isLoggedIn) {
|
||||
uni.navigateTo({ url: '/pages/login/login' })
|
||||
// 未登录:弹窗给用户选择,可取消,不强制跳转
|
||||
uni.showModal({
|
||||
title: '登录后收藏',
|
||||
content: '登录后即可收藏喜欢的 AI 知识',
|
||||
confirmText: '去登录',
|
||||
cancelText: '暂不',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.navigateTo({ url: '/pages/login/login' })
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
try {
|
||||
@@ -190,10 +203,12 @@ function goToAI() {
|
||||
}
|
||||
|
||||
// 微信小程序分享
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareAppMessage(() => ({
|
||||
title: 'AI 维度 - 你的AI知识助手',
|
||||
path: '/pages/index/index'
|
||||
}))
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -44,6 +44,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { getDimension } from '@/utils/dimensions'
|
||||
import { knowledgeApi } from '@/utils/api'
|
||||
@@ -80,12 +82,12 @@ async function loadKnowledge() {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await knowledgeApi.list({ dim: dimId.value })
|
||||
knowledgeList.value = data.map(item => ({
|
||||
knowledgeList.value = (data.items || []).map(item => ({
|
||||
...item,
|
||||
icon: getDimIcon(item.dim),
|
||||
desc: item.content?.substring(0, 40) + '...' || '',
|
||||
readTime: Math.ceil((item.content?.length || 300) / 200),
|
||||
isPremium: item.isPremium,
|
||||
isPremium: false,
|
||||
collected: false,
|
||||
collectCount: item.collectionCount
|
||||
}))
|
||||
@@ -166,10 +168,12 @@ function goDetail(item) {
|
||||
}
|
||||
|
||||
// 微信小程序分享
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareAppMessage(() => ({
|
||||
title: 'AI 维度 - 你的AI知识助手',
|
||||
path: '/pages/index/index'
|
||||
}))
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
<!-- 维度标签 -->
|
||||
<view
|
||||
class="dim-tag"
|
||||
:style="{ color: dimColor(item.dimensionId) }"
|
||||
:style="{ color: dimColor(item.dimension) }"
|
||||
>
|
||||
{{ dimName(item.dimensionId) }}
|
||||
{{ dimName(item.dimension) }}
|
||||
</view>
|
||||
<!-- 标题 -->
|
||||
<text class="fav-title">{{ item.title }}</text>
|
||||
@@ -35,8 +35,9 @@
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-else>
|
||||
<text class="empty-icon">⭐</text>
|
||||
<text class="empty-text">你还没有收藏任何内容</text>
|
||||
<text class="empty-sub">去首页发现有趣的 AI 知识吧</text>
|
||||
<text class="empty-text" v-if="userStore.isLoggedIn">你还没有收藏任何内容</text>
|
||||
<text class="empty-text" v-else>登录后可收藏喜欢的 AI 知识</text>
|
||||
<text class="empty-sub" v-if="userStore.isLoggedIn">去首页发现有趣的 AI 知识吧</text>
|
||||
<view class="empty-btn" @tap="goHome">
|
||||
<text>开始探索</text>
|
||||
</view>
|
||||
@@ -45,6 +46,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { knowledgeApi } from '@/utils/api'
|
||||
import { getDimension } from '@/utils/dimensions'
|
||||
@@ -56,8 +59,8 @@ const loading = ref(true)
|
||||
|
||||
onMounted(() => {
|
||||
if (!userStore.isLoggedIn) {
|
||||
uni.showToast({ title: '请先登录', icon: 'none' })
|
||||
uni.navigateTo({ url: '/pages/login/login' })
|
||||
// 未登录时显示空状态提示,不强制跳转登录
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
loadFavorites()
|
||||
@@ -114,10 +117,12 @@ function goHome() {
|
||||
}
|
||||
|
||||
// 微信小程序分享
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareAppMessage(() => ({
|
||||
title: 'AI 维度 - 你的AI知识助手',
|
||||
path: '/pages/index/index'
|
||||
}))
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -101,6 +101,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { feedbackApi } from '@/utils/api'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
@@ -228,10 +230,12 @@ function goBack() {
|
||||
}
|
||||
|
||||
// 微信小程序分享
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareAppMessage(() => ({
|
||||
title: 'AI 维度 - 你的AI知识助手',
|
||||
path: '/pages/index/index'
|
||||
}))
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -83,6 +83,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { DIMENSIONS } from '@/utils/dimensions'
|
||||
import { trendApi } from '@/utils/api'
|
||||
@@ -153,10 +155,12 @@ function goNewsDetail(item) {
|
||||
}
|
||||
|
||||
// 微信小程序分享
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareAppMessage(() => ({
|
||||
title: 'AI 维度 - 你的AI知识助手',
|
||||
path: '/pages/index/index'
|
||||
}))
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -37,20 +37,20 @@
|
||||
<!-- 头像 -->
|
||||
<view class="avatar">
|
||||
<image
|
||||
v-if="item.avatarUrl"
|
||||
:src="item.avatarUrl"
|
||||
v-if="item.avatar"
|
||||
:src="item.avatar"
|
||||
class="avatar-img"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<text v-else class="avatar-text">
|
||||
{{ (item.nickName || item.username || '用').charAt(0) }}
|
||||
{{ (item.nickname || item.username || '用').charAt(0) }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 信息 -->
|
||||
<view class="user-info">
|
||||
<text class="user-name">{{ item.nickName || item.username || '用户' }}</text>
|
||||
<text class="user-score">{{ item.score || 0 }} 分</text>
|
||||
<text class="user-name">{{ item.nickname || item.username || '用户' }}</text>
|
||||
<text class="user-score">{{ item.totalScore || 0 }} 分</text>
|
||||
</view>
|
||||
|
||||
<!-- 已解锁维度 -->
|
||||
@@ -81,6 +81,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { userApi } from '@/utils/api'
|
||||
import { getDimension } from '@/utils/dimensions'
|
||||
@@ -102,8 +104,7 @@ const dimTabs = [
|
||||
|
||||
onMounted(() => {
|
||||
if (!userStore.isLoggedIn) {
|
||||
uni.showToast({ title: '请先登录', icon: 'none' })
|
||||
uni.navigateTo({ url: '/pages/login/login' })
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
loadBoard()
|
||||
@@ -116,10 +117,10 @@ async function loadBoard() {
|
||||
const data = await userApi.leaderboard(dimParam)
|
||||
const list = Array.isArray(data) ? data : data.list || []
|
||||
// 标记当前用户
|
||||
const myId = userStore.userInfo?.id || userStore.userInfo?.userId || ''
|
||||
const myId = userStore.userInfo?.openid || ''
|
||||
board.value = list.map((item) => ({
|
||||
...item,
|
||||
isCurrentUser: String(item.userId || item.id || '') === String(myId)
|
||||
isCurrentUser: item.openid === myId
|
||||
}))
|
||||
} catch (e) {
|
||||
console.error('[leaderboard] load failed:', e)
|
||||
@@ -135,10 +136,12 @@ function dimDotColor(id) {
|
||||
}
|
||||
|
||||
// 微信小程序分享
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareAppMessage(() => ({
|
||||
title: 'AI 维度 - 你的AI知识助手',
|
||||
path: '/pages/index/index'
|
||||
}))
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -4,6 +4,14 @@
|
||||
<view class="bg-orb orb-2"></view>
|
||||
<view class="bg-orb orb-3"></view>
|
||||
|
||||
<!-- 顶部返回 -->
|
||||
<view class="nav-bar">
|
||||
<view class="nav-back" @click="goBack">
|
||||
<text class="nav-back-icon">←</text>
|
||||
<text class="nav-back-text">返回</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="login-container">
|
||||
<view class="logo-wrap">
|
||||
<view class="logo-ring"></view>
|
||||
@@ -28,6 +36,11 @@
|
||||
<input class="form-input" v-model="loginForm.password" type="password" placeholder="请输入密码" />
|
||||
</view>
|
||||
|
||||
<!-- 忘记密码 -->
|
||||
<view class="forgot-row">
|
||||
<text class="forgot-link" @click="goForgotPassword">忘记密码?</text>
|
||||
</view>
|
||||
|
||||
<!-- 登录协议勾选 -->
|
||||
<view class="agreement-row">
|
||||
<view class="agreement-checkbox" :class="{ checked: loginAgreed }" @click="loginAgreed = !loginAgreed">
|
||||
@@ -45,12 +58,14 @@
|
||||
<text>{{ loading ? '登录中...' : '登录' }}</text>
|
||||
</view>
|
||||
<!-- 微信小程序一键登录 -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="wx-login-row" v-if="isMiniApp">
|
||||
<view class="wx-login-btn" @click="wechatLogin">
|
||||
<text class="wx-icon">💬</text>
|
||||
<text>微信一键登录</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
|
||||
<!-- 注册表单 -->
|
||||
@@ -59,6 +74,10 @@
|
||||
<text class="form-label">邮箱 <text style="color:var(--color-vip);font-size:11px;">*</text></text>
|
||||
<input class="form-input" v-model="registerForm.email" type="email" placeholder="请输入邮箱地址" @input="registerForm.email = $event.detail.value.trim(); verifyCodeSent = false" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">用户名 <text style="color:var(--color-vip);font-size:11px;">*</text></text>
|
||||
<input class="form-input" v-model="registerForm.username" placeholder="请输入用户名(用于登录)" @input="registerForm.username = $event.detail.value.trim()" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-label">昵称 <text style="color:var(--text-muted);font-size:11px;">(选填)</text></text>
|
||||
<input class="form-input" v-model="registerForm.nickName" placeholder="显示名称,留空自动生成" />
|
||||
@@ -100,21 +119,30 @@
|
||||
<view class="footer-info">
|
||||
<text>© 2026 宇之然 · AI 维度</text>
|
||||
</view>
|
||||
|
||||
<!-- 显著有效的「拒绝/暂不登录」入口,避免强制登录 -->
|
||||
<view class="skip-login" @click="goBrowse">
|
||||
<text>暂不登录,先体验</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
|
||||
import { ref } from 'vue'
|
||||
import { useUserStore } from '@/stores/index'
|
||||
|
||||
const isMiniApp = ref(false)
|
||||
// #ifdef MP-WEIXIN
|
||||
try {
|
||||
const sysInfo = uni.getSystemInfoSync()
|
||||
if (sysInfo && sysInfo.version && sysInfo.platform) {
|
||||
isMiniApp.value = true
|
||||
}
|
||||
} catch (e) {}
|
||||
// #endif
|
||||
|
||||
const activeTab = ref('login')
|
||||
const loading = ref(false)
|
||||
@@ -127,7 +155,7 @@ let countdownTimer = null
|
||||
const userStore = useUserStore()
|
||||
|
||||
const loginForm = ref({ email: '', password: '' })
|
||||
const registerForm = ref({ email: '', password: '', nickName: '', verifyCode: '' })
|
||||
const registerForm = ref({ email: '', username: '', password: '', nickName: '', verifyCode: '' })
|
||||
|
||||
const BASE_URL = 'https://wdkj.yuzhiran.com.cn'
|
||||
|
||||
@@ -241,6 +269,10 @@ const handleRegister = async () => {
|
||||
uni.showToast({ title: '请输入密码', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!registerForm.value.username) {
|
||||
uni.showToast({ title: '请输入用户名', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (registerForm.value.password.length < 6) {
|
||||
uni.showToast({ title: '密码至少 6 位', icon: 'none' })
|
||||
return
|
||||
@@ -255,7 +287,8 @@ const handleRegister = async () => {
|
||||
registerForm.value.email,
|
||||
registerForm.value.password,
|
||||
registerForm.value.nickName || '',
|
||||
registerForm.value.verifyCode
|
||||
registerForm.value.verifyCode,
|
||||
registerForm.value.username
|
||||
)
|
||||
uni.showToast({ title: '注册成功', icon: 'success' })
|
||||
setTimeout(() => uni.switchTab({ url: '/pages/index/index' }), 1000)
|
||||
@@ -274,11 +307,26 @@ function openAgreement(type) {
|
||||
uni.navigateTo({ url: pages[type] || pages.terms })
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack({ fallback: () => uni.switchTab({ url: '/pages/index/index' }) })
|
||||
}
|
||||
|
||||
/** 拒绝登录:返回首页以游客身份浏览,不强制登录 */
|
||||
function goBrowse() {
|
||||
uni.switchTab({ url: '/pages/index/index' })
|
||||
}
|
||||
|
||||
function goForgotPassword() {
|
||||
uni.showToast({ title: '请联系管理员重置密码', icon: 'none' })
|
||||
}
|
||||
|
||||
// 微信小程序分享
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareAppMessage(() => ({
|
||||
title: 'AI 维度 - 你的AI知识助手',
|
||||
path: '/pages/index/index'
|
||||
}))
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -303,6 +351,19 @@ onShareAppMessage(() => ({
|
||||
.orb-2 { width: 160px; height: 160px; top: 30%; right: -40px; background: var(--dim3-color); }
|
||||
.orb-3 { width: 180px; height: 180px; bottom: -60px; left: 30%; background: var(--dim5-color); }
|
||||
|
||||
// 顶部导航栏
|
||||
.nav-bar {
|
||||
position: absolute; top: 0; left: 0; right: 0; z-index: 10;
|
||||
padding: 44px 20px 0;
|
||||
.nav-back {
|
||||
display: inline-flex; align-items: center; gap: 4px;
|
||||
padding: 8px 12px; cursor: pointer;
|
||||
&:active { opacity: 0.6; }
|
||||
}
|
||||
.nav-back-icon { font-size: 20px; color: var(--text-primary); }
|
||||
.nav-back-text { font-size: 14px; color: var(--text-secondary); }
|
||||
}
|
||||
|
||||
.login-container {
|
||||
position: relative; z-index: 1;
|
||||
width: 100%; max-width: 360px;
|
||||
@@ -354,6 +415,15 @@ onShareAppMessage(() => ({
|
||||
}
|
||||
}
|
||||
|
||||
// 忘记密码
|
||||
.forgot-row {
|
||||
display: flex; justify-content: flex-end; margin: -8px 0 0;
|
||||
.forgot-link {
|
||||
font-size: 12px; color: var(--color-brand); padding: 4px 0;
|
||||
&:active { opacity: 0.6; }
|
||||
}
|
||||
}
|
||||
|
||||
// 验证码行
|
||||
.verify-row {
|
||||
display: flex; gap: 10px;
|
||||
@@ -417,5 +487,20 @@ onShareAppMessage(() => ({
|
||||
}
|
||||
|
||||
.footer-info { margin-top: 40px; font-size: 11px; color: var(--text-muted); }
|
||||
|
||||
// 暂不登录入口(拒绝/返回,显著有效)
|
||||
.skip-login {
|
||||
margin-top: 24px;
|
||||
padding: 14px 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
&:active { opacity: 0.7; }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -90,6 +90,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { trendApi, knowledgeApi } from '@/utils/api'
|
||||
|
||||
@@ -140,9 +142,14 @@ const relatedTags = computed(() => {
|
||||
})
|
||||
|
||||
async function loadDetail() {
|
||||
const id = uni.getStorageSync('__news_id__')
|
||||
loading.value = false
|
||||
// Try API detail first, fallback to local data from trend page
|
||||
// 列表页通过 __news_item__ 传入条目(含 _id),优先拉取详情以刷新阅读量
|
||||
const raw = uni.getStorageSync('__news_item__')
|
||||
let item = null
|
||||
if (raw) {
|
||||
try { item = JSON.parse(raw) } catch (e) {}
|
||||
}
|
||||
const id = item?._id
|
||||
if (id) {
|
||||
try {
|
||||
const data = await trendApi.detail(id)
|
||||
@@ -150,17 +157,11 @@ async function loadDetail() {
|
||||
isCollected.value = data.collected || false
|
||||
return
|
||||
} catch (e) {
|
||||
// fall through to local
|
||||
// 回退到本地缓存
|
||||
}
|
||||
}
|
||||
// Fallback: read from trend page's stored item
|
||||
try {
|
||||
const raw = uni.getStorageSync('__news_item__')
|
||||
if (raw) {
|
||||
news.value = JSON.parse(raw)
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
if (item) {
|
||||
news.value = item
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +176,7 @@ function onFavorite() {
|
||||
|
||||
function onShare() {
|
||||
uni.setClipboardData({
|
||||
data: news.value.link || `https://ai-dimension.yuzhiran.com.cn/news/${uni.getStorageSync('__news_id__')}`,
|
||||
data: news.value.link || `https://ai-dimension.yuzhiran.com.cn/news/${news.value._id || ''}`,
|
||||
success: () => uni.showToast({ title: '链接已复制', icon: 'none' })
|
||||
})
|
||||
}
|
||||
@@ -199,10 +200,12 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
// 微信小程序分享
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareAppMessage(() => ({
|
||||
title: 'AI 维度 - 你的AI知识助手',
|
||||
path: '/pages/index/index'
|
||||
}))
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -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>
|
||||
@@ -95,11 +95,17 @@
|
||||
</view>
|
||||
|
||||
<!-- 登录/退出 -->
|
||||
<view class="action-btn" v-if="!userStore.isLoggedIn" @click="goLogin">
|
||||
<text>微信一键登录</text>
|
||||
<view class="action-row" v-if="!userStore.isLoggedIn">
|
||||
<view class="action-btn primary" @click="goLogin">
|
||||
<text>微信一键登录</text>
|
||||
</view>
|
||||
<!-- 未登录:提供显著有效的「拒绝/暂不登录」入口,避免强制登录 -->
|
||||
<view class="action-btn ghost" @click="goBrowse">
|
||||
<text>暂不登录,先体验</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="action-btn logout" v-else @click="handleLogout">
|
||||
<text>退出登录</text>
|
||||
<text>退出登录</text>
|
||||
</view>
|
||||
|
||||
<text class="version">宇之然 AI 维度 v1.0.0</text>
|
||||
@@ -107,6 +113,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { knowledgeApi, aiChatApi } from '@/utils/api'
|
||||
@@ -207,6 +215,11 @@ function goLogin() {
|
||||
uni.navigateTo({ url: '/pages/login/login' })
|
||||
}
|
||||
|
||||
/** 仅浏览:未登录用户可跳过登录直接使用首页 */
|
||||
function goBrowse() {
|
||||
uni.switchTab({ url: '/pages/index/index' })
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
uni.showModal({
|
||||
title: '退出登录',
|
||||
@@ -276,10 +289,12 @@ function goSettings() {
|
||||
}
|
||||
|
||||
// 微信小程序分享
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareAppMessage(() => ({
|
||||
title: 'AI 维度 - 你的AI知识助手',
|
||||
path: '/pages/index/index'
|
||||
}))
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -510,17 +525,39 @@ onShareAppMessage(() => ({
|
||||
}
|
||||
|
||||
// 按钮
|
||||
.action-row {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
text-align: center;
|
||||
padding: 16px 0;
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
color: var(--dim1-color);
|
||||
border-top: 1px solid var(--glass-border);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.action-btn.primary {
|
||||
color: white;
|
||||
background: linear-gradient(135deg, #7c4dff, #b388ff);
|
||||
box-shadow: 0 0 20px rgba(124, 77, 255, 0.2);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.action-btn.ghost {
|
||||
color: var(--text-secondary);
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
.action-btn.logout {
|
||||
margin-top: 20px;
|
||||
color: var(--text-muted);
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.version {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</view>
|
||||
<view class="stats-row">
|
||||
<view class="stat-item">
|
||||
<text class="stat-value">{{ stats?.totalPoints || 0 }}</text>
|
||||
<text class="stat-value">{{ stats?.totalScore || 0 }}</text>
|
||||
<text class="stat-label">累计积分</text>
|
||||
</view>
|
||||
<view class="stat-divider"></view>
|
||||
@@ -122,6 +122,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { knowledgeApi, userApi } from '@/utils/api'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
@@ -134,7 +136,7 @@ const loading = ref(true)
|
||||
|
||||
// 学习时长(小时)
|
||||
const studyHours = computed(() => {
|
||||
const hours = stats.value?.studyHours || 0
|
||||
const hours = stats.value?.totalPlayTime || 0
|
||||
if (hours < 1) return `${Math.round(hours * 60)}分`
|
||||
return hours.toFixed(1) + 'h'
|
||||
})
|
||||
@@ -156,7 +158,7 @@ function initDimData() {
|
||||
color: dim.color,
|
||||
bg: dim.bg,
|
||||
total: 0,
|
||||
done: exploreData[dim.id] || 0,
|
||||
done: exploreData['dim' + dim.id]?.completed ? 1 : 0,
|
||||
percent: 0,
|
||||
expanded: false
|
||||
}))
|
||||
@@ -167,11 +169,7 @@ async function fetchDimTotals() {
|
||||
for (const dim of dimProgress.value) {
|
||||
try {
|
||||
const data = await knowledgeApi.list({ dim: dim.id, limit: 1 })
|
||||
dim.total = data?.total || data?.length || (Array.isArray(data) ? data.length : 0)
|
||||
// 如果 list 返回的是数组而不是带 total 的对象,直接用数组长度
|
||||
if (Array.isArray(data) && dim.total === 0) {
|
||||
dim.total = data.length
|
||||
}
|
||||
dim.total = data?.pagination?.total || data?.items?.length || 0
|
||||
// 计算百分比
|
||||
if (dim.total > 0) {
|
||||
dim.percent = Math.round((dim.done / dim.total) * 100)
|
||||
@@ -200,10 +198,10 @@ async function loadData() {
|
||||
userApi.progress(userStore.token).catch(() => ({ exploreData: {} }))
|
||||
])
|
||||
|
||||
// 合并 exploreData
|
||||
// 合并 exploreData(进度接口返回 {isNewUser, data: user},exploreData 在 data 下)
|
||||
stats.value = {
|
||||
...statsData,
|
||||
exploreData: { ...statsData?.exploreData, ...progressData?.exploreData }
|
||||
exploreData: progressData?.data?.exploreData || {}
|
||||
}
|
||||
|
||||
dimProgress.value = initDimData()
|
||||
@@ -225,10 +223,12 @@ function goBack() {
|
||||
}
|
||||
|
||||
// 微信小程序分享
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareAppMessage(() => ({
|
||||
title: 'AI 维度 - 你的AI知识助手',
|
||||
path: '/pages/index/index'
|
||||
}))
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -64,8 +64,8 @@
|
||||
<text class="result-title">{{ item.title }}</text>
|
||||
|
||||
<!-- 摘要 -->
|
||||
<text class="result-summary" v-if="item.summary">
|
||||
{{ item.summary }}
|
||||
<text class="result-summary" v-if="item.content">
|
||||
{{ item.content }}
|
||||
</text>
|
||||
|
||||
<!-- 标签与分类 -->
|
||||
@@ -85,6 +85,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { knowledgeApi } from '@/utils/api'
|
||||
import { DIMENSIONS } from '@/utils/dimensions'
|
||||
@@ -188,10 +190,12 @@ onUnmounted(() => {
|
||||
})
|
||||
|
||||
// 微信小程序分享
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareAppMessage(() => ({
|
||||
title: 'AI 维度 - 你的AI知识助手',
|
||||
path: '/pages/index/index'
|
||||
}))
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<view class="page-settings page-layout">
|
||||
<view class="status-bar-placeholder"></view>
|
||||
<text class="page-title">设置</text>
|
||||
<view class="nav-bar">
|
||||
<view class="nav-back" @click="goBack">‹</view>
|
||||
<text class="page-title">设置</text>
|
||||
<view class="nav-placeholder"></view>
|
||||
</view>
|
||||
|
||||
<!-- 偏好设置 -->
|
||||
<view class="section-title">偏好设置</view>
|
||||
@@ -60,6 +64,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
const pushOn = ref(true)
|
||||
@@ -131,18 +137,20 @@ function showContact() {
|
||||
}
|
||||
|
||||
function showAgreement() {
|
||||
uni.showModal({
|
||||
title: '用户协议 / 隐私政策',
|
||||
content: '暂无内容',
|
||||
showCancel: false
|
||||
})
|
||||
uni.navigateTo({ url: '/pages/agreement/agreement?type=terms' })
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
// 微信小程序分享
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareAppMessage(() => ({
|
||||
title: 'AI 维度 - 你的AI知识助手',
|
||||
path: '/pages/index/index'
|
||||
}))
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -152,6 +160,24 @@ onShareAppMessage(() => ({
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.nav-back {
|
||||
font-size: 28px;
|
||||
color: var(--text-primary);
|
||||
padding: 0 8px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.nav-placeholder {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.status-bar-placeholder { height: 44px; }
|
||||
|
||||
.page-title {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<view class="page-trend page-layout">
|
||||
<view class="page-trend">
|
||||
<view class="status-bar-placeholder"></view>
|
||||
|
||||
<!-- 顶部 -->
|
||||
@@ -17,7 +17,8 @@
|
||||
:key="idx"
|
||||
@click="activeFilter = idx"
|
||||
>
|
||||
<text>{{ tag }}</text>
|
||||
<text class="filter-icon">{{ tag.icon }}</text>
|
||||
<text class="filter-label">{{ tag.text }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -64,10 +65,18 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { trendApi } from '@/utils/api'
|
||||
|
||||
const filters = ref(['全部', '🚀 产品', '📄 论文', '🔧 工具', '🏢 公司'])
|
||||
const filters = ref([
|
||||
{ icon: '📰', text: '全部' },
|
||||
{ icon: '🚀', text: '产品' },
|
||||
{ icon: '📄', text: '论文' },
|
||||
{ icon: '🔧', text: '工具' },
|
||||
{ icon: '🏢', text: '公司' }
|
||||
])
|
||||
const activeFilter = ref(0)
|
||||
const items = ref([])
|
||||
const loading = ref(false)
|
||||
@@ -78,7 +87,7 @@ onMounted(() => {
|
||||
|
||||
const displayItems = computed(() => {
|
||||
if (activeFilter.value === 0) return items.value
|
||||
const categoryMap = { 1: 'product', 2: 'paper', 3: 'tool', 4: 'company' }
|
||||
const categoryMap = { 1: '产品', 2: '论文', 3: '工具', 4: '公司' }
|
||||
const category = categoryMap[activeFilter.value]
|
||||
if (!category) return items.value
|
||||
return items.value.filter(item => item.category === category)
|
||||
@@ -101,19 +110,19 @@ async function fetchTrends() {
|
||||
function getTagText(item) {
|
||||
if (item.hot) return '🔥 热门'
|
||||
if (item.picked) return '📌 精选'
|
||||
if (item.category === 'product') return '🚀 产品'
|
||||
if (item.category === 'paper') return '📄 论文'
|
||||
if (item.category === 'tool') return '🔧 工具'
|
||||
if (item.category === 'company') return '🏢 公司'
|
||||
if (item.category === '产品') return '🚀 产品'
|
||||
if (item.category === '论文') return '📄 论文'
|
||||
if (item.category === '工具') return '🔧 工具'
|
||||
if (item.category === '公司') return '🏢 公司'
|
||||
return '📰 资讯'
|
||||
}
|
||||
|
||||
function getTagClass(item) {
|
||||
if (item.hot) return 'hot'
|
||||
if (item.picked) return 'pick'
|
||||
if (item.category === 'product') return 'product'
|
||||
if (item.category === 'paper') return 'paper'
|
||||
if (item.category === 'tool') return 'tool'
|
||||
if (item.category === '产品') return 'product'
|
||||
if (item.category === '论文') return 'paper'
|
||||
if (item.category === '工具') return 'tool'
|
||||
return ''
|
||||
}
|
||||
|
||||
@@ -181,10 +190,12 @@ function getSampleData() {
|
||||
}
|
||||
|
||||
// 微信小程序分享
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareAppMessage(() => ({
|
||||
title: 'AI 维度 - 你的AI知识助手',
|
||||
path: '/pages/index/index'
|
||||
}))
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -225,12 +236,27 @@ onShareAppMessage(() => ({
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
padding: 6px 14px;
|
||||
padding: 8px 14px 6px;
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 16px;
|
||||
border-radius: 12px;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
min-width: 64px;
|
||||
}
|
||||
|
||||
.filter-icon {
|
||||
font-size: 16px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 11px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.filter-item.active {
|
||||
@@ -245,7 +271,7 @@ onShareAppMessage(() => ({
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding: 0 0 16px;
|
||||
padding: 0 20px 16px;
|
||||
}
|
||||
|
||||
.trend-card {
|
||||
|
||||
@@ -36,18 +36,33 @@ export const useUserStore = defineStore('user', () => {
|
||||
return data
|
||||
}
|
||||
|
||||
async function h5Register(email, password, nickName, verifyCode) {
|
||||
const data = await authApi.register(email, password, nickName, verifyCode)
|
||||
async function h5Register(email, password, nickName, verifyCode, username) {
|
||||
const data = await authApi.register(email, password, nickName, verifyCode, username)
|
||||
token.value = data.token
|
||||
userInfo.value = data.userInfo
|
||||
uni.setStorageSync('token', data.token)
|
||||
return data
|
||||
}
|
||||
|
||||
// 将后端扁平的订阅字段映射为前端使用的 subInfo 结构
|
||||
function mapSubscription(u) {
|
||||
if (!u) return null
|
||||
const isSub = !!u.isSubscriber
|
||||
const expires = u.subscribeExpiry || null
|
||||
if (!isSub && !expires) return null
|
||||
return {
|
||||
plan: u.subscriptionPlan || (isSub ? 'PRO' : 'free'),
|
||||
expiresAt: expires,
|
||||
startDate: u.subscribeStart || u.createdAt || null
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchMe() {
|
||||
try {
|
||||
const data = await authApi.me(token.value)
|
||||
userInfo.value = data
|
||||
// /me 返回完整用户文档,直接派生订阅信息
|
||||
subInfo.value = mapSubscription(data)
|
||||
return data
|
||||
} catch (err) {
|
||||
console.error('[userStore] fetchMe failed:', err)
|
||||
@@ -56,14 +71,11 @@ export const useUserStore = defineStore('user', () => {
|
||||
}
|
||||
|
||||
async function fetchSubInfo(tokenVal) {
|
||||
// 从后端获取订阅信息 - 复用 knowledgeApi 或专门接口
|
||||
// 当前后端无单独订阅接口,从 /api/user/progress 中获取
|
||||
const { userApi } = await import('@/utils/api')
|
||||
try {
|
||||
const progress = await userApi.progress(tokenVal)
|
||||
if (progress && progress.subscription) {
|
||||
subInfo.value = progress.subscription
|
||||
}
|
||||
const u = progress && (progress.data || progress)
|
||||
subInfo.value = mapSubscription(u)
|
||||
} catch (err) {
|
||||
console.error('[userStore] fetchSubInfo failed:', err)
|
||||
}
|
||||
|
||||
+11
-6
@@ -6,8 +6,8 @@
|
||||
const BASE_URL = 'https://wdkj.yuzhiran.com.cn'
|
||||
const DEV_URL = 'http://127.0.0.1:3001'
|
||||
|
||||
// 生产环境使用域名(SSL 已续期)
|
||||
const API_BASE = 'https://wdkj.yuzhiran.com.cn'
|
||||
// 开发环境走本地后端,生产环境使用线上域名
|
||||
const API_BASE = import.meta.env.DEV ? DEV_URL : BASE_URL
|
||||
|
||||
/**
|
||||
* 通用请求封装
|
||||
@@ -54,8 +54,8 @@ export const authApi = {
|
||||
request('POST', '/api/auth/user/login', loginData),
|
||||
|
||||
/** H5 注册 */
|
||||
register: (email, password, nickName, verifyCode) =>
|
||||
request('POST', '/api/auth/user/register', { email, password, nickName, verifyCode }),
|
||||
register: (email, password, nickName, verifyCode, username) =>
|
||||
request('POST', '/api/auth/user/register', { email, password, nickName, verifyCode, username }),
|
||||
|
||||
/** 获取当前用户 */
|
||||
me: (token) =>
|
||||
@@ -155,10 +155,15 @@ export const paymentApi = {
|
||||
/** 获取商品列表 */
|
||||
products: () =>
|
||||
request('GET', '/api/payment/products'),
|
||||
|
||||
/** 创建订单 */
|
||||
/** 创建订单(小程序支付) */
|
||||
createOrder: (productId, token) =>
|
||||
request('POST', '/api/payment/create-order', { productId }, token),
|
||||
/** 创建H5支付订单 */
|
||||
createH5Order: (productId, clientIp, token) =>
|
||||
request('POST', '/api/payment/create-h5-order', { productId, clientIp }, token),
|
||||
/** 验证支付结果 */
|
||||
verify: (productId, orderNo, token) =>
|
||||
request('POST', '/api/payment/verify', { productId, orderNo }, token),
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user