'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 } 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; 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: { departments: Department[]; members: Member[]; roles: RoleItem[]; passwordRule: PasswordRule }) { await saveServerData('members', state); } async function loadStored(): Promise<{ departments: Department[]; members: Member[]; roles: RoleItem[]; passwordRule: PasswordRule } | null> { try { return await loadServerData<{ departments: Department[]; members: Member[]; roles: RoleItem[]; passwordRule: PasswordRule }>('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 migratedRoles = cached.roles.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 rolesChanged = permissionMerged.changed || roles.length !== rolesWithNewDefaults.length || roles.some((role, index) => JSON.stringify(role) !== JSON.stringify(rolesWithNewDefaults[index])); const sourceMembers = domainMembers && domainMembers.length > 0 ? domainMembers : cached.members; const ensured = ensureSystemAdminMember(sourceMembers); const usernameEnsured = ensureMemberUsernames(ensured.members); const next = { departments: cached.departments, members: usernameEnsured.members, roles, passwordRule: cached.passwordRule || DEFAULT_PASSWORD_RULE, }; set({ ...next, loaded: true }); if (ensured.changed || usernameEnsured.changed || rolesChanged) void saveStored(next).catch(() => {}); } else { set({ members: domainMembers && domainMembers.length > 0 ? domainMembers : get().members, loaded: true }); } }, updatePasswordRule: (rule) => { const previous = get().passwordRule; set({ passwordRule: rule }); scheduleSaveWithOptimisticRollback({ save: () => saveStored({ departments: get().departments, members: get().members, 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, members: get().members, 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, members: get().members, 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, members: get().members, 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 { await saveStored({ departments: get().departments, members, roles: get().roles, passwordRule: get().passwordRule }); } }, 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) { try { await saveStored({ departments: get().departments, members: ensured.members, roles: get().roles, passwordRule: get().passwordRule }); } catch { set({ members: previousMembers }); 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 { await saveStored({ departments: get().departments, members, roles: get().roles, passwordRule: get().passwordRule }); } }, 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, members: get().members, 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, members: get().members, 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, members: get().members, roles, passwordRule: get().passwordRule }), expected: roles, getCurrent: () => get().roles, rollback: () => set({ roles: previous }), }); }, }));