优化:用户信息的获取

This commit is contained in:
ivan 2026-02-14 19:47:08 +08:00
parent a7bbc509da
commit 41b349fd09
8 changed files with 118 additions and 21 deletions

View File

@ -1,11 +1,13 @@
--- ---
description: 修改或新增 src/ 代码时同步更新 docs/ 技术文档 description: 修改或新增 src/ 代码时,必须在同一轮对话中同步更新 docs/ 技术文档
globs: src/**/* globs: src/**/*
alwaysApply: false alwaysApply: true
--- ---
# 文档同步规则 # 文档同步规则
**修改 src/ 后必做**:在同一轮对话中,立即更新 `docs/` 中对应的技术文档。不要等到用户提醒。
当你在 `src/` 下**新增或修改**任何文件时,必须同步更新 `docs/` 中对应的技术文档。 当你在 `src/` 下**新增或修改**任何文件时,必须同步更新 `docs/` 中对应的技术文档。
## 文件映射关系 ## 文件映射关系
@ -35,3 +37,10 @@ API 与组件文档需补充类型说明、Props、Events 等。
## 示例 ## 示例
修改 `src/api/event.ts` 后,需更新 `docs/api/event.md`;新增 `src/api/order.ts` 时,需新建 `docs/api/order.md`。 修改 `src/api/event.ts` 后,需更新 `docs/api/event.md`;新增 `src/api/order.ts` 时,需新建 `docs/api/order.md`。
## 修改后检查清单
修改 `src/` 内任何文件后,在同一轮回复中完成:
- [ ] 根据上表找到对应的 `docs/` 路径
- [ ] 更新或新建该文档,反映本次代码变更

View File

@ -18,6 +18,7 @@
- Buy/Sell Tab 切换 - Buy/Sell Tab 切换
- Market/Limit 类型、Merge/Split 菜单 - Market/Limit 类型、Merge/Split 菜单
- **Buy 模式 Amount 区**:无论余额是否充足,均显示 Amount 标签、Balance、金额输入、+$1/+$20/+$100/Max 快捷按钮(桌面端、嵌入弹窗、移动端弹窗一致)
- 余额不足时 Buy 显示 Deposit 按钮 - 余额不足时 Buy 显示 Deposit 按钮
- 25%/50%/Max 快捷份额 - 25%/50%/Max 快捷份额
- 调用 market API 下单、Split、Merge - 调用 market API 下单、Split、Merge

View File

@ -8,9 +8,10 @@
## 核心能力 ## 核心能力
- 顶部导航栏返回、PolyMarket 标题、Login 或余额+头像菜单 - 顶部导航栏返回、PolyMarket 标题、Login 或余额+用户名+头像菜单
- 登录态:`userStore.isLoggedIn` 控制展示 - 登录态:`userStore.isLoggedIn` 控制展示
- 挂载时:若已登录,拉取用户信息与余额 - 用户名:`nickName``userName` 显示在头像左侧(有值时)
- 挂载时与 `isLoggedIn` 变为 true 时:拉取用户信息与余额(`router.isReady()` + `nextTick` 后执行),确保钱包登录、刷新页面后头像和用户名正确显示
- keep-alive`include="['Home']"` 缓存首页 - keep-alive`include="['Home']"` 缓存首页
## 扩展方式 ## 扩展方式

View File

