feat: 1. 将大部分的请求从fetch改成axios方便管理。

2. 给文档类型添加入口模块和相关数据的渲染。并且给文档类型进行功能上的角色权限区分
3. 新增角色权限管理页面
This commit is contained in:
2025-11-20 20:34:31 +08:00
parent 2e604e8ede
commit 3850d05bdd
25 changed files with 2299 additions and 762 deletions
+7
View File
@@ -607,6 +607,13 @@ function convertIcon(elementIcon: string | null): string {
if (!elementIcon) {
return 'ri-file-line'; // 默认图标
}
// 如果已经是 RemixIcon 格式(以 ri- 开头),直接返回
if (elementIcon.startsWith('ri-')) {
return elementIcon;
}
// 否则尝试从 Element UI 映射表中查找
return ICON_MAPPING[elementIcon] || 'ri-file-line';
}
+30 -45
View File
@@ -1,4 +1,5 @@
import { postgrestGet, postgrestPut } from "../postgrest-client";
import axios from 'axios';
/**
* 从不同格式的 API 响应中提取数据
@@ -134,26 +135,18 @@ export async function submitCrossCheckingOpinion(
evaluation_result_id: opinionData.reviewPointResultId
};
const response = await fetch(`${API_BASE_URL}/admin/cross_review/proposals`, {
method: 'POST',
const response = await axios.post(`${API_BASE_URL}/admin/cross_review/proposals`, requestData, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(requestData)
}
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || '提交失败');
}
return {
data: {
success: true,
message: '意见提交成功',
data: data
data: response.data
}
};
} catch (error) {
@@ -190,23 +183,19 @@ export async function getCrossCheckingOpinions(
// 如果没传userId,默认用1
const realUserId = userId ?? 1;
// 实际后端API调用,拼接API_BASE_URL
const response = await fetch(`${API_BASE_URL}/admin/cross_review/proposals/document`, {
method: 'POST',
const response = await axios.post(`${API_BASE_URL}/admin/cross_review/proposals/document`, {
user_id: realUserId,
document_id: documentId, // 如果后端需要document_id可以加上
page,
page_size: pageSize
}, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
user_id: realUserId,
document_id: documentId, // 如果后端需要document_id可以加上
page,
page_size: pageSize
})
}
});
if (!response.ok) {
throw new Error('获取意见列表失败');
}
const data = await response.json();
const data = response.data;
console.log('最原始的返回data', data);
// 处理新的数据结构,支持分页
const responseData = data.data || data;
@@ -328,23 +317,24 @@ export async function performOpinionAction(
throw new Error('无效的操作类型');
}
const response = await fetch(endpoint, {
method: actionData.action === 'withdraw_opinion' ? 'DELETE' : 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(requestBody)
});
const response = actionData.action === 'withdraw_opinion'
? await axios.delete(endpoint, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
: await axios.post(endpoint, requestBody, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
const data = await response.json();
const data = response.data;
console.log('返回的意见列表数据',data);
if (!response.ok) {
throw new Error(data.message || data.error || '操作失败');
}
return {
data: {
success: true,
@@ -417,20 +407,15 @@ export async function checkProposalVotes(
document_id: documentId
};
const response = await fetch(`${API_BASE_URL}/admin/cross_review/proposals/document/check_pending_votes`, {
method: 'POST',
const response = await axios.post(`${API_BASE_URL}/admin/cross_review/proposals/document/check_pending_votes`, requestData, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(requestData)
}
});
const data = await response.json();
const data = response.data;
if (!response.ok) {
throw new Error(data.message || '检查失败');
}
console.log("检查投票数据",data);
return {
+27 -43
View File
@@ -1,4 +1,5 @@
import { UPLOAD_URL } from '../../config/api-config';
import axios from 'axios';
/**
* 从不同格式的 API 响应中提取数据
@@ -146,8 +147,8 @@ export async function uploadCrossCheckingDocument(
// 发送请求
try {
console.log('【交叉评查上传】开始fetch请求...');
const headers: HeadersInit = {
console.log('【交叉评查上传】开始axios请求...');
const headers: Record<string, string> = {
'X-File-Name': encodeURIComponent(fileName),
};
@@ -155,50 +156,35 @@ export async function uploadCrossCheckingDocument(
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(uploadUrl, {
method: 'POST',
headers,
body: formData
const response = await axios.post(uploadUrl, formData, {
headers
});
console.log('【交叉评查上传】收到服务器响应:', { status: response.status, statusText: response.statusText });
if (!response.ok) {
const errorText = await response.text();
console.error(`【交叉评查上传】上传失败 (${response.status}): ${errorText}`);
return {
error: `上传失败: ${response.status} ${response.statusText} - ${errorText}`,
status: response.status
};
}
console.log('【交叉评查上传】开始解析JSON响应');
let responseData;
try {
responseData = await response.json();
console.log('【交叉评查上传】JSON响应解析成功:', responseData);
} catch (jsonError) {
console.error('【交叉评查上传】JSON解析失败:', jsonError);
return {
error: `解析响应JSON失败: ${jsonError instanceof Error ? jsonError.message : '未知错误'}`,
status: 500
};
}
const extractedData = extractApiData<CrossCheckingFileUploadResponse>(responseData);
console.log('【交叉评查上传】JSON响应解析成功:', response.data);
const extractedData = extractApiData<CrossCheckingFileUploadResponse>(response.data);
console.log('【交叉评查上传】提取的数据:', extractedData);
if (!extractedData) {
console.error('【交叉评查上传】无法提取数据');
return { error: '处理上传响应失败', status: 500 };
}
console.log('【交叉评查上传】上传成功,返回数据');
return { data: extractedData as CrossCheckingFileUploadResponse };
} catch (fetchError) {
console.error('【交叉评查上传】fetch请求失败:', fetchError);
return {
error: `fetch请求错误: ${fetchError instanceof Error ? fetchError.message : '未知错误'}`,
} catch (axiosError) {
console.error('【交叉评查上传】axios请求失败:', axiosError);
if (axios.isAxiosError(axiosError)) {
const errorText = axiosError.response?.data || axiosError.message;
return {
error: `上传失败: ${axiosError.response?.status || 500} ${axiosError.response?.statusText || ''} - ${errorText}`,
status: axiosError.response?.status || 500
};
}
return {
error: `axios请求错误: ${axiosError instanceof Error ? axiosError.message : '未知错误'}`,
status: 500
};
}
@@ -258,14 +244,12 @@ export async function batchUploadAndAssignCrossCheckingFiles(
};
formData.append('upload_info', JSON.stringify(uploadInfo));
formData.append('assign_user_ids', JSON.stringify(assignUserIds));
const headers: HeadersInit = {};
const headers: Record<string, string> = {};
if (token) headers['Authorization'] = `Bearer ${token}`;
const response = await fetch(uploadUrl, {
method: 'POST',
headers,
body: formData
const response = await axios.post(uploadUrl, formData, {
headers
});
const result = await response.json();
const result = response.data;
if (result && result.success) {
successes.push({ file: fileInfo, result });
} else {
+35 -44
View File
@@ -1,5 +1,6 @@
import { API_BASE_URL } from '../../config/api-config';
import { postgrestPut } from '../postgrest-client';
import axios from 'axios';
// 交叉评查任务状态枚举
export enum CrossCheckingTaskStatus {
@@ -393,33 +394,28 @@ export async function getUserTaskDocuments(page: number = 1, pageSize: number =
// 拼接绝对路径,去除多余斜杠
const base = API_BASE_URL.endsWith('/') ? API_BASE_URL.slice(0, -1) : API_BASE_URL;
const url = `${base}/admin/cross_review/tasks/user_tasks`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${jwtToken || ''}`
},
body: JSON.stringify({
page: page,
page_size: pageSize
})
const response = await axios.post(url, {
page: page,
page_size: pageSize
}, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${jwtToken || ''}`
}
});
if (!response.ok) {
return {
success: false,
error: `HTTP ${response.status}: ${response.statusText}`
};
}
const result = await response.json();
return {
success: true,
data: result
data: response.data
};
} catch (error) {
if (axios.isAxiosError(error)) {
return {
success: false,
error: `HTTP ${error.response?.status}: ${error.response?.statusText || error.message}`
};
}
return {
success: false,
error: error instanceof Error ? error.message : '获取用户任务列表失败'
@@ -441,33 +437,28 @@ export async function getTaskDocuments(taskId: number, page: number = 1, pageSiz
const base = API_BASE_URL.endsWith('/') ? API_BASE_URL.slice(0, -1) : API_BASE_URL;
const url = `${base}/admin/cross_review/tasks/${taskId}/documents`;
// console.log('最终请求URL:', url);
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${jwtToken || ''}`
},
body: JSON.stringify({
page: page,
page_size: pageSize
})
const response = await axios.post(url, {
page: page,
page_size: pageSize
}, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${jwtToken || ''}`
}
});
if (!response.ok) {
return {
success: false,
error: `HTTP ${response.status}: ${response.statusText}`
};
}
const result = await response.json();
return {
success: true,
data: result
data: response.data
};
} catch (error) {
if (axios.isAxiosError(error)) {
return {
success: false,
error: `HTTP ${error.response?.status}: ${error.response?.statusText || error.message}`
};
}
return {
success: false,
error: error instanceof Error ? error.message : '获取任务文档列表失败'
+184 -33
View File
@@ -25,6 +25,10 @@ export interface DocumentTypeUI {
name: string;
description: string;
groups: DocumentTypeGroup[];
entry_module?: {
id: number;
name: string;
} | null;
llm_extraction_template_id?: number | null;
vlm_extraction_template_id?: number | null;
evaluation_template_id?: number | null;
@@ -39,6 +43,7 @@ export interface DocumentTypeCreateDTO {
name: string;
description?: string;
group_ids: string[];
entry_module_id?: number | null;
llm_extraction_template_id?: number | null;
vlm_extraction_template_id?: number | null;
evaluation_template_id?: number | null;
@@ -108,27 +113,27 @@ export async function getAllEvaluationPointGroups(token?: string): Promise<{
id: number;
name: string;
}>>('evaluation_point_groups', params);
if (response.error) {
return { error: response.error, status: response.status };
}
// 使用extractApiData提取数据
const extractedData = extractApiData<Array<{
id: number;
name: string;
}>>(response.data);
if (!extractedData) {
return { data: [] };
}
// 转换为DocumentTypeGroup格式
const groups: DocumentTypeGroup[] = extractedData.map(item => ({
id: item.id.toString(),
name: item.name
}));
return { data: groups };
} catch (error) {
console.error('获取所有评查点分组失败:', error);
@@ -136,6 +141,101 @@ export async function getAllEvaluationPointGroups(token?: string): Promise<{
}
}
/**
* 获取父级评查分组(pid=0的分组)
* @param token JWT token (可选)
* @returns 父级评查点分组列表
*/
export async function getParentEvaluationPointGroups(token?: string): Promise<{
data?: DocumentTypeGroup[];
error?: string;
status?: number;
}> {
try {
const params: PostgrestParams = {
select: 'id, name',
filter: {
'pid': 'eq.0'
},
order: 'id.asc',
token
};
const response = await postgrestGet<Array<{
id: number;
name: string;
}>>('evaluation_point_groups', params);
if (response.error) {
return { error: response.error, status: response.status };
}
// 使用extractApiData提取数据
const extractedData = extractApiData<Array<{
id: number;
name: string;
}>>(response.data);
if (!extractedData) {
return { data: [] };
}
// 转换为DocumentTypeGroup格式
const groups: DocumentTypeGroup[] = extractedData.map(item => ({
id: item.id.toString(),
name: item.name
}));
return { data: groups };
} catch (error) {
console.error('获取父级评查点分组失败:', error);
return { error: error instanceof Error ? error.message : '获取父级评查点分组失败' };
}
}
/**
* 获取所有入口模块
* @param token JWT token (可选)
* @returns 入口模块列表
*/
export async function getEntryModules(token?: string): Promise<{
data?: Array<{ id: number; name: string }>;
error?: string;
status?: number;
}> {
try {
const params: PostgrestParams = {
select: 'id, name',
order: 'id.asc',
token
};
const response = await postgrestGet<Array<{
id: number;
name: string;
}>>('entry_modules', params);
if (response.error) {
return { error: response.error, status: response.status };
}
// 使用extractApiData提取数据
const extractedData = extractApiData<Array<{
id: number;
name: string;
}>>(response.data);
if (!extractedData) {
return { data: [] };
}
return { data: extractedData };
} catch (error) {
console.error('获取入口模块失败:', error);
return { error: error instanceof Error ? error.message : '获取入口模块失败' };
}
}
/**
* 根据ID获取评查点分组信息
* @param ids 评查点分组ID数组
@@ -216,19 +316,22 @@ export async function getDocumentTypes(searchParams: DocumentTypeSearchParams =
try {
const page = searchParams.page || 1;
const pageSize = searchParams.pageSize || 10;
// 构建查询参数
// 构建查询参数,使用 PostgREST 的资源嵌入语法来关联查询
// 使用外键约束名称进行关联:entry_modules!fk_document_types_entry_module
const params: PostgrestParams = {
select: `
id,
name,
description,
evaluation_point_groups_ids,
entry_module_id,
entry_modules!fk_document_types_entry_module(id, name),
prompt_config,
created_at,
updated_at,
code
`,
`.replace(/\s+/g,' ').trim(),
order: 'updated_at.desc',
headers: {
'Prefer': 'count=exact'
@@ -238,13 +341,13 @@ export async function getDocumentTypes(searchParams: DocumentTypeSearchParams =
filter: {} as Record<string, string>,
token: frontendJWT
};
// 添加筛选条件
const filter: Record<string, string> = {};
if (searchParams.name) {
filter['name'] = `ilike.%${searchParams.name}%`;
}
// 如果有分组ID筛选条件
if (searchParams.ruleType) {
filter['evaluation_point_groups_ids'] = `cs.[${searchParams.ruleType}]`;
@@ -264,32 +367,70 @@ export async function getDocumentTypes(searchParams: DocumentTypeSearchParams =
}
params.filter = filter;
// console.log('获取文档类型列表,参数:', params);
const response = await postgrestGet<DocumentType[]>('document_types', params);
const response = await postgrestGet<(DocumentType & {
entry_modules: { id: number; name: string } | null;
})[]>('document_types', params);
if (response.error) {
return { error: response.error, status: response.status };
}
// 使用extractApiData提取数据
const extractedData = extractApiData<DocumentType[]>(response.data);
const documentTypes = extractedData || [];
// console.log('提取的文档类型数据:', documentTypes);
// 🔧 优化:移除评查点分组查询(文档列表UI不需要此数据)
// 直接转换为UI类型,不查询关联的分组信息
const uiTypes = documentTypes.map(type => ({
...convertToUIDocumentType(type),
groups: [] // 保持接口兼容性,但不填充数据
}));
// console.log('提取的文档类型数据:', JSON.stringify(response));
// 使用extractApiData提取数据
const extractedData = extractApiData<(DocumentType & {
entry_modules: { id: number; name: string } | null;
})[]>(response.data);
const documentTypes = extractedData || [];
// 并发查询所有需要的评查点分组信息
const allGroupIds = new Set<number>();
documentTypes.forEach(type => {
if (type.evaluation_point_groups_ids) {
const ids = Array.isArray(type.evaluation_point_groups_ids)
? type.evaluation_point_groups_ids
: [type.evaluation_point_groups_ids as unknown as number];
ids.forEach(id => allGroupIds.add(id));
}
});
// 如果有分组ID,查询所有分组信息
let groupsMap: Map<number, DocumentTypeGroup> = new Map();
if (allGroupIds.size > 0) {
const groupsResponse = await getEvaluationPointGroupsByIds(Array.from(allGroupIds), frontendJWT);
if (groupsResponse.data) {
groupsResponse.data.forEach(group => {
groupsMap.set(parseInt(group.id, 10), group);
});
}
}
// 转换为UI类型,包含entry_module和groups信息
const uiTypes = documentTypes.map(type => {
// 获取该文档类型关联的分组
let typeGroups: DocumentTypeGroup[] = [];
if (type.evaluation_point_groups_ids) {
const ids = Array.isArray(type.evaluation_point_groups_ids)
? type.evaluation_point_groups_ids
: [type.evaluation_point_groups_ids as unknown as number];
typeGroups = ids.map(id => groupsMap.get(id)).filter(Boolean) as DocumentTypeGroup[];
}
return {
...convertToUIDocumentType({ ...type, groups: typeGroups }),
entry_module: type.entry_modules || null,
groups: typeGroups
};
});
// 获取总数
let totalCount = 0;
const responseWithHeaders = response as {
data: unknown;
headers: Record<string, string>
const responseWithHeaders = response as {
data: unknown;
headers: Record<string, string>
};
if (responseWithHeaders.headers) {
const rangeHeader = responseWithHeaders.headers['content-range'];
@@ -300,7 +441,7 @@ export async function getDocumentTypes(searchParams: DocumentTypeSearchParams =
}
}
}
return {
data: {
types: uiTypes,
@@ -360,7 +501,10 @@ export async function deleteDocumentType(id: string, frontendJWT?: string): Prom
/**
* 将API返回的文档类型转换为UI文档类型
*/
function convertToUIDocumentType(type: DocumentType & { groups: DocumentTypeGroup[] }): DocumentTypeUI {
function convertToUIDocumentType(type: DocumentType & {
groups: DocumentTypeGroup[];
entry_modules?: { id: number; name: string } | null;
}): DocumentTypeUI {
// 提取提示词模板ID,确保安全处理以避免控制台警告
let llmExtractionTemplateId: number | null = null;
let vlmExtractionTemplateId: number | null = null;
@@ -396,6 +540,7 @@ function convertToUIDocumentType(type: DocumentType & { groups: DocumentTypeGrou
name: type.name,
description: type.description || '',
groups: type.groups || [],
entry_module: type.entry_modules || null,
llm_extraction_template_id: llmExtractionTemplateId,
vlm_extraction_template_id: vlmExtractionTemplateId,
evaluation_template_id: evaluationTemplateId,
@@ -428,18 +573,22 @@ export async function getDocumentType(id: string, frontendJWT?: string): Promise
name,
description,
evaluation_point_groups_ids,
entry_module_id,
entry_modules!fk_document_types_entry_module(id, name),
prompt_config,
created_at,
updated_at,
code
`,
`.replace(/\s+/g,' ').trim(),
filter: {
'id': `eq.${id}`
},
token: frontendJWT
};
const response = await postgrestGet<DocumentType[]>('document_types', params);
const response = await postgrestGet<(DocumentType & {
entry_modules: { id: number; name: string } | null;
})[]>('document_types', params);
if (response.error) {
return { error: response.error, status: response.status };
@@ -573,6 +722,7 @@ export async function createDocumentType(documentType: DocumentTypeCreateDTO, fr
name: documentType.name.trim(),
description: documentType.description || '',
evaluation_point_groups_ids: groupIds,
entry_module_id: documentType.entry_module_id || null,
prompt_config: promptConfig,
// code: documentType.code || null
};
@@ -698,6 +848,7 @@ export async function updateDocumentType(id: string, documentType: DocumentTypeU
name: documentType.name.trim(),
description: documentType.description || '',
evaluation_point_groups_ids: groupIds,
entry_module_id: documentType.entry_module_id || null,
prompt_config: promptConfig
};
+1 -10
View File
@@ -802,19 +802,10 @@ export interface RuleGroup {
*/
export async function getRuleTypes(documentTypeIds?: number[], token?: string): Promise<{data: RuleType[]; error?: never} | {data?: never; error: string; status?: number}> {
try {
// 如果没有传入 documentTypeIds,返回空数组
if (!documentTypeIds || documentTypeIds.length === 0) {
console.warn('getRuleTypes: 未提供 documentTypeIds');
return { data: [] };
}
// 1️⃣ 根据 documentTypeIds 查询 document_types 表
const typeIdsStr = documentTypeIds.join(',');
const documentTypesParams: PostgrestParams = {
select: 'id, name, evaluation_point_groups_ids',
filter: {
'id': `in.(${typeIdsStr})`
},
filter: {},
token
};
-36
View File
@@ -384,42 +384,6 @@ export async function getDocumentWithNoUserId(id: string, frontendJWT?: string):
/**
* 获取文件下载链接
* @param filePath 文件路径
* @returns 下载链接
*/
export async function getFileDownloadUrl(filePath: string): Promise<{
data?: { downloadUrl: string };
error?: string;
status?: number;
}> {
try {
if (!filePath) {
return { error: '文件路径不能为空', status: 400 };
}
// 这里应该调用获取文件下载链接的API
// 假设后端有这样的端点:/api/files/generate-download-url?path=xxx
// 实际项目中需要根据你的后端API调整
// 临时解决方案:返回Remix路由路径
// 这将通过Remix服务器代理对文件的访问
return {
data: {
downloadUrl: `/documents/download?path=${encodeURIComponent(filePath)}`
}
};
} catch (error) {
console.error('获取文件下载链接失败:', error);
return {
error: error instanceof Error ? error.message : '获取文件下载链接失败',
status: 500
};
}
}
/**
* 更新文档信息
* @param id 文档ID
+35 -70
View File
@@ -1,6 +1,7 @@
import { postgrestGet, type PostgrestParams } from '../postgrest-client';
import dayjs from 'dayjs';
import { UPLOAD_URL } from '../../config/api-config';
import axios from 'axios';
// import { API_BASE_URL } from '../client';
/**
@@ -213,26 +214,15 @@ export async function uploadContractTemplate(
}
// 发送请求
const response = await fetch(uploadUrl, {
method: 'POST',
headers,
body: formData
const response = await axios.post(uploadUrl, formData, {
headers
});
console.log('【合同模板上传】服务器响应状态:', response.status);
if (!response.ok) {
const errorText = await response.text();
console.error('【合同模板上传】服务器返回错误:', errorText);
return {
error: `服务器错误: ${response.status} ${response.statusText}`,
status: response.status
};
}
const result = await response.json();
const result = response.data;
console.log('【合同模板上传】服务器返回结果:', result);
if (result.success) {
return { data: result.result };
} else {
@@ -299,26 +289,15 @@ export async function appendContractAttachments(
}
// 发送请求
const response = await fetch(uploadUrl, {
method: 'POST',
headers,
body: formData
const response = await axios.post(uploadUrl, formData, {
headers
});
console.log('【合同附件追加】服务器响应状态:', response.status);
if (!response.ok) {
const errorText = await response.text();
console.error('【合同附件追加】服务器返回错误:', errorText);
return {
error: `服务器错误: ${response.status} ${response.statusText}`,
status: response.status
};
}
const result = await response.json();
const result = response.data;
console.log('【合同附件追加】服务器返回结果:', result);
if (result.success) {
return { data: result.result };
} else {
@@ -388,12 +367,12 @@ export async function uploadDocumentToServer(
// console.log('【调试】准备发送请求到服务器:', uploadUrl);
// 发送请求
// const response = await fetch(`${API_BASE_URL}/admin/documents/upload`, {
// const response = await axios.post(`${API_BASE_URL}/admin/documents/upload`, ...
try {
// console.log('【调试】开始fetch请求...');
// console.log('【调试】开始axios请求...');
// 构建请求头,只在有JWT token时添加Authorization
const headers: HeadersInit = {
const headers: Record<string, string> = {
'X-File-Name': encodeURIComponent(fileName)
};
@@ -401,37 +380,16 @@ export async function uploadDocumentToServer(
headers['Authorization'] = `Bearer ${jwtToken}`;
}
const response = await fetch(uploadUrl, {
method: 'POST',
headers,
body: formData
const response = await axios.post(uploadUrl, formData, {
headers
});
// console.log('【调试】收到服务器响应:', { status: response.status, statusText: response.statusText });
if (!response.ok) {
const errorText = await response.text();
console.error(`【调试】上传失败 (${response.status}): ${errorText}`);
return {
error: `上传失败: ${response.status} ${response.statusText} - ${errorText}`,
status: response.status
};
}
// console.log('【调试】开始解析JSON响应');
let responseData;
try {
responseData = await response.json();
// console.log('【上传调试】服务器原始JSON响应:', responseData);
// console.log('【上传调试】响应类型:', typeof responseData);
// console.log('【上传调试】响应keys:', Object.keys(responseData));
} catch (jsonError) {
console.error('【调试】JSON解析失败:', jsonError);
return {
error: `解析响应JSON失败: ${jsonError instanceof Error ? jsonError.message : '未知错误'}`,
status: 500
};
}
const responseData = response.data;
// console.log('【上传调试】服务器原始JSON响应:', responseData);
// console.log('【上传调试】响应类型:', typeof responseData);
// console.log('【上传调试】响应keys:', Object.keys(responseData));
const extractedData = extractApiData<FileUploadResponse>(responseData);
// console.log('【上传调试】提取后的数据:', extractedData);
@@ -449,10 +407,17 @@ export async function uploadDocumentToServer(
// console.log('【调试】上传成功,返回数据');
return { data: extractedData };
} catch (fetchError) {
console.error('【调试】fetch请求失败:', fetchError);
return {
error: `fetch请求错误: ${fetchError instanceof Error ? fetchError.message : '未知错误'}`,
} catch (axiosError) {
console.error('【调试】axios请求失败:', axiosError);
if (axios.isAxiosError(axiosError)) {
const errorText = axiosError.response?.data || axiosError.message;
return {
error: `上传失败: ${axiosError.response?.status || 500} ${axiosError.response?.statusText || ''} - ${errorText}`,
status: axiosError.response?.status || 500
};
}
return {
error: `axios请求错误: ${axiosError instanceof Error ? axiosError.message : '未知错误'}`,
status: 500
};
}
+23 -22
View File
@@ -21,6 +21,7 @@ import { createCookieSessionStorage } from "@remix-run/node";
import { postgrestGet, postgrestPost, postgrestPut } from "../postgrest-client";
import { JWTUtils, type UserInfoForJWT } from "~/utils/jwt";
import { OAUTH_CONFIG, API_BASE_URL } from "~/config/api-config";
import axios from 'axios';
/**
* 用户角色类型定义
@@ -95,7 +96,7 @@ export const sessionStorage = createCookieSessionStorage({
path: "/", // Cookie 作用域为整个应用
sameSite: "lax", // CSRF 保护,允许顶级导航
secrets: ["s3cr3t"], // TODO: 应该从环境变量读取
maxAge: 60 * 60 * 2, // 2小时,与 OAuth Token 同步
maxAge: 60 * 60 * 8, // 8小时,确保大于等于JWT token最大有效期(通常为6小时)
secure: false, // 开发环境中禁用 HTTPS 要求
},
});
@@ -369,12 +370,16 @@ export async function createUserSession(params: {
if (params.frontendJWT) {
session.set("frontendJWT", params.frontendJWT);
}
const cookie = await sessionStorage.commitSession(session);
// console.log("创建完整会话 - 设置Cookie:", !!cookie);
// console.log("创建完整会话 - 用户角色:", params.userRole);
// console.log("创建完整会话 - 重定向到:", params.redirectTo);
// 🔑 根据 tokenExpiresIn 动态设置 Cookie 的 maxAge
// 如果有 tokenExpiresIn,使用它作为 Cookie 有效期;否则使用默认值(8小时)
const cookieMaxAge = params.tokenExpiresIn || (60 * 60 * 8); // 默认8小时
// console.log("🍪 [createUserSession] Cookie maxAge:", cookieMaxAge, "秒 (", (cookieMaxAge / 3600).toFixed(2), "小时)");
const cookie = await sessionStorage.commitSession(session, {
maxAge: cookieMaxAge // 🔑 动态设置 Cookie 有效期
});
return new Response(null, {
status: 302, // HTTP 重定向状态码
headers: {
@@ -487,20 +492,18 @@ async function callIDaaSLogout(accessToken: string, appId: string): Promise<void
formData.append('redirect_url', encodeURIComponent(redirectUri));
try {
const response = await fetch(logoutUrl, {
method: 'POST',
const response = await axios.post(logoutUrl, formData.toString(), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: formData.toString(),
});
if (!response.ok) {
throw new Error(`IDaaS登出失败: ${response.status} ${response.statusText}`);
}
console.log("IDaaS单点登出请求成功");
} catch (error) {
if (axios.isAxiosError(error)) {
console.error("调用IDaaS登出接口失败:", error.response?.status, error.response?.statusText);
throw new Error(`IDaaS登出失败: ${error.response?.status} ${error.response?.statusText}`);
}
console.error("调用IDaaS登出接口失败:", error);
throw error;
}
@@ -775,18 +778,16 @@ export async function simpleRootLogin(
}
// 调用登录接口
const loginResponse = await fetch(`${API_BASE_URL}/password_login`, {
method: 'POST',
const loginResponse = await axios.post(`${API_BASE_URL}/password_login`, {
sub: username.trim(),
password: password.trim()
}, {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
sub: username.trim(),
password: password.trim()
})
}
});
const loginResult = await loginResponse.json();
const loginResult = loginResponse.data;
console.log('登录接口返回', loginResult);
// 检查重试次数
+22 -29
View File
@@ -4,6 +4,7 @@
*/
import { API_BASE_URL } from "~/config/api-config";
import axios from 'axios';
/**
* 登录请求参数(OAuth 方式)
@@ -64,30 +65,26 @@ export async function loginWithOAuth(loginData: LoginRequest): Promise<LoginResp
console.log("📝 [Login Client] 调用后端 OAuth 登录接口:", loginUrl);
try {
const response = await fetch(loginUrl, {
method: "POST",
const response = await axios.post(loginUrl, loginData, {
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify(loginData)
}
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
console.error("❌ [Login Client] OAuth 登录请求失败:", response.status, errorData);
console.log("✅ [Login Client] OAuth 登录成功");
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
const errorData = error.response?.data || {};
console.error("❌ [Login Client] OAuth 登录请求失败:", error.response?.status, errorData);
return {
success: false,
error: errorData.error || errorData.message || `登录失败: ${response.status}`
error: errorData.error || errorData.message || `登录失败: ${error.response?.status || 'Unknown'}`
};
}
const data = await response.json();
console.log("✅ [Login Client] OAuth 登录成功");
return data;
} catch (error) {
console.error("❌ [Login Client] OAuth 登录请求异常:", error);
return {
success: false,
@@ -120,33 +117,29 @@ export async function loginWithPassword(
console.log("📝 [Login Client] 调用后端密码登录接口:", loginUrl);
try {
const response = await fetch(loginUrl, {
method: "POST",
const response = await axios.post(loginUrl, {
username,
password
}, {
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({
username,
password
})
}
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
console.error("❌ [Login Client] 密码登录请求失败:", response.status, errorData);
console.log("✅ [Login Client] 密码登录成功");
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
const errorData = error.response?.data || {};
console.error("❌ [Login Client] 密码登录请求失败:", error.response?.status, errorData);
return {
success: false,
error: errorData.error || errorData.message || `登录失败: ${response.status}`
error: errorData.error || errorData.message || `登录失败: ${error.response?.status || 'Unknown'}`
};
}
const data = await response.json();
console.log("✅ [Login Client] 密码登录成功");
return data;
} catch (error) {
console.error("❌ [Login Client] 密码登录请求异常:", error);
return {
success: false,
+52 -77
View File
@@ -6,6 +6,8 @@
* 2. 如果需要新的网络请求,在 `OAuthClient` 中添加
*/
import axios from 'axios';
interface OAuthConfig {
serverUrl: string;
clientId: string;
@@ -114,46 +116,38 @@ export class OAuthClient {
});
try {
// 创建 AbortController 用于超时控制
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 15000); // 15秒超时
const response = await fetch(url, {
method: 'POST',
const response = await axios.post(url, data, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: data,
signal: controller.signal
timeout: 60000 // 60秒超时
});
clearTimeout(timeoutId);
console.log('🔧 Token响应状态:', response.status, response.statusText);
if (!response.ok) {
const errorData = await response.json();
console.error('❌ 获取访问令牌失败:', {
status: response.status,
statusText: response.statusText,
errorData: errorData
});
return null;
}
const tokenResponse = await response.json() as TokenResponse;
const tokenResponse = response.data as TokenResponse;
console.log('✅ 获取访问令牌成功:', {
token_type: tokenResponse.token_type,
expires_in: tokenResponse.expires_in,
scope: tokenResponse.scope
});
return tokenResponse;
} catch (error) {
// 判断是否为超时错误
if (error instanceof Error && error.name === 'AbortError') {
console.error('❌ 获取访问令牌超时(15秒):', error.message);
if (axios.isAxiosError(error)) {
if (error.code === 'ECONNABORTED') {
console.error('❌ 获取访问令牌超时(60秒):', error.message);
} else if (error.response) {
console.error('❌ 获取访问令牌失败:', {
status: error.response.status,
statusText: error.response.statusText,
errorData: error.response.data
});
} else {
console.error('❌ 获取访问令牌网络错误:', error.message);
}
} else {
console.error('❌ 获取访问令牌网络错误:', error);
console.error('❌ 获取访问令牌错误:', error);
}
return null;
}
@@ -168,31 +162,25 @@ export class OAuthClient {
const url = `${this.config.serverUrl}/api/bff/v1.2/oauth2/userinfo`;
try {
// 创建 AbortController 用于超时控制
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 15000); // 15秒超时
const response = await fetch(url, {
const response = await axios.get(url, {
headers: {
'Authorization': `Bearer ${accessToken}`
},
signal: controller.signal
timeout: 60000 // 60秒超时
});
clearTimeout(timeoutId);
if (!response.ok) {
console.error('获取用户信息失败:', response.status, response.statusText);
return null;
}
return await response.json() as UserInfoResponse;
return response.data as UserInfoResponse;
} catch (error) {
// 判断是否为超时错误
if (error instanceof Error && error.name === 'AbortError') {
console.error('❌ 获取用户信息超时(15秒):', error.message);
if (axios.isAxiosError(error)) {
if (error.code === 'ECONNABORTED') {
console.error('❌ 获取用户信息超时(60秒):', error.message);
} else if (error.response) {
console.error('获取用户信息失败:', error.response.status, error.response.statusText);
} else {
console.error('❌ 获取用户信息网络错误:', error.message);
}
} else {
console.error('❌ 获取用户信息网络错误:', error);
console.error('❌ 获取用户信息错误:', error);
}
return null;
}
@@ -219,34 +207,25 @@ export class OAuthClient {
});
try {
// 创建 AbortController 用于超时控制
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 15000); // 15秒超时
const response = await fetch(url, {
method: 'POST',
const response = await axios.post(url, data, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: data,
signal: controller.signal
timeout: 60000 // 60秒超时
});
clearTimeout(timeoutId);
if (!response.ok) {
const errorData = await response.json();
console.error('刷新访问令牌失败:', errorData);
return null;
}
return await response.json() as TokenResponse;
return response.data as TokenResponse;
} catch (error) {
// 判断是否为超时错误
if (error instanceof Error && error.name === 'AbortError') {
console.error('❌ 刷新访问令牌超时(15秒):', error.message);
if (axios.isAxiosError(error)) {
if (error.code === 'ECONNABORTED') {
console.error('❌ 刷新访问令牌超时(60秒):', error.message);
} else if (error.response) {
console.error('刷新访问令牌失败:', error.response.data);
} else {
console.error('❌ 刷新访问令牌网络错误:', error.message);
}
} else {
console.error('❌ 刷新访问令牌网络错误:', error);
console.error('❌ 刷新访问令牌错误:', error);
}
return null;
}
@@ -266,25 +245,21 @@ export class OAuthClient {
});
try {
// 创建 AbortController 用于超时控制
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 15000); // 15秒超时
const response = await fetch(url, {
method: 'POST',
const response = await axios.post(url, data, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: data,
signal: controller.signal
timeout: 60000 // 60秒超时
});
clearTimeout(timeoutId);
return response.ok;
return response.status >= 200 && response.status < 300;
} catch (error) {
// 判断是否为超时错误
if (error instanceof Error && error.name === 'AbortError') {
console.error('❌ 登出超时(15秒):', error.message);
if (axios.isAxiosError(error)) {
if (error.code === 'ECONNABORTED') {
console.error('❌ 登出超时(60秒):', error.message);
} else {
console.error('❌ 登出失败:', error);
}
} else {
console.error('❌ 登出失败:', error);
}
@@ -0,0 +1,560 @@
/**
* 角色权限管理 API
* 用于角色、路由权限、用户角色的管理
*/
// ==================== 类型定义 ====================
/**
* 路由信息
*/
export interface RouteInfo {
id: number;
route_path: string;
route_name: string;
route_title: string;
component?: string;
parent_id?: number | null;
icon?: string;
sort_order: number;
is_hidden: boolean;
is_cache: boolean;
status: number;
children?: RouteInfo[];
}
/**
* 角色信息
*/
export interface RoleInfo {
id: number;
role_key: string;
role_name: string;
data_scope: string;
description: string;
parent_role_id?: number | null;
priority: number;
is_system_role: boolean;
created_at: string;
updated_at: string;
}
/**
* 角色-路由权限关联
*/
export interface RoleRoutePermission {
id: number;
role_id: number;
route_id: number;
permission: string; // 'R' | 'RW' | 'NONE'
created_at: string;
}
/**
* 用户信息
*/
export interface UserInfo {
id: number;
username: string;
nick_name: string;
phone_number?: string;
email?: string;
ou_name: string;
status: number;
is_leader: boolean;
}
/**
* 用户-角色关联
*/
export interface UserRoleRelation {
id: number;
user_id: number;
role_id: number;
created_at: string;
}
// ==================== 模拟数据 ====================
/**
* 模拟路由数据(树形结构)
*/
const mockRoutes: RouteInfo[] = [
{
id: 1,
route_path: '/documents',
route_name: 'documents',
route_title: '文档管理',
icon: 'ri-file-text-line',
sort_order: 1,
is_hidden: false,
is_cache: true,
status: 1,
parent_id: null,
children: [
{
id: 11,
route_path: '/documents/list',
route_name: 'documents-list',
route_title: '文档列表',
icon: 'ri-list-check',
sort_order: 1,
is_hidden: false,
is_cache: true,
status: 1,
parent_id: 1
},
{
id: 12,
route_path: '/documents/upload',
route_name: 'documents-upload',
route_title: '文档上传',
icon: 'ri-upload-line',
sort_order: 2,
is_hidden: false,
is_cache: true,
status: 1,
parent_id: 1
}
]
},
{
id: 2,
route_path: '/cross-checking',
route_name: 'cross-checking',
route_title: '交叉评查',
icon: 'ri-exchange-line',
sort_order: 2,
is_hidden: false,
is_cache: true,
status: 1,
parent_id: null,
children: [
{
id: 21,
route_path: '/cross-checking/tasks',
route_name: 'cross-checking-tasks',
route_title: '评查任务',
icon: 'ri-task-line',
sort_order: 1,
is_hidden: false,
is_cache: true,
status: 1,
parent_id: 2
}
]
},
{
id: 3,
route_path: '/settings',
route_name: 'settings',
route_title: '系统设置',
icon: 'ri-settings-3-line',
sort_order: 3,
is_hidden: false,
is_cache: true,
status: 1,
parent_id: null,
children: [
{
id: 31,
route_path: '/settings/document-types',
route_name: 'document-types',
route_title: '文档类型管理',
icon: 'ri-file-list-line',
sort_order: 1,
is_hidden: false,
is_cache: true,
status: 1,
parent_id: 3
},
{
id: 32,
route_path: '/settings/rule-groups',
route_name: 'rule-groups',
route_title: '评查点分组',
icon: 'ri-folder-line',
sort_order: 2,
is_hidden: false,
is_cache: true,
status: 1,
parent_id: 3
},
{
id: 33,
route_path: '/settings/prompts',
route_name: 'prompts',
route_title: '提示词管理',
icon: 'ri-message-line',
sort_order: 3,
is_hidden: false,
is_cache: true,
status: 1,
parent_id: 3
}
]
},
{
id: 4,
route_path: '/role-permissions',
route_name: 'role-permissions',
route_title: '角色权限管理',
icon: 'ri-shield-user-line',
sort_order: 4,
is_hidden: false,
is_cache: true,
status: 1,
parent_id: null
}
];
/**
* 模拟角色数据
*/
const mockRoles: RoleInfo[] = [
{
id: 1,
role_key: 'admin',
role_name: '系统管理员',
data_scope: 'ALL',
description: '拥有系统所有权限',
priority: 1,
is_system_role: true,
created_at: '2024-01-01 10:00:00',
updated_at: '2024-01-01 10:00:00'
},
{
id: 2,
role_key: 'provincial',
role_name: '省级管理员',
data_scope: 'PROVINCE',
description: '省级权限,可管理文档类型和评查点',
priority: 2,
is_system_role: false,
created_at: '2024-01-02 10:00:00',
updated_at: '2024-01-02 10:00:00'
},
{
id: 3,
role_key: 'city_admin',
role_name: '市级管理员',
data_scope: 'CITY',
description: '市级权限,可管理本市文档',
priority: 3,
is_system_role: false,
created_at: '2024-01-03 10:00:00',
updated_at: '2024-01-03 10:00:00'
},
{
id: 4,
role_key: 'common_user',
role_name: '普通用户',
data_scope: 'SELF',
description: '普通用户,只能查看自己的文档',
priority: 4,
is_system_role: false,
created_at: '2024-01-04 10:00:00',
updated_at: '2024-01-04 10:00:00'
},
{
id: 5,
role_key: 'reviewer',
role_name: '评审员',
data_scope: 'DEPARTMENT',
description: '负责文档评审工作',
priority: 5,
is_system_role: false,
created_at: '2024-01-05 10:00:00',
updated_at: '2024-01-05 10:00:00'
}
];
/**
* 模拟角色-路由权限关联数据
*/
const mockRoleRoutePermissions: RoleRoutePermission[] = [
// 系统管理员拥有所有权限
{ id: 1, role_id: 1, route_id: 1, permission: 'RW', created_at: '2024-01-01 10:00:00' },
{ id: 2, role_id: 1, route_id: 11, permission: 'RW', created_at: '2024-01-01 10:00:00' },
{ id: 3, role_id: 1, route_id: 12, permission: 'RW', created_at: '2024-01-01 10:00:00' },
{ id: 4, role_id: 1, route_id: 2, permission: 'RW', created_at: '2024-01-01 10:00:00' },
{ id: 5, role_id: 1, route_id: 21, permission: 'RW', created_at: '2024-01-01 10:00:00' },
{ id: 6, role_id: 1, route_id: 3, permission: 'RW', created_at: '2024-01-01 10:00:00' },
{ id: 7, role_id: 1, route_id: 31, permission: 'RW', created_at: '2024-01-01 10:00:00' },
{ id: 8, role_id: 1, route_id: 32, permission: 'RW', created_at: '2024-01-01 10:00:00' },
{ id: 9, role_id: 1, route_id: 33, permission: 'RW', created_at: '2024-01-01 10:00:00' },
{ id: 10, role_id: 1, route_id: 4, permission: 'RW', created_at: '2024-01-01 10:00:00' },
// 省级管理员
{ id: 11, role_id: 2, route_id: 1, permission: 'RW', created_at: '2024-01-02 10:00:00' },
{ id: 12, role_id: 2, route_id: 11, permission: 'RW', created_at: '2024-01-02 10:00:00' },
{ id: 13, role_id: 2, route_id: 12, permission: 'RW', created_at: '2024-01-02 10:00:00' },
{ id: 14, role_id: 2, route_id: 3, permission: 'RW', created_at: '2024-01-02 10:00:00' },
{ id: 15, role_id: 2, route_id: 31, permission: 'RW', created_at: '2024-01-02 10:00:00' },
{ id: 16, role_id: 2, route_id: 32, permission: 'RW', created_at: '2024-01-02 10:00:00' },
// 普通用户
{ id: 17, role_id: 4, route_id: 1, permission: 'R', created_at: '2024-01-04 10:00:00' },
{ id: 18, role_id: 4, route_id: 11, permission: 'R', created_at: '2024-01-04 10:00:00' },
];
/**
* 模拟用户数据
*/
const mockUsers: UserInfo[] = [
{
id: 1,
username: 'admin',
nick_name: '系统管理员',
phone_number: '13800138000',
email: 'admin@example.com',
ou_name: '系统管理部',
status: 1,
is_leader: true
},
{
id: 2,
username: 'zhangsan',
nick_name: '张三',
phone_number: '13800138001',
email: 'zhangsan@example.com',
ou_name: '广东省局',
status: 1,
is_leader: true
},
{
id: 3,
username: 'lisi',
nick_name: '李四',
phone_number: '13800138002',
email: 'lisi@example.com',
ou_name: '梅州市局',
status: 1,
is_leader: false
},
{
id: 4,
username: 'wangwu',
nick_name: '王五',
phone_number: '13800138003',
email: 'wangwu@example.com',
ou_name: '云浮市局',
status: 1,
is_leader: false
},
{
id: 5,
username: 'zhaoliu',
nick_name: '赵六',
phone_number: '13800138004',
email: 'zhaoliu@example.com',
ou_name: '揭阳市局',
status: 1,
is_leader: false
}
];
/**
* 模拟用户-角色关联数据
*/
const mockUserRoles: UserRoleRelation[] = [
{ id: 1, user_id: 1, role_id: 1, created_at: '2024-01-01 10:00:00' },
{ id: 2, user_id: 2, role_id: 2, created_at: '2024-01-02 10:00:00' },
{ id: 3, user_id: 3, role_id: 3, created_at: '2024-01-03 10:00:00' },
{ id: 4, user_id: 4, role_id: 4, created_at: '2024-01-04 10:00:00' },
{ id: 5, user_id: 5, role_id: 5, created_at: '2024-01-05 10:00:00' }
];
// ==================== API 函数 ====================
/**
* 获取所有角色列表
*/
export async function getRoles(): Promise<RoleInfo[]> {
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 300));
return mockRoles;
}
/**
* 获取所有路由(树形结构)
*/
export async function getRoutes(): Promise<RouteInfo[]> {
await new Promise(resolve => setTimeout(resolve, 300));
return mockRoutes;
}
/**
* 获取指定角色的路由权限
* @param roleId 角色ID
*/
export async function getRoleRoutePermissions(roleId: number): Promise<RoleRoutePermission[]> {
await new Promise(resolve => setTimeout(resolve, 200));
return mockRoleRoutePermissions.filter(p => p.role_id === roleId);
}
/**
* 更新角色的路由权限
* @param roleId 角色ID
* @param routeIds 路由ID数组
*/
export async function updateRoleRoutePermissions(
roleId: number,
routeIds: number[]
): Promise<{ success: boolean; message: string }> {
await new Promise(resolve => setTimeout(resolve, 500));
// 在实际应用中,这里会调用后端API
console.log('更新角色权限:', { roleId, routeIds });
// 模拟更新本地数据
// 删除该角色的旧权限
const oldPermissions = mockRoleRoutePermissions.filter(p => p.role_id === roleId);
oldPermissions.forEach(p => {
const index = mockRoleRoutePermissions.indexOf(p);
if (index > -1) {
mockRoleRoutePermissions.splice(index, 1);
}
});
// 添加新权限
routeIds.forEach((routeId, index) => {
mockRoleRoutePermissions.push({
id: Date.now() + index,
role_id: roleId,
route_id: routeId,
permission: 'RW',
created_at: new Date().toISOString()
});
});
return { success: true, message: '角色权限更新成功' };
}
/**
* 获取指定角色的用户列表
* @param roleId 角色ID
*/
export async function getRoleUsers(roleId: number): Promise<UserInfo[]> {
await new Promise(resolve => setTimeout(resolve, 200));
// 查找具有该角色的用户ID
const userIds = mockUserRoles
.filter(ur => ur.role_id === roleId)
.map(ur => ur.user_id);
// 返回用户详细信息
return mockUsers.filter(u => userIds.includes(u.id));
}
/**
* 获取所有用户列表
*/
export async function getAllUsers(): Promise<UserInfo[]> {
await new Promise(resolve => setTimeout(resolve, 300));
return mockUsers;
}
/**
* 为用户分配角色
* @param userId 用户ID
* @param roleIds 角色ID数组
*/
export async function assignUserRoles(
userId: number,
roleIds: number[]
): Promise<{ success: boolean; message: string }> {
await new Promise(resolve => setTimeout(resolve, 500));
console.log('为用户分配角色:', { userId, roleIds });
// 模拟更新本地数据
// 删除该用户的旧角色
const oldRoles = mockUserRoles.filter(ur => ur.user_id === userId);
oldRoles.forEach(ur => {
const index = mockUserRoles.indexOf(ur);
if (index > -1) {
mockUserRoles.splice(index, 1);
}
});
// 添加新角色
roleIds.forEach((roleId, index) => {
mockUserRoles.push({
id: Date.now() + index,
user_id: userId,
role_id: roleId,
created_at: new Date().toISOString()
});
});
return { success: true, message: '用户角色分配成功' };
}
/**
* 创建新角色
* @param roleData 角色数据
*/
export async function createRole(
roleData: Omit<RoleInfo, 'id' | 'created_at' | 'updated_at'>
): Promise<{ success: boolean; message: string; data?: RoleInfo }> {
await new Promise(resolve => setTimeout(resolve, 500));
const newRole: RoleInfo = {
...roleData,
id: Math.max(...mockRoles.map(r => r.id)) + 1,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
};
mockRoles.push(newRole);
return { success: true, message: '角色创建成功', data: newRole };
}
/**
* 更新角色信息
* @param roleId 角色ID
* @param roleData 角色数据
*/
export async function updateRole(
roleId: number,
roleData: Partial<Omit<RoleInfo, 'id' | 'created_at' | 'updated_at'>>
): Promise<{ success: boolean; message: string }> {
await new Promise(resolve => setTimeout(resolve, 500));
const roleIndex = mockRoles.findIndex(r => r.id === roleId);
if (roleIndex === -1) {
return { success: false, message: '角色不存在' };
}
mockRoles[roleIndex] = {
...mockRoles[roleIndex],
...roleData,
updated_at: new Date().toISOString()
};
return { success: true, message: '角色更新成功' };
}
/**
* 删除角色
* @param roleId 角色ID
*/
export async function deleteRole(roleId: number): Promise<{ success: boolean; message: string }> {
await new Promise(resolve => setTimeout(resolve, 500));
const role = mockRoles.find(r => r.id === roleId);
if (!role) {
return { success: false, message: '角色不存在' };
}
if (role.is_system_role) {
return { success: false, message: '系统角色不能删除' };
}
const roleIndex = mockRoles.indexOf(role);
mockRoles.splice(roleIndex, 1);
return { success: true, message: '角色删除成功' };
}
+4 -11
View File
@@ -1,5 +1,6 @@
import { get } from '../axios-client';
import { API_BASE_URL } from '../../config/api-config';
import axios from 'axios';
// 用户信息接口
export interface UserInfo {
@@ -56,24 +57,16 @@ export async function getOrganizationTree(includeUsers: boolean = true, jwtToken
let responseData: OrganizationResponse;
if (jwtToken) {
// 如果提供了JWT Token,则使用fetch并携带Authorization头
// 如果提供了JWT Token,则使用axios并携带Authorization头
const url = `${API_BASE_URL}/admin/users/organizations?include_users=${includeUsers}`;
const response = await fetch(url, {
const response = await axios.get(url, {
headers: {
'Authorization': `Bearer ${jwtToken}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
const errorText = await response.text();
console.error('获取组织架构失败 (fetch):', errorText);
return {
success: false,
error: `HTTP error! status: ${response.status}, ${errorText}`
};
}
responseData = await response.json();
responseData = response.data;
} else {
// 否则,使用原有的get方法
const response = await get<OrganizationResponse>(