多用户与角色:新增系统管理员 + 用户管理

- 新增 users 表(scrypt 口令哈希)与持久化层,启动兜底种子账号
- 登录改为后端用户表校验账号密码;JWT 带角色;保留无DB演示回退
- 新增系统管理员角色 + 用户管理页(增删改/改角色/启停/重置密码)
- 用户管理端点按 系统管理员 角色强制校验(RBAC)
- 各角色可建任意多个账号(多销售/多风控/多管理)
- 更新登录页快速登录与首屏快照
This commit is contained in:
freedakgmail
2026-06-13 17:35:52 +08:00
parent 2537e5beef
commit 6562208b13
12 changed files with 563 additions and 42 deletions
+45
View File
@@ -799,6 +799,51 @@ export async function deleteDraftApi(id: string): Promise<void> {
await fetch(`${API_BASE}/api/drafts/${encodeURIComponent(id)}`, { method: 'DELETE', headers: authHeader() });
}
/** 用户角色。 */
export type UserRole = '商务/销售' | '风控' | '管理层' | '系统管理员';
/** 用户账号(不含密码)。 */
export interface UserItem {
id: string;
username: string;
displayName: string | null;
role: UserRole;
active: boolean;
createdAt: string;
}
/** 列出全部用户(系统管理员)。 */
export async function listUsers(): Promise<UserItem[]> {
return request<UserItem[]>('GET', '/api/users');
}
/** 新增用户。 */
export async function createUserApi(input: { username: string; displayName?: string; password: string; role: UserRole }): Promise<UserItem> {
return request<UserItem>('POST', '/api/users', input);
}
/** 更新用户显示名/角色/启用状态。 */
export async function updateUserApi(id: string, patch: { displayName?: string | null; role?: UserRole; active?: boolean }): Promise<UserItem> {
const res = await fetch(`${API_BASE}/api/users/${encodeURIComponent(id)}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', ...authHeader() },
body: JSON.stringify(patch),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new ApiError(res.status, typeof data.error === 'string' ? data.error : `HTTP ${res.status}`);
return data as UserItem;
}
/** 重置用户密码。 */
export async function resetUserPasswordApi(id: string, password: string): Promise<void> {
await request('POST', `/api/users/${encodeURIComponent(id)}/password`, { password });
}
/** 删除用户。 */
export async function deleteUserApi(id: string): Promise<void> {
await fetch(`${API_BASE}/api/users/${encodeURIComponent(id)}`, { method: 'DELETE', headers: authHeader() });
}
/** 方案对比。 */
export interface ScenarioItem {
id: string;