优化:用户信息的获取

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/**/*
alwaysApply: false
alwaysApply: true
---
# 文档同步规则
**修改 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/` 内任何文件后,在同一轮回复中完成:
- [ ] 根据上表找到对应的 `docs/` 路径
- [ ] 更新或新建该文档,反映本次代码变更

View File

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

View File

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

View File

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

View File

@ -1,21 +1,35 @@
<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { computed, onMounted, watch, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useUserStore } from './stores/user'
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
const currentRoute = computed(() => {
return route.path
})
const currentRoute = computed(() => 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(() => {
if (userStore.isLoggedIn) {
userStore.fetchUserInfo()
userStore.fetchUsdcBalance()
}
refreshUserData()
})
watch(
() => userStore.isLoggedIn,
(loggedIn) => {
if (loggedIn) refreshUserData()
},
{ immediate: true },
)
</script>
<template>
@ -51,6 +65,7 @@ onMounted(() => {
>
<span class="balance-text">${{ userStore.balance }}</span>
</v-btn>
<span v-if="displayName" class="header-username">{{ displayName }}</span>
<v-menu location="bottom" :close-on-content-click="false">
<template #activator="{ props }">
<v-btn v-bind="props" icon variant="text" class="avatar-btn">
@ -92,6 +107,16 @@ onMounted(() => {
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 {
font-weight: 500;
font-size: 0.95rem;

View File

@ -56,6 +56,24 @@
</v-btn>
</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 -->
<template v-if="activeTab === 'sell'">
<div class="input-group shares-group">
@ -441,6 +459,24 @@
>{{ noLabel }} {{ noPriceCents }}¢</v-btn
>
</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 -->
<template v-if="activeTab === 'sell'">
<div class="input-group shares-group">
@ -809,6 +845,24 @@
>{{ noLabel }} {{ noPriceCents }}¢</v-btn
>
</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 -->
<template v-if="activeTab === 'sell'">
<div class="input-group shares-group">

View File

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

View File

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