Files
yuxingu-Miniprogram-dev/pages/yxg/mine.vue
T
jackyu66gitandCursor b09d82df8d feat: 小程序统一走 Go 后台,咨询与登录切到 /api/v1
去掉协会 WebView 和魔方账密页,微信登录与咨询接口共用同一 token 和拦截器。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-15 00:26:28 +08:00

161 lines
5.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<yxg-page title="我的魔方">
<view class="hero">
<view class="av" @click="pickAvatar">
<image class="av-img" :src="avatarSrc" mode="aspectFill" />
</view>
<text class="name">{{ accountLabel }}</text>
<text class="meta">{{ loggedIn ? `${profileCount} 份档案` : '登录后同步你的探索' }}</text>
<view v-if="loggedIn" class="logout" @click="logout"><text>退出登录</text></view>
<view v-else class="login" @click="openWechatLogin"><text>登录</text></view>
</view>
<view v-if="loading" class="hint"><text>加载中</text></view>
<view v-else-if="error" class="err" @click="load"><text>{{ error }} · 点此重试</text></view>
<template v-else>
<view v-if="membership && membership.active" class="vip">
<text class="vip-t">成长会员 · {{ planLabel }}</text>
</view>
<view class="sec">
<text class="sec-t">档案与内容</text>
<view class="list">
<view v-for="it in archive" :key="it.to" class="row" @click="yxgGo(it.to)">
<yxg-tool-icon :name="it.icon" :size="28" />
<text class="row-t">{{ it.label }}</text>
<text class="chev"></text>
</view>
</view>
</view>
<view class="sec">
<text class="sec-t">成长与会员</text>
<view class="list">
<view v-for="it in growth" :key="it.to" class="row" @click="yxgGo(it.to)">
<yxg-tool-icon :name="it.icon" :size="28" />
<text class="row-t">{{ it.label }}</text>
<text class="chev"></text>
</view>
</view>
</view>
</template>
</yxg-page>
</template>
<script setup>
import YxgPage from '@/components/yxg/yxg-page.vue'
import YxgToolIcon from '@/components/yxg/yxg-tool-icon.vue'
import { yxgApi, assetURL, setYxgToken } from '@/util/yxgApi.js'
import { mineGroupArchive, mineGroupGrowth } from '@/util/yxgCatalog.js'
import { YXG_DEFAULT_AVATAR } from '@/util/yxgConfig.js'
import { openWechatLogin } from '@/util/yxgAuth.js'
import { yxgGo } from '@/util/yxgNav.js'
const loading = ref(true)
const error = ref('')
const me = ref(null)
const membership = ref(null)
const profileCount = ref(0)
const archive = mineGroupArchive
const growth = mineGroupGrowth
const loggedIn = computed(() => !!me.value)
const accountLabel = computed(() => me.value?.nickname || me.value?.phone || '未登录')
const avatarSrc = computed(() => assetURL(me.value?.avatar_url) || YXG_DEFAULT_AVATAR)
const planLabel = computed(() => {
const p = membership.value?.plan
if (p === 'quarter') return '季卡'
if (p === 'year') return '年卡'
if (p === 'month') return '月卡'
return '会员中'
})
async function load() {
loading.value = true
error.value = ''
try {
me.value = await yxgApi.authMe()
} catch {
me.value = null
membership.value = null
profileCount.value = 0
loading.value = false
return
}
try {
const [m, profiles] = await Promise.all([yxgApi.getMembership(), yxgApi.listProfiles()])
membership.value = m
profileCount.value = (profiles.items || []).length
} catch (e) {
error.value = e instanceof Error ? e.message : '加载失败'
} finally {
loading.value = false
}
}
async function logout() {
try { await yxgApi.authLogout() } catch { /* ignore */ }
setYxgToken('')
uni.removeStorageSync('userinfo')
me.value = null
membership.value = null
profileCount.value = 0
}
function pickAvatar() {
if (!loggedIn.value) {
openWechatLogin()
return
}
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
success: async (res) => {
const path = res.tempFilePaths[0]
try {
me.value = await yxgApi.authUploadAvatar(path)
} catch (e) {
uni.showToast({ title: e.message || '上传失败', icon: 'none' })
}
},
})
}
onShow(load)
</script>
<style>
page { background-color: #ffd1c7; }
</style>
<style scoped>
.hero {
margin: 8px 16px 0; padding: 20px 16px; background: #fff; border-radius: 20px;
display: flex; flex-direction: column; align-items: center; gap: 6px;
box-shadow: 0 8px 28px rgba(229, 77, 66, 0.1);
}
.av { width: 72px; height: 72px; border-radius: 50%; overflow: hidden; background: #ffe8e0; }
.av-img { width: 72px; height: 72px; }
.name { font-size: 18px; font-weight: 700; color: #333; }
.meta { font-size: 12px; color: #999; }
.logout, .login {
margin-top: 8px; padding: 6px 16px; border-radius: 999px; font-size: 13px;
}
.logout { color: #999; background: #f7f2ef; }
.login { color: #fff; background: #e54d42; }
.hint, .err { padding: 16px; font-size: 13px; color: #999; }
.err { color: #e54d42; }
.vip {
margin: 14px 16px 0; padding: 12px 14px; border-radius: 14px;
background: linear-gradient(145deg, #fff4e0, #ffd98a);
}
.vip-t { font-size: 14px; font-weight: 700; color: #8a5a18; }
.sec { margin: 18px 16px 0; }
.sec-t { display: block; font-size: 15px; font-weight: 700; color: #333; margin-bottom: 10px; }
.list { background: #fff; border-radius: 16px; overflow: hidden; }
.row {
display: flex; align-items: center; gap: 12px;
padding: 12px 14px; border-bottom: 1px solid #f5f5f5;
}
.row:last-child { border-bottom: none; }
.row-t { flex: 1; font-size: 15px; color: #333; }
.chev { color: #ccc; font-size: 16px; }
</style>