@ -15,6 +15,7 @@
- `logout`:清空并清除持久化 - `logout`:清空并清除持久化
- `getAuthHeaders`:返回 `{ 'x-token', 'x-user-id' }`,未登录时返回 `undefined` - `getAuthHeaders`:返回 `{ 'x-token', 'x-user-id' }`,未登录时返回 `undefined`
- `fetchUserInfo``fetchUsdcBalance`:拉取并更新用户信息与余额 - `fetchUserInfo``fetchUsdcBalance`:拉取并更新用户信息与余额
- `fetchUserInfo` 兼容多种 API 字段名:`userName`/`username``nickName`/`nickname``headerImg`/`avatar`/`avatarUrl`;支持 `data` 直接为用户对象或 `data.user` 嵌套结构
## 使用方式 ## 使用方式
@ -41,3 +42,7 @@ await userStore.fetchUsdcBalance()
1. **多端登录**:扩展 `setUser` 支持多设备 token 管理 1. **多端登录**:扩展 `setUser` 支持多设备 token 管理
2. **Token 刷新**:在 `getAuthHeaders` 或请求拦截器中加入 refresh 逻辑 2. **Token 刷新**:在 `getAuthHeaders` 或请求拦截器中加入 refresh 逻辑
3. **登出回调**:在 `logout` 中增加清理逻辑(如取消订阅、重置其他 store 3. **登出回调**:在 `logout` 中增加清理逻辑(如取消订阅、重置其他 store
## 登录后刷新用户信息
钱包登录Login.vue成功后需调用 `fetchUserInfo()` 以获取完整用户信息头像、昵称。App.vue 在 `onMounted``watch(isLoggedIn)` 时也会调用,确保自动登录或刷新页面后能正确显示。

View File

@ -1,21 +1,35 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted } from 'vue' import { computed, onMounted, watch, nextTick } from 'vue'
import { useRoute } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useUserStore } from './stores/user' import { useUserStore } from './stores/user'
const route = useRoute() const route = useRoute()
const router = useRouter()
const userStore = useUserStore() const userStore = useUserStore()
const currentRoute = computed(() => { const currentRoute = computed(() => route.path)
return route.path const displayName = computed(
}) () => userStore.user?.nickName || userStore.user?.userName || ''
)
async function refreshUserData() {
if (!userStore.isLoggedIn) return
await router.isReady()
await nextTick()
await userStore.fetchUserInfo()
await userStore.fetchUsdcBalance()
}
onMounted(() => { onMounted(() => {
if (userStore.isLoggedIn) { refreshUserData()
userStore.fetchUserInfo()
userStore.fetchUsdcBalance()
}
}) })
watch(
() => userStore.isLoggedIn,
(loggedIn) => {
if (loggedIn) refreshUserData()
},
{ immediate: true },
)
</script> </script>
<template> <template>
@ -51,6 +65,7 @@ onMounted(() => {
> >
<span class="balance-text">${{ userStore.balance }}</span> <span class="balance-text">${{ userStore.balance }}</span>
</v-btn> </v-btn>
<span v-if="displayName" class="header-username">{{ displayName }}</span>
<v-menu location="bottom" :close-on-content-click="false"> <v-menu location="bottom" :close-on-content-click="false">
<template #activator="{ props }"> <template #activator="{ props }">
<v-btn v-bind="props" icon variant="text" class="avatar-btn"> <v-btn v-bind="props" icon variant="text" class="avatar-btn">
@ -92,6 +107,16 @@ onMounted(() => {
text-transform: none; text-transform: none;
} }
.header-username {
font-size: 0.9rem;
color: rgba(255, 255, 255, 0.95);
margin-right: 4px;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.balance-text { .balance-text {
font-weight: 500; font-weight: 500;
font-size: 0.95rem; font-size: 0.95rem;

View File

@ -56,6 +56,24 @@
</v-btn> </v-btn>
</div> </div>
<!-- Buy Market: Amount 余额充足时也显示 -->
<template v-if="activeTab === 'buy'">
<div class="input-group">
<div class="amount-header">
<div>
<span class="label amount-label">Amount</span>
<span class="balance-label">Balance ${{ balance.toFixed(2) }}</span>
</div>
<div class="amount-value">${{ amount.toFixed(2) }}</div>
</div>
<div class="amount-buttons">
<v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn>
<v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn>
<v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn>
<v-btn class="amount-btn" @click="setMaxAmount">Max</v-btn>
</div>
</div>
</template>
<!-- Sell Market: Shares input + 25%/50%/Max --> <!-- Sell Market: Shares input + 25%/50%/Max -->
<template v-if="activeTab === 'sell'"> <template v-if="activeTab === 'sell'">
<div class="input-group shares-group"> <div class="input-group shares-group">
@ -441,6 +459,24 @@
>{{ noLabel }} {{ noPriceCents }}¢</v-btn >{{ noLabel }} {{ noPriceCents }}¢</v-btn
> >
</div> </div>
<!-- Buy Market: Amount 余额充足时也显示 -->
<template v-if="activeTab === 'buy'">
<div class="input-group">
<div class="amount-header">
<div>
<span class="label amount-label">Amount</span>
<span class="balance-label">Balance ${{ balance.toFixed(2) }}</span>
</div>
<div class="amount-value">${{ amount.toFixed(2) }}</div>
</div>
<div class="amount-buttons">
<v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn>
<v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn>
<v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn>
<v-btn class="amount-btn" @click="setMaxAmount">Max</v-btn>
</div>
</div>
</template>
<!-- Sell Market: Shares input + 25%/50%/Max --> <!-- Sell Market: Shares input + 25%/50%/Max -->
<template v-if="activeTab === 'sell'"> <template v-if="activeTab === 'sell'">
<div class="input-group shares-group"> <div class="input-group shares-group">
@ -809,6 +845,24 @@
>{{ noLabel }} {{ noPriceCents }}¢</v-btn >{{ noLabel }} {{ noPriceCents }}¢</v-btn
> >
</div> </div>
<!-- Buy Market: Amount 余额充足时也显示 -->
<template v-if="activeTab === 'buy'">
<div class="input-group">
<div class="amount-header">
<div>
<span class="label amount-label">Amount</span>
<span class="balance-label">Balance ${{ balance.toFixed(2) }}</span>
</div>
<div class="amount-value">${{ amount.toFixed(2) }}</div>
</div>
<div class="amount-buttons">
<v-btn class="amount-btn" @click="adjustAmount(1)">+$1</v-btn>
<v-btn class="amount-btn" @click="adjustAmount(20)">+$20</v-btn>
<v-btn class="amount-btn" @click="adjustAmount(100)">+$100</v-btn>
<v-btn class="amount-btn" @click="setMaxAmount">Max</v-btn>
</div>
</div>
</template>
<!-- Sell Market: Shares input + 25%/50%/Max --> <!-- Sell Market: Shares input + 25%/50%/Max -->
<template v-if="activeTab === 'sell'"> <template v-if="activeTab === 'sell'">
<div class="input-group shares-group"> <div class="input-group shares-group">

View File

@ -96,14 +96,15 @@ export const useUserStore = defineStore('user', () => {
try { try {
const res = await getUserInfo(headers) const res = await getUserInfo(headers)
console.log('[fetchUserInfo] 接口响应:', JSON.stringify(res, null, 2)) console.log('[fetchUserInfo] 接口响应:', JSON.stringify(res, null, 2))
if ((res.code === 0 || res.code === 200) && res.data) { const data = res.data as Record<string, unknown> | undefined
const u = res.data const u = (data?.user ?? data) as Record<string, unknown>
if ((res.code === 0 || res.code === 200) && u) {
user.value = { user.value = {
id: u.ID, id: u.ID as number | string | undefined,
ID: u.ID, ID: u.ID as number | undefined,
userName: u.userName, userName: (u.userName ?? u.username) as string | undefined,
nickName: u.nickName, nickName: (u.nickName ?? u.nickname) as string | undefined,
headerImg: u.headerImg, headerImg: (u.headerImg ?? u.avatar ?? u.avatarUrl) as string | undefined,
...u, ...u,
} }
if (token.value && user.value) saveToStorage(token.value, user.value) if (token.value && user.value) saveToStorage(token.value, user.value)

View File

@ -184,7 +184,8 @@ const connectWithWallet = async () => {
const { token, user } = loginData.data const { token, user } = loginData.data
console.log('[walletLogin] 存入 store 的 user:', JSON.stringify(user, null, 2)) console.log('[walletLogin] 存入 store 的 user:', JSON.stringify(user, null, 2))
userStore.setUser({ token, user }) userStore.setUser({ token, user })
userStore.fetchUsdcBalance() await userStore.fetchUserInfo()
await userStore.fetchUsdcBalance()
} }
router.push('/') router.push('/')