完善个人中心页面

This commit is contained in:
Eamon
2026-09-04 13:21:47 +08:00
parent 6d6d300203
commit f59e929843
27 changed files with 614 additions and 414 deletions

View File

@@ -0,0 +1,11 @@
import { create } from "zustand"
import { API_BASE_URL } from "@/lib/api"
// ==================== 应用配置 ====================
// 后端地址等运行时配置统一从 store 读取,初始值来自 import.meta.env(见 lib/api.ts)
type AppState = {
apiUrl: string
}
export const useAppStore = create<AppState>()(() => ({ apiUrl: API_BASE_URL }))

View File

@@ -0,0 +1,54 @@
import { create } from "zustand"
type ClientState = {
breakpoint: {
sm: boolean
md: boolean
lg: boolean
xl: boolean
}
}
type ClientActions = {
setBreakpoints: (breakpoints: Partial<ClientState["breakpoint"]>) => void
}
export type ClientStore = ClientState & ClientActions
export const useClientStore = create<ClientStore>()(set => ({
breakpoint: { sm: false, md: false, lg: false, xl: false },
setBreakpoints: breakpoints =>
set(state => ({
breakpoint: { ...state.breakpoint, ...breakpoints },
})),
}))
let initialized = false
// 应用入口调用一次,开始监听窗口断点变化
export function initClientStore() {
if (initialized) return
initialized = true
const queries = {
sm: window.matchMedia("(width >= 40rem)"),
md: window.matchMedia("(width >= 48rem)"),
lg: window.matchMedia("(width >= 64rem)"),
xl: window.matchMedia("(width >= 80rem)"),
}
const setBreakpoints = useClientStore.getState().setBreakpoints
setBreakpoints({
sm: queries.sm.matches,
md: queries.md.matches,
lg: queries.lg.matches,
xl: queries.xl.matches,
})
for (const key of Object.keys(queries) as (keyof typeof queries)[]) {
queries[key].addEventListener("change", e => {
setBreakpoints({ [key]: e.matches })
})
}
}

View File

@@ -0,0 +1,41 @@
import { create } from "zustand"
import { fetchAgreementStatus } from "@/api/agreement"
import { fetchApiIndex } from "@/api/dashboard"
import type { ApiIndexItem } from "@/lib/models/dashboard"
// ==================== 个人中心账户总览 ====================
// 数据源:.NET /user/ApiIndex;协议签署状态:php-api /tencent/index/get_is_upload
export type DashboardState = {
data: ApiIndexItem | null
agreementSigned: boolean | null
loading: boolean
error: string | null
}
type DashboardActions = {
refresh: () => Promise<void>
refreshAgreement: () => Promise<void>
}
export type DashboardStore = DashboardState & DashboardActions
export const useDashboardStore = create<DashboardStore>()(set => ({
data: null,
agreementSigned: null,
loading: false,
error: null,
refresh: async () => {
set({ loading: true })
const res = await fetchApiIndex()
if (res.success) {
set({ data: res.data, error: null, loading: false })
} else {
set({ error: res.message, loading: false })
}
},
refreshAgreement: async () => {
const res = await fetchAgreementStatus()
if (res.success) set({ agreementSigned: res.data })
},
}))

View File

@@ -0,0 +1,20 @@
import { create } from "zustand"
type LayoutState = {
navbar: boolean
}
type LayoutActions = {
setNavbar: (navbar: boolean) => void
toggleNavbar: () => void
}
export type LayoutStore = LayoutState & LayoutActions
export const useLayoutStore = create<LayoutStore>()(set => ({
navbar: false,
setNavbar: navbar => set({ navbar }),
toggleNavbar: () => set(state => ({ navbar: !state.navbar })),
}))

View File

@@ -0,0 +1,34 @@
import { create } from "zustand"
import {
AUTH_EVENT,
getUserInfo,
getUserToken,
type UserInfoRecord,
} from "@/api/token"
export type ProfileState = {
token: string | null
userInfo: UserInfoRecord | null
}
type ProfileActions = {
refreshFromStorage: () => void
}
export type ProfileStore = ProfileState & ProfileActions
export const useProfileStore = create<ProfileStore>()(set => ({
token: getUserToken(),
userInfo: getUserInfo<UserInfoRecord>(),
refreshFromStorage: () => {
set({ token: getUserToken(), userInfo: getUserInfo<UserInfoRecord>() })
},
}))
// 应用入口调用一次,监听登录态变化(含跨标签页 focus 同步)
export function initProfileStore() {
const sync = () => useProfileStore.getState().refreshFromStorage()
window.addEventListener(AUTH_EVENT, sync)
window.addEventListener("focus", sync)
}