'use client'; import { create } from 'zustand'; import type { Department, Member, RoleItem, PasswordRule } from '@/lib/members'; import { DEFAULT_PASSWORD_RULE, ensureMemberUsernames, generatePassword, generateUsernameFromName } from '@/lib/members'; import { SYSTEM_ADMIN_MEMBER, SYSTEM_ADMIN_ROLE_ID, canDeleteSystemMember, ensureSystemAdminMember, sanitizeSystemAdminPatch, } from '@/lib/member-system'; import { createMemberDomain, deleteMemberDomain, listMembersDomain, updateMemberDomain, } from '@/lib/domain-api'; import { scheduleSaveWithOptimisticRollback } from '@/lib/optimistic-persistence'; import { DEFAULT_ROLE_PERMISSIONS, DEFAULT_ROLE_PRESETS } from '@/lib/permissions'; import { mergePresetRolePermissions } from '@/lib/role-permission-migration'; import { loadServerData, saveServerData, SERVER_DATA_CACHE_MS, SERVER_DATA_SAVE_ERROR_EVENT } from '@/lib/server-data'; const PRESET_DEPARTMENTS: Department[] = [ { id: 'dept-1', name: '产品部', order: 1, createdAt: '2024-01-01' }, { id: 'dept-2', name: '技术部', order: 2, createdAt: '2024-01-01' }, { id: 'dept-2-1', name: '前端组', parentId: 'dept-2', order: 1, createdAt: '2024-01-01' }, { id: 'dept-2-2', name: '后端组', parentId: 'dept-2', order: 2, createdAt: '2024-01-01' }, { id: 'dept-2-3', name: '测试组', parentId: 'dept-2', order: 3, createdAt: '2024-01-01' }, { id: 'dept-3', name: '设计部', order: 3, createdAt: '2024-01-01' }, { id: 'dept-4', name: '运营部', order: 4, createdAt: '2024-01-01' }, ]; const PRESET_ROLES: RoleItem[] = DEFAULT_ROLE_PRESETS; const MOCK_MEMBERS: Member[] = [ SYSTEM_ADMIN_MEMBER, ]; let lastMembersFetchAt = 0; type StoredMemberConfig = { departments?: Department[]; members?: Member[]; roles?: RoleItem[]; passwordRule?: PasswordRule; }; type MemberConfigState = { departments: Department[]; roles: RoleItem[]; passwordRule: PasswordRule; }; interface MemberState { departments: Department[]; members: Member[]; roles: RoleItem[]; passwordRule: PasswordRule; loading: boolean; loaded: boolean; fetchMembers: (options?: { force?: boolean }) => Promise; updatePasswordRule: (rule: PasswordRule) => void; createDepartment: (data: Omit) => void; updateDepartment: (id: string, data: Partial) => void; deleteDepartment: (id: string) => void; createMember: (data: Omit) => void; updateMember: (id: string, data: Partial) => Promise; deleteMember: (id: string) => void; createRole: (data: Omit) => void; updateRole: (id: string, data: Partial) => void; deleteRole: (id: string) => void; } async function saveStored(state: MemberConfigState) { await saveServerData('members', { departments: state.departments, members: [], roles: state.roles, passwordRule: state.passwordRule }); } async function loadStored(): Promise { try { return await loadServerData('members'); } catch {} return null; } export const useMemberStore = create((set, get) => ({ departments: PRESET_DEPARTMENTS, members: MOCK_MEMBERS, roles: PRESET_ROLES, passwordRule: DEFAULT_PASSWORD_RULE, loading: false, loaded: false, fetchMembers: async (options) => { if (!options?.force && get().loaded && Date.now() - lastMembersFetchAt < SERVER_DATA_CACHE_MS) return; const [cached, domainMembers] = await Promise.all([ loadStored(), listMembersDomain().catch(() => null), ]); if (!options?.force && get().loaded && Date.now() - lastMembersFetchAt < SERVER_DATA_CACHE_MS) return; lastMembersFetchAt = Date.now(); if (cached) { // 迁移:旧 RoleItem 没 permissions 字段,补默认值 const configuredRoles = Array.isArray(cached.roles) ? cached.roles : PRESET_ROLES; const migratedRoles = configuredRoles.map((r) => { if (!('permissions' in r) || !Array.isArray((r as any).permissions)) { return { ...r, permissions: DEFAULT_ROLE_PERMISSIONS[r.id] ?? [] }; } return r; }); const permissionMerged = mergePresetRolePermissions(migratedRoles); const rolesWithNewDefaults = permissionMerged.roles; const adminRole = PRESET_ROLES.find((r) => r.id === SYSTEM_ADMIN_ROLE_ID)!; const roles = rolesWithNewDefaults.some((r) => r.id === SYSTEM_ADMIN_ROLE_ID) ? rolesWithNewDefaults.map((r) => r.id === SYSTEM_ADMIN_ROLE_ID ? { ...adminRole, ...r, isSystem: true, permissions: DEFAULT_ROLE_PERMISSIONS[SYSTEM_ADMIN_ROLE_ID] } : r) : [adminRole, ...rolesWithNewDefaults]; const sourceMembers = domainMembers && domainMembers.length > 0 ? domainMembers : get().members; const ensured = ensureSystemAdminMember(sourceMembers); const usernameEnsured = ensureMemberUsernames(ensured.members); const next = { departments: Array.isArray(cached.departments) ? cached.departments : PRESET_DEPARTMENTS, members: usernameEnsured.members, roles, passwordRule: cached.passwordRule || DEFAULT_PASSWORD_RULE, }; set({ ...next, loaded: true }); } else { const sourceMembers = domainMembers && domainMembers.length > 0 ? domainMembers : get().members; const ensured = ensureSystemAdminMember(sourceMembers); const usernameEnsured = ensureMemberUsernames(ensured.members); set({ members: usernameEnsured.members, loaded: true }); } }, updatePasswordRule: (rule) => { const previous = get().passwordRule; set({ passwordRule: rule }); scheduleSaveWithOptimisticRollback({ save: () => saveStored({ departments: get().departments, roles: get().roles, passwordRule: rule }), expected: rule, getCurrent: () => get().passwordRule, rollback: () => set({ passwordRule: previous }), }); }, createDepartment: (data) => { const previous = get().departments; const dept: Department = { ...data, id: `dept-${Date.now()}`, createdAt: new Date().toISOString().slice(0, 10) }; const departments = [...previous, dept]; set({ departments }); scheduleSaveWithOptimisticRollback({ save: () => saveStored({ departments, roles: get().roles, passwordRule: get().passwordRule }), expected: departments, getCurrent: () => get().departments, rollback: () => set({ departments: previous }), }); }, updateDepartment: (id, data) => { const previous = get().departments; const departments = previous.map((d) => d.id === id ? { ...d, ...data } : d); set({ departments }); scheduleSaveWithOptimisticRollback({ save: () => saveStored({ departments, roles: get().roles, passwordRule: get().passwordRule }), expected: departments, getCurrent: () => get().departments, rollback: () => set({ departments: previous }), }); }, deleteDepartment: (id) => { const previous = get().departments; const departments = previous.filter((d) => d.id !== id && d.parentId !== id); set({ departments }); scheduleSaveWithOptimisticRollback({ save: () => saveStored({ departments, roles: get().roles, passwordRule: get().passwordRule }), expected: departments, getCurrent: () => get().departments, rollback: () => set({ departments: previous }), }); }, createMember: (data) => { const previous = get().members; const member: Member = { ...data, username: data.username || generateUsernameFromName(data.name, previous.map((m) => m.username ?? '')), phone: data.phone ?? '', id: `m-${Date.now()}`, createdAt: new Date().toISOString().slice(0, 10), }; const members = [...previous, member]; set({ members }); scheduleSaveWithOptimisticRollback({ save: async () => { try { const created = await createMemberDomain(member, previous); set({ members: get().members.map((item) => (item.id === member.id ? { ...member, ...created } : item)), }); } catch (error) { notifyMemberSaveFailure(error); throw error; } }, expected: members, getCurrent: () => get().members, rollback: () => set({ members: previous }), }); }, updateMember: async (id, data) => { const previousMembers = get().members; const safeData = sanitizeSystemAdminPatch(id, data); const updatedMembers = get().members.map((m) => m.id === id ? { ...m, ...safeData } : m); const ensured = ensureSystemAdminMember(updatedMembers); set({ members: ensured.members }); try { await updateMemberDomain(id, safeData); } catch (error) { set({ members: previousMembers }); notifyMemberSaveFailure(error); throw error; } }, deleteMember: (id) => { if (!canDeleteSystemMember(id)) return; const previous = get().members; const members = previous.filter((m) => m.id !== id); set({ members }); scheduleSaveWithOptimisticRollback({ save: async () => { try { await deleteMemberDomain(id); } catch (error) { notifyMemberSaveFailure(error); throw error; } }, expected: members, getCurrent: () => get().members, rollback: () => set({ members: previous }), }); }, createRole: (data) => { const previous = get().roles; const role: RoleItem = { ...data, id: `role-${Date.now()}`, createdAt: new Date().toISOString().slice(0, 10), permissions: data.permissions ?? [], }; const roles = [...previous, role]; set({ roles }); scheduleSaveWithOptimisticRollback({ save: () => saveStored({ departments: get().departments, roles, passwordRule: get().passwordRule }), expected: roles, getCurrent: () => get().roles, rollback: () => set({ roles: previous }), }); }, updateRole: (id, data) => { const previous = get().roles; const roles = previous.map((r) => { if (r.id !== id) return r; // 超管不允许改 permissions(防误操作锁死系统) if (r.id === 'role-admin' && data.permissions) { const { permissions, ...rest } = data; return { ...r, ...rest }; } return { ...r, ...data }; }); set({ roles }); scheduleSaveWithOptimisticRollback({ save: () => saveStored({ departments: get().departments, roles, passwordRule: get().passwordRule }), expected: roles, getCurrent: () => get().roles, rollback: () => set({ roles: previous }), }); }, deleteRole: (id) => { if (id === SYSTEM_ADMIN_ROLE_ID) return; const previous = get().roles; const roles = previous.filter((r) => r.id !== id); set({ roles }); scheduleSaveWithOptimisticRollback({ save: () => saveStored({ departments: get().departments, roles, passwordRule: get().passwordRule }), expected: roles, getCurrent: () => get().roles, rollback: () => set({ roles: previous }), }); }, })); function notifyMemberSaveFailure(error: unknown) { if (typeof window === 'undefined') return; window.dispatchEvent( new CustomEvent(SERVER_DATA_SAVE_ERROR_EVENT, { detail: { key: 'members', message: error instanceof Error && error.message ? error.message : '成员/账号保存失败,请检查 API 服务', }, }), ); }