严重 Bug: - NaN 金额校验绕过(add 页面 parseFloat 空字符串) - 4 个页面补充 waitForReady(add/budget/category-manage/group-manage) - 导出 Loading 遮罩不消失(profile exportData 缺 hideLoading) - Store 吞掉请求错误不 re-throw(transaction/stats/budget/user) - 分类排序 SQL 拼接改参数化查询(category PUT /sort) - 备份接口添加管理员权限检查(backup requireAdmin) 中等问题: - 群组加入竞态条件改为 INSERT IGNORE - 统计页切月/切Tab添加 loadSeq 防竞态 - stats store 补全 loading 状态 + Overview 初始值补全 7 字段 - 保存成功后返回不再弹「放弃修改」确认框 - 群组管理创建/加入添加 saving 双重提交保护 - 预算页添加 onShow 刷新 - 服务端输入校验(name/color/invite_code/微信code) UI/样式: - 触摸目标: bell/nav-back/nav-save→88rpx, avatar-badge→64rpx, action-btn→80rpx - :active 反馈: bell/profile-card/identity-card/id-manage/arrow/action-btn/group-btn/cat-info - 内联样式改动态绑定(4处 spacer 使用 capsuleRight) - FAB 文字+改用 Icon 组件 - prefers-reduced-motion 动画降级(Skeleton/profile/budget/add) - 胶囊适配(add/category-manage/group-manage) - CategoryIcon !important 改为 iconStyle 计算属性 文档: - CLAUDE.md 补充 SaveSuccess/ChartWrapper 组件 + category/budget/index API
54 lines
1.8 KiB
TypeScript
54 lines
1.8 KiB
TypeScript
import { defineStore } from 'pinia'
|
||
import { ref, computed } from 'vue'
|
||
import * as api from '@/api/user'
|
||
import { API_BASE } from '@/config'
|
||
|
||
export const useUserStore = defineStore('user', () => {
|
||
const userInfo = ref<api.UserInfo | null>(null)
|
||
const loading = ref(false)
|
||
|
||
const nickname = computed(() => {
|
||
return userInfo.value?.nickname || uni.getStorageSync('xc:nickname') || '小菜'
|
||
})
|
||
|
||
const avatarUrl = computed(() => {
|
||
const filename = userInfo.value?.avatar_url || uni.getStorageSync('xc:avatar_url') || ''
|
||
if (!filename) return ''
|
||
// 已是完整 URL(http/blob)直接返回
|
||
if (filename.startsWith('http') || filename.startsWith('blob:')) return filename
|
||
// 拼接:API_BASE + /user/avatar/ + 文件名
|
||
return `${API_BASE}/user/avatar/${filename}`
|
||
})
|
||
|
||
async function fetchUserInfo() {
|
||
loading.value = true
|
||
try {
|
||
userInfo.value = await api.getUserInfo()
|
||
if (userInfo.value) {
|
||
uni.setStorageSync('xc:nickname', userInfo.value.nickname)
|
||
uni.setStorageSync('xc:avatar_url', userInfo.value.avatar_url)
|
||
}
|
||
} catch (e) {
|
||
console.error('[UserStore] fetchUserInfo error:', e)
|
||
throw e
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
async function updateProfile(nickname: string) {
|
||
await api.updateNickname(nickname)
|
||
if (userInfo.value) userInfo.value.nickname = nickname
|
||
uni.setStorageSync('xc:nickname', nickname)
|
||
}
|
||
|
||
async function uploadAvatar(filePath: string) {
|
||
const result = await api.uploadAvatar(filePath)
|
||
if (userInfo.value) userInfo.value.avatar_url = result.avatar_url
|
||
uni.setStorageSync('xc:avatar_url', result.avatar_url)
|
||
return avatarUrl.value
|
||
}
|
||
|
||
return { userInfo, loading, nickname, avatarUrl, fetchUserInfo, updateProfile, uploadAvatar }
|
||
})
|