176 lines
7.9 KiB
TypeScript
176 lines
7.9 KiB
TypeScript
'use client';
|
||
import { create } from 'zustand';
|
||
import type { Department, Member, RoleItem, PasswordRule } from '@/lib/members';
|
||
import { DEFAULT_PASSWORD_RULE, generatePassword } from '@/lib/members';
|
||
import {
|
||
SYSTEM_ADMIN_MEMBER,
|
||
SYSTEM_ADMIN_ROLE_ID,
|
||
canDeleteSystemMember,
|
||
ensureSystemAdminMember,
|
||
sanitizeSystemAdminPatch,
|
||
} from '@/lib/member-system';
|
||
import { DEFAULT_ROLE_PERMISSIONS } from '@/lib/permissions';
|
||
import { loadServerData, saveServerData } 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[] = [
|
||
{ id: 'role-admin', name: '超级管理员', description: '拥有系统全部权限', createdAt: '2024-01-01', isSystem: true, permissions: DEFAULT_ROLE_PERMISSIONS['role-admin'] },
|
||
{ id: 'role-pm', name: '产品经理', description: '管理产品和需求', createdAt: '2024-01-01', permissions: DEFAULT_ROLE_PERMISSIONS['role-pm'] },
|
||
{ id: 'role-dev', name: '开发工程师', description: '负责开发任务', createdAt: '2024-01-01', permissions: DEFAULT_ROLE_PERMISSIONS['role-dev'] },
|
||
{ id: 'role-test', name: '测试工程师', description: '负责测试任务', createdAt: '2024-01-01', permissions: DEFAULT_ROLE_PERMISSIONS['role-test'] },
|
||
{ id: 'role-design', name: '设计师', description: '负责UI/UX设计', createdAt: '2024-01-01', permissions: DEFAULT_ROLE_PERMISSIONS['role-design'] },
|
||
];
|
||
|
||
const MOCK_MEMBERS: Member[] = [
|
||
SYSTEM_ADMIN_MEMBER,
|
||
];
|
||
|
||
interface MemberState {
|
||
departments: Department[];
|
||
members: Member[];
|
||
roles: RoleItem[];
|
||
passwordRule: PasswordRule;
|
||
loading: boolean;
|
||
fetchMembers: () => void;
|
||
updatePasswordRule: (rule: PasswordRule) => void;
|
||
createDepartment: (data: Omit<Department, 'id' | 'createdAt'>) => void;
|
||
updateDepartment: (id: string, data: Partial<Department>) => void;
|
||
deleteDepartment: (id: string) => void;
|
||
createMember: (data: Omit<Member, 'id' | 'createdAt'>) => void;
|
||
updateMember: (id: string, data: Partial<Member>) => void;
|
||
deleteMember: (id: string) => void;
|
||
createRole: (data: Omit<RoleItem, 'id' | 'createdAt'>) => void;
|
||
updateRole: (id: string, data: Partial<RoleItem>) => void;
|
||
deleteRole: (id: string) => void;
|
||
}
|
||
|
||
function saveStored(state: { departments: Department[]; members: Member[]; roles: RoleItem[]; passwordRule: PasswordRule }) {
|
||
saveServerData('members', state).catch(() => {});
|
||
}
|
||
|
||
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<MemberState>((set, get) => ({
|
||
departments: PRESET_DEPARTMENTS,
|
||
members: MOCK_MEMBERS,
|
||
roles: PRESET_ROLES,
|
||
passwordRule: DEFAULT_PASSWORD_RULE,
|
||
loading: false,
|
||
|
||
fetchMembers: async () => {
|
||
const cached = await loadStored();
|
||
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 adminRole = PRESET_ROLES.find((r) => r.id === SYSTEM_ADMIN_ROLE_ID)!;
|
||
const roles = migratedRoles.some((r) => r.id === SYSTEM_ADMIN_ROLE_ID)
|
||
? migratedRoles.map((r) => r.id === SYSTEM_ADMIN_ROLE_ID ? { ...adminRole, ...r, isSystem: true, permissions: DEFAULT_ROLE_PERMISSIONS[SYSTEM_ADMIN_ROLE_ID] } : r)
|
||
: [adminRole, ...migratedRoles];
|
||
const rolesChanged =
|
||
roles.length !== migratedRoles.length ||
|
||
roles.some((role, index) => JSON.stringify(role) !== JSON.stringify(migratedRoles[index]));
|
||
const ensured = ensureSystemAdminMember(cached.members);
|
||
const next = {
|
||
departments: cached.departments,
|
||
members: ensured.members,
|
||
roles,
|
||
passwordRule: cached.passwordRule || DEFAULT_PASSWORD_RULE,
|
||
};
|
||
set(next);
|
||
if (ensured.changed || rolesChanged) saveStored(next);
|
||
}
|
||
},
|
||
|
||
updatePasswordRule: (rule) => {
|
||
set({ passwordRule: rule });
|
||
saveStored({ departments: get().departments, members: get().members, roles: get().roles, passwordRule: rule });
|
||
},
|
||
|
||
createDepartment: (data) => {
|
||
const dept: Department = { ...data, id: `dept-${Date.now()}`, createdAt: new Date().toISOString().slice(0, 10) };
|
||
const departments = [...get().departments, dept];
|
||
set({ departments });
|
||
saveStored({ departments, members: get().members, roles: get().roles, passwordRule: get().passwordRule });
|
||
},
|
||
updateDepartment: (id, data) => {
|
||
const departments = get().departments.map((d) => d.id === id ? { ...d, ...data } : d);
|
||
set({ departments });
|
||
saveStored({ departments, members: get().members, roles: get().roles, passwordRule: get().passwordRule });
|
||
},
|
||
deleteDepartment: (id) => {
|
||
const departments = get().departments.filter((d) => d.id !== id && d.parentId !== id);
|
||
set({ departments });
|
||
saveStored({ departments, members: get().members, roles: get().roles, passwordRule: get().passwordRule });
|
||
},
|
||
|
||
createMember: (data) => {
|
||
const member: Member = { ...data, id: `m-${Date.now()}`, createdAt: new Date().toISOString().slice(0, 10) };
|
||
const members = [...get().members, member];
|
||
set({ members });
|
||
saveStored({ departments: get().departments, members, roles: get().roles, passwordRule: get().passwordRule });
|
||
},
|
||
updateMember: (id, data) => {
|
||
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 });
|
||
saveStored({ departments: get().departments, members: ensured.members, roles: get().roles, passwordRule: get().passwordRule });
|
||
},
|
||
deleteMember: (id) => {
|
||
if (!canDeleteSystemMember(id)) return;
|
||
const members = get().members.filter((m) => m.id !== id);
|
||
set({ members });
|
||
saveStored({ departments: get().departments, members, roles: get().roles, passwordRule: get().passwordRule });
|
||
},
|
||
|
||
createRole: (data) => {
|
||
const role: RoleItem = {
|
||
...data,
|
||
id: `role-${Date.now()}`,
|
||
createdAt: new Date().toISOString().slice(0, 10),
|
||
permissions: data.permissions ?? [],
|
||
};
|
||
const roles = [...get().roles, role];
|
||
set({ roles });
|
||
saveStored({ departments: get().departments, members: get().members, roles, passwordRule: get().passwordRule });
|
||
},
|
||
updateRole: (id, data) => {
|
||
const roles = get().roles.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 });
|
||
saveStored({ departments: get().departments, members: get().members, roles, passwordRule: get().passwordRule });
|
||
},
|
||
deleteRole: (id) => {
|
||
if (id === SYSTEM_ADMIN_ROLE_ID) return;
|
||
const roles = get().roles.filter((r) => r.id !== id);
|
||
set({ roles });
|
||
saveStored({ departments: get().departments, members: get().members, roles, passwordRule: get().passwordRule });
|
||
},
|
||
}));
|