Files
training/frontend/src/stores/userStore.ts
2026-07-28 12:52:36 +08:00

143 lines
4.1 KiB
TypeScript

import { create } from 'zustand'
import { userApi } from '../http/userApi'
import type { UserQuery, UserRequest } from '../interfaces/user/api'
import type { Role, Status, User, UserSummary } from '../interfaces/user/model'
import { errorMessage } from '../utils/user'
interface UserStore {
users: User[]
total: number
summary: UserSummary
search: string
roleFilter: Role | ''
statusFilter: Status | ''
selectedIds: Set<number>
isLoading: boolean
isSaving: boolean
isBulkUpdating: boolean
error: string
setSearch: (search: string) => void
setRoleFilter: (role: Role | '') => void
setStatusFilter: (status: Status | '') => void
clearFilters: () => void
loadUsers: () => Promise<void>
loadSummary: () => Promise<void>
createUser: (payload: UserRequest) => Promise<boolean>
updateUser: (id: number, payload: UserRequest) => Promise<boolean>
deleteUser: (id: number) => Promise<boolean>
updateSelectedStatus: (status: Status) => Promise<boolean>
toggleSelected: (id: number) => void
toggleAll: () => void
clearError: () => void
}
const emptySummary: UserSummary = { total: 0, active: 0, inactive: 0, admins: 0 }
function queryFromState(state: UserStore): UserQuery {
return {
keyword: state.search,
role: state.roleFilter,
status: state.statusFilter,
page: 1,
pageSize: 50,
}
}
export const useUserStore = create<UserStore>((set, get) => ({
users: [],
total: 0,
summary: emptySummary,
search: '',
roleFilter: '',
statusFilter: '',
selectedIds: new Set<number>(),
isLoading: true,
isSaving: false,
isBulkUpdating: false,
error: '',
setSearch: (search) => set({ search }),
setRoleFilter: (roleFilter) => set({ roleFilter }),
setStatusFilter: (statusFilter) => set({ statusFilter }),
clearFilters: () => set({ search: '', roleFilter: '', statusFilter: '' }),
loadUsers: async () => {
set({ isLoading: true, error: '' })
try {
const data = await userApi.list(queryFromState(get()))
set({ users: data.items, total: data.total, selectedIds: new Set<number>() })
} catch (error) {
set({ error: errorMessage(error, '无法加载用户列表') })
} finally {
set({ isLoading: false })
}
},
loadSummary: async () => {
try {
const summary = await userApi.summary()
set({ summary })
} catch (error) {
set({ error: errorMessage(error, '无法加载用户概览') })
}
},
createUser: async (payload) => {
set({ isSaving: true, error: '' })
try {
await userApi.create(payload)
return true
} catch (error) {
set({ error: errorMessage(error, '保存失败,请检查输入') })
return false
} finally {
set({ isSaving: false })
}
},
updateUser: async (id, payload) => {
set({ isSaving: true, error: '' })
try {
await userApi.update(id, payload)
return true
} catch (error) {
set({ error: errorMessage(error, '保存失败,请检查输入') })
return false
} finally {
set({ isSaving: false })
}
},
deleteUser: async (id) => {
set({ error: '' })
try {
await userApi.remove(id)
return true
} catch (error) {
set({ error: errorMessage(error, '删除失败,请稍后重试') })
return false
}
},
updateSelectedStatus: async (status) => {
const ids = [...get().selectedIds]
if (ids.length === 0) return false
set({ isBulkUpdating: true, error: '' })
try {
await userApi.updateStatuses({ ids, status })
return true
} catch (error) {
set({ error: errorMessage(error, '批量更新失败,请稍后重试') })
return false
} finally {
set({ isBulkUpdating: false })
}
},
toggleSelected: (id) => set((state) => {
const selectedIds = new Set(state.selectedIds)
if (selectedIds.has(id)) selectedIds.delete(id)
else selectedIds.add(id)
return { selectedIds }
}),
toggleAll: () => set((state) => ({
selectedIds: state.users.length > 0 && state.selectedIds.size === state.users.length
? new Set<number>()
: new Set(state.users.map((user) => user.id)),
})),
clearError: () => set({ error: '' }),
}))