refactor(data): 收口关系表运行时数据源
Some checks failed
Deploy Production / Build, push, deploy, verify (push) Has been cancelled
Some checks failed
Deploy Production / Build, push, deploy, verify (push) Has been cancelled
- 移除已迁移业务 AppData 运行时 fallback,改走领域 API 和关系表快读 - 补齐需求产品负责人、版本计划任务 JSON 和成员 username 回填迁移 - 统一治理字典入口,并补充 AI provider、数据源契约和领域服务测试 Co-Authored-By: Codex GPT-5 <codex@openai.com>
This commit is contained in:
@@ -18,7 +18,7 @@ import {
|
||||
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';
|
||||
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' },
|
||||
@@ -37,6 +37,19 @@ const MOCK_MEMBERS: 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[];
|
||||
@@ -57,13 +70,13 @@ interface MemberState {
|
||||
deleteRole: (id: string) => void;
|
||||
}
|
||||
|
||||
async function saveStored(state: { departments: Department[]; members: Member[]; roles: RoleItem[]; passwordRule: PasswordRule }) {
|
||||
await saveServerData('members', state);
|
||||
async function saveStored(state: MemberConfigState) {
|
||||
await saveServerData('members', { departments: state.departments, members: [], roles: state.roles, passwordRule: state.passwordRule });
|
||||
}
|
||||
|
||||
async function loadStored(): Promise<{ departments: Department[]; members: Member[]; roles: RoleItem[]; passwordRule: PasswordRule } | null> {
|
||||
async function loadStored(): Promise<StoredMemberConfig | null> {
|
||||
try {
|
||||
return await loadServerData<{ departments: Department[]; members: Member[]; roles: RoleItem[]; passwordRule: PasswordRule }>('members');
|
||||
return await loadServerData<StoredMemberConfig>('members');
|
||||
} catch {}
|
||||
return null;
|
||||
}
|
||||
@@ -86,7 +99,8 @@ export const useMemberStore = create<MemberState>((set, get) => ({
|
||||
lastMembersFetchAt = Date.now();
|
||||
if (cached) {
|
||||
// 迁移:旧 RoleItem 没 permissions 字段,补默认值
|
||||
const migratedRoles = cached.roles.map((r) => {
|
||||
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] ?? [] };
|
||||
}
|
||||
@@ -98,23 +112,21 @@ export const useMemberStore = create<MemberState>((set, get) => ({
|
||||
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 sourceMembers = domainMembers && domainMembers.length > 0 ? domainMembers : get().members;
|
||||
const ensured = ensureSystemAdminMember(sourceMembers);
|
||||
const usernameEnsured = ensureMemberUsernames(ensured.members);
|
||||
const next = {
|
||||
departments: cached.departments,
|
||||
departments: Array.isArray(cached.departments) ? cached.departments : PRESET_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 });
|
||||
const sourceMembers = domainMembers && domainMembers.length > 0 ? domainMembers : get().members;
|
||||
const ensured = ensureSystemAdminMember(sourceMembers);
|
||||
const usernameEnsured = ensureMemberUsernames(ensured.members);
|
||||
set({ members: usernameEnsured.members, loaded: true });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -122,7 +134,7 @@ export const useMemberStore = create<MemberState>((set, get) => ({
|
||||
const previous = get().passwordRule;
|
||||
set({ passwordRule: rule });
|
||||
scheduleSaveWithOptimisticRollback({
|
||||
save: () => saveStored({ departments: get().departments, members: get().members, roles: get().roles, passwordRule: rule }),
|
||||
save: () => saveStored({ departments: get().departments, roles: get().roles, passwordRule: rule }),
|
||||
expected: rule,
|
||||
getCurrent: () => get().passwordRule,
|
||||
rollback: () => set({ passwordRule: previous }),
|
||||
@@ -135,7 +147,7 @@ export const useMemberStore = create<MemberState>((set, get) => ({
|
||||
const departments = [...previous, dept];
|
||||
set({ departments });
|
||||
scheduleSaveWithOptimisticRollback({
|
||||
save: () => saveStored({ departments, members: get().members, roles: get().roles, passwordRule: get().passwordRule }),
|
||||
save: () => saveStored({ departments, roles: get().roles, passwordRule: get().passwordRule }),
|
||||
expected: departments,
|
||||
getCurrent: () => get().departments,
|
||||
rollback: () => set({ departments: previous }),
|
||||
@@ -146,7 +158,7 @@ export const useMemberStore = create<MemberState>((set, get) => ({
|
||||
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 }),
|
||||
save: () => saveStored({ departments, roles: get().roles, passwordRule: get().passwordRule }),
|
||||
expected: departments,
|
||||
getCurrent: () => get().departments,
|
||||
rollback: () => set({ departments: previous }),
|
||||
@@ -157,7 +169,7 @@ export const useMemberStore = create<MemberState>((set, get) => ({
|
||||
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 }),
|
||||
save: () => saveStored({ departments, roles: get().roles, passwordRule: get().passwordRule }),
|
||||
expected: departments,
|
||||
getCurrent: () => get().departments,
|
||||
rollback: () => set({ departments: previous }),
|
||||
@@ -182,8 +194,9 @@ export const useMemberStore = create<MemberState>((set, get) => ({
|
||||
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 });
|
||||
} catch (error) {
|
||||
notifyMemberSaveFailure(error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
expected: members,
|
||||
@@ -200,12 +213,9 @@ export const useMemberStore = create<MemberState>((set, get) => ({
|
||||
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;
|
||||
}
|
||||
set({ members: previousMembers });
|
||||
notifyMemberSaveFailure(error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
deleteMember: (id) => {
|
||||
@@ -217,8 +227,9 @@ export const useMemberStore = create<MemberState>((set, get) => ({
|
||||
save: async () => {
|
||||
try {
|
||||
await deleteMemberDomain(id);
|
||||
} catch {
|
||||
await saveStored({ departments: get().departments, members, roles: get().roles, passwordRule: get().passwordRule });
|
||||
} catch (error) {
|
||||
notifyMemberSaveFailure(error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
expected: members,
|
||||
@@ -238,7 +249,7 @@ export const useMemberStore = create<MemberState>((set, get) => ({
|
||||
const roles = [...previous, role];
|
||||
set({ roles });
|
||||
scheduleSaveWithOptimisticRollback({
|
||||
save: () => saveStored({ departments: get().departments, members: get().members, roles, passwordRule: get().passwordRule }),
|
||||
save: () => saveStored({ departments: get().departments, roles, passwordRule: get().passwordRule }),
|
||||
expected: roles,
|
||||
getCurrent: () => get().roles,
|
||||
rollback: () => set({ roles: previous }),
|
||||
@@ -257,7 +268,7 @@ export const useMemberStore = create<MemberState>((set, get) => ({
|
||||
});
|
||||
set({ roles });
|
||||
scheduleSaveWithOptimisticRollback({
|
||||
save: () => saveStored({ departments: get().departments, members: get().members, roles, passwordRule: get().passwordRule }),
|
||||
save: () => saveStored({ departments: get().departments, roles, passwordRule: get().passwordRule }),
|
||||
expected: roles,
|
||||
getCurrent: () => get().roles,
|
||||
rollback: () => set({ roles: previous }),
|
||||
@@ -269,10 +280,22 @@ export const useMemberStore = create<MemberState>((set, get) => ({
|
||||
const roles = previous.filter((r) => r.id !== id);
|
||||
set({ roles });
|
||||
scheduleSaveWithOptimisticRollback({
|
||||
save: () => saveStored({ departments: get().departments, members: get().members, roles, passwordRule: get().passwordRule }),
|
||||
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 服务',
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user