fix: 1. 接入入口模块的管理接口,优化样式。
2. 将查看文档评查结果详情对接接口,采用接口的方式进行查询。
This commit is contained in:
@@ -178,13 +178,9 @@ axiosInstance.interceptors.response.use(
|
|||||||
if (isAxiosError(error) && error.response?.status === 403) {
|
if (isAxiosError(error) && error.response?.status === 403) {
|
||||||
console.warn('⚠️ [403 Forbidden] 无权限访问:', error.config?.url);
|
console.warn('⚠️ [403 Forbidden] 无权限访问:', error.config?.url);
|
||||||
|
|
||||||
// 只在客户端显示 toast 提示
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
toastService.warning('无权限访问该资源');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改错误消息为友好提示,避免显示原始的 "Request failed with status code 403"
|
// 修改错误消息为友好提示,避免显示原始的 "Request failed with status code 403"
|
||||||
error.message = '无权限访问该资源';
|
// 注意:不在这里显示 toast,由组件层统一处理,避免重复提示
|
||||||
|
error.message = '无权限';
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
/**
|
/**
|
||||||
* 入口模块管理 API 客户端
|
* 入口模块管理 API 客户端
|
||||||
* 提供入口模块的增删改查功能
|
* 提供入口模块的增删改查功能
|
||||||
|
* API 文档: auth_doc/ENTRY_MODULE_API.md
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { postgrestGet, postgrestPost, postgrestPut, postgrestDelete } from "../postgrest-client";
|
import { get, post, put, del } from "../axios-client";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地区配置接口
|
||||||
|
*/
|
||||||
|
export interface AreaConfig {
|
||||||
|
area: string; // 地区名称(如:梅州、云浮、揭阳、潮州)
|
||||||
|
enabled: boolean; // 是否启用(默认 true)
|
||||||
|
sort_order: number; // 排序号(默认 0)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 入口模块数据接口
|
* 入口模块数据接口
|
||||||
@@ -11,9 +21,9 @@ import { postgrestGet, postgrestPost, postgrestPut, postgrestDelete } from "../p
|
|||||||
export interface EntryModule {
|
export interface EntryModule {
|
||||||
id?: number;
|
id?: number;
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string | null;
|
||||||
path?: string; // logo图片路径
|
path?: string | null; // logo图片路径
|
||||||
areas?: string[]; // 地区数组
|
areas?: AreaConfig[] | null; // 地区配置列表
|
||||||
created_at?: string;
|
created_at?: string;
|
||||||
updated_at?: string;
|
updated_at?: string;
|
||||||
}
|
}
|
||||||
@@ -25,7 +35,7 @@ export interface EntryModuleSearchParams {
|
|||||||
name?: string;
|
name?: string;
|
||||||
area?: string;
|
area?: string;
|
||||||
page?: number;
|
page?: number;
|
||||||
pageSize?: number;
|
page_size?: number; // 注意:API使用page_size而不是pageSize
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,83 +46,94 @@ export interface EntryModulesResponse {
|
|||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API统一响应格式
|
||||||
|
* 注意:后端返回的字段是 msg 而不是 message,code 为 0 表示成功
|
||||||
|
*/
|
||||||
|
interface ApiResponse<T> {
|
||||||
|
code: number; // 0 表示成功
|
||||||
|
msg: string; // 消息(不是 message)
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列表数据响应格式
|
||||||
|
*/
|
||||||
|
interface ListResponse<T> {
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
page_size: number;
|
||||||
|
items: T[];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取入口模块列表
|
* 获取入口模块列表
|
||||||
* @param searchParams 搜索参数
|
* @param searchParams 搜索参数
|
||||||
* @param jwtToken JWT令牌
|
|
||||||
* @returns 入口模块列表和总数
|
* @returns 入口模块列表和总数
|
||||||
|
*
|
||||||
|
* 注意:不需要传递 JWT,axios-client 会自动从 localStorage 读取并添加
|
||||||
*/
|
*/
|
||||||
export async function getEntryModules(
|
export async function getEntryModules(
|
||||||
searchParams: EntryModuleSearchParams = {},
|
searchParams: EntryModuleSearchParams = {}
|
||||||
jwtToken?: string | null
|
|
||||||
): Promise<{ data?: EntryModulesResponse; error?: string }> {
|
): Promise<{ data?: EntryModulesResponse; error?: string }> {
|
||||||
try {
|
try {
|
||||||
const { name, area, page = 1, pageSize = 10 } = searchParams;
|
const { name, area, page = 1, page_size = 10 } = searchParams;
|
||||||
|
|
||||||
// 构建过滤条件
|
// 构建查询参数
|
||||||
const filter: Record<string, string> = {};
|
const queryParams: Record<string, any> = {
|
||||||
|
page,
|
||||||
|
page_size
|
||||||
|
};
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
filter.name = `ilike.*${name}*`;
|
queryParams.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有地区筛选,使用 JSONB 查询
|
|
||||||
if (area) {
|
if (area) {
|
||||||
filter.areas = `cs.{"${area}"}`; // cs = contains (JSONB数组包含)
|
queryParams.area = area;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算分页
|
// 调用 API
|
||||||
const offset = (page - 1) * pageSize;
|
const response = await get<ApiResponse<ListResponse<EntryModule>>>(
|
||||||
|
'/api/v3/entry-modules',
|
||||||
|
queryParams
|
||||||
|
);
|
||||||
|
|
||||||
// 构建查询参数(一次请求获取数据和总数)
|
if (response.error) {
|
||||||
const queryParams: any = {
|
console.error('❌ [getEntryModules] API错误:', response.error);
|
||||||
select: "*",
|
return { error: response.error };
|
||||||
order: "created_at.desc",
|
|
||||||
limit: pageSize,
|
|
||||||
offset: offset,
|
|
||||||
headers: {
|
|
||||||
'Prefer': 'count=exact'
|
|
||||||
},
|
|
||||||
token: jwtToken
|
|
||||||
};
|
|
||||||
|
|
||||||
// 只在有过滤条件时添加 filter
|
|
||||||
if (Object.keys(filter).length > 0) {
|
|
||||||
queryParams.filter = filter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取分页数据
|
// 🔑 解析响应数据
|
||||||
const result = await postgrestGet<EntryModule[]>("entry_modules", queryParams);
|
// axios-client 返回: { data: {后端JSON}, status: 200 }
|
||||||
|
// 后端返回: { code: 0, msg: "成功", data: { total, items } }
|
||||||
|
const backendResponse = response.data;
|
||||||
|
|
||||||
if (result.error) {
|
if (!backendResponse) {
|
||||||
return { error: result.error };
|
console.error('❌ [getEntryModules] 响应为空');
|
||||||
|
return { error: '响应为空' };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从 Content-Range 头获取总数
|
// 检查后端返回的 code(0 表示成功)
|
||||||
let totalCount = 0;
|
if (backendResponse.code !== 0) {
|
||||||
const responseWithHeaders = result as {
|
console.error('❌ [getEntryModules] 后端返回错误:', backendResponse.msg);
|
||||||
data: unknown;
|
return { error: backendResponse.msg || '请求失败' };
|
||||||
headers?: Record<string, string>;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
if (responseWithHeaders.headers) {
|
const apiData = backendResponse.data;
|
||||||
const rangeHeader = responseWithHeaders.headers['content-range'];
|
if (!apiData) {
|
||||||
if (rangeHeader) {
|
console.error('❌ [getEntryModules] data 字段为空');
|
||||||
const total = rangeHeader.split('/')[1];
|
return { error: '数据为空' };
|
||||||
if (total !== '*') {
|
|
||||||
totalCount = parseInt(total, 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
modules: result.data || [],
|
modules: apiData.items || [],
|
||||||
total: totalCount || (result.data?.length || 0)
|
total: apiData.total || 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取入口模块列表失败:", error);
|
console.error("❌ [getEntryModules] 获取入口模块列表失败:", error);
|
||||||
return { error: error instanceof Error ? error.message : "获取入口模块列表失败" };
|
return { error: error instanceof Error ? error.message : "获取入口模块列表失败" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,31 +141,39 @@ export async function getEntryModules(
|
|||||||
/**
|
/**
|
||||||
* 根据ID获取入口模块
|
* 根据ID获取入口模块
|
||||||
* @param id 入口模块ID
|
* @param id 入口模块ID
|
||||||
* @param jwtToken JWT令牌
|
|
||||||
* @returns 入口模块数据
|
* @returns 入口模块数据
|
||||||
|
*
|
||||||
|
* 注意:不需要传递 JWT,axios-client 会自动从 localStorage 读取并添加
|
||||||
*/
|
*/
|
||||||
export async function getEntryModuleById(
|
export async function getEntryModuleById(
|
||||||
id: number,
|
id: number
|
||||||
jwtToken?: string | null
|
|
||||||
): Promise<{ data?: EntryModule; error?: string }> {
|
): Promise<{ data?: EntryModule; error?: string }> {
|
||||||
try {
|
try {
|
||||||
const result = await postgrestGet<EntryModule[]>("entry_modules", {
|
// 调用 API
|
||||||
filter: { id: `eq.${id}` },
|
const response = await get<ApiResponse<EntryModule>>(
|
||||||
token: jwtToken
|
`/api/v3/entry-modules/${id}`
|
||||||
});
|
);
|
||||||
|
|
||||||
if (result.error) {
|
if (response.error) {
|
||||||
return { error: result.error };
|
console.error('❌ [getEntryModuleById] API错误:', response.error);
|
||||||
|
return { error: response.error };
|
||||||
}
|
}
|
||||||
|
|
||||||
const module = result.data?.[0];
|
const backendResponse = response.data;
|
||||||
|
if (!backendResponse || backendResponse.code !== 0) {
|
||||||
|
console.error('❌ [getEntryModuleById] 后端返回错误:', backendResponse?.msg);
|
||||||
|
return { error: backendResponse?.msg || '获取失败' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const module = backendResponse.data;
|
||||||
if (!module) {
|
if (!module) {
|
||||||
|
console.error('❌ [getEntryModuleById] 入口模块不存在');
|
||||||
return { error: "入口模块不存在" };
|
return { error: "入口模块不存在" };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { data: module };
|
return { data: module };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取入口模块失败:", error);
|
console.error("❌ [getEntryModuleById] 获取入口模块失败:", error);
|
||||||
return { error: error instanceof Error ? error.message : "获取入口模块失败" };
|
return { error: error instanceof Error ? error.message : "获取入口模块失败" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,28 +181,69 @@ export async function getEntryModuleById(
|
|||||||
/**
|
/**
|
||||||
* 创建入口模块
|
* 创建入口模块
|
||||||
* @param module 入口模块数据
|
* @param module 入口模块数据
|
||||||
* @param jwtToken JWT令牌
|
|
||||||
* @returns 创建的入口模块
|
* @returns 创建的入口模块
|
||||||
|
*
|
||||||
|
* 注意:不需要传递 JWT,axios-client 会自动从 localStorage 读取并添加
|
||||||
*/
|
*/
|
||||||
export async function createEntryModule(
|
export async function createEntryModule(
|
||||||
module: Omit<EntryModule, "id" | "created_at" | "updated_at">,
|
module: {
|
||||||
jwtToken?: string | null
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
path?: string | null;
|
||||||
|
areas?: string[] | AreaConfig[]; // 兼容两种格式
|
||||||
|
}
|
||||||
): Promise<{ data?: EntryModule; error?: string }> {
|
): Promise<{ data?: EntryModule; error?: string }> {
|
||||||
try {
|
try {
|
||||||
const result = await postgrestPost<EntryModule[], EntryModule>(
|
// 转换 areas 格式(如果是字符串数组,转换为 AreaConfig 数组)
|
||||||
"entry_modules",
|
let areasConfig: AreaConfig[] | undefined;
|
||||||
module as EntryModule,
|
if (module.areas && Array.isArray(module.areas)) {
|
||||||
jwtToken
|
if (typeof module.areas[0] === 'string') {
|
||||||
);
|
// 字符串数组转换为 AreaConfig 数组
|
||||||
|
areasConfig = (module.areas as string[]).map((area, index) => ({
|
||||||
if (result.error) {
|
area,
|
||||||
return { error: result.error };
|
enabled: true,
|
||||||
|
sort_order: index + 1
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
// 已经是 AreaConfig 数组
|
||||||
|
areasConfig = module.areas as AreaConfig[];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createdModule = Array.isArray(result.data) ? result.data[0] : result.data;
|
// 构建请求体
|
||||||
return { data: createdModule as EntryModule };
|
const requestBody = {
|
||||||
|
name: module.name,
|
||||||
|
description: module.description || undefined,
|
||||||
|
path: module.path || undefined,
|
||||||
|
areas: areasConfig
|
||||||
|
};
|
||||||
|
|
||||||
|
// 调用 API
|
||||||
|
const response = await post<ApiResponse<EntryModule>>(
|
||||||
|
'/api/v3/entry-modules',
|
||||||
|
requestBody
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error('❌ [createEntryModule] API错误:', response.error);
|
||||||
|
return { error: response.error };
|
||||||
|
}
|
||||||
|
|
||||||
|
const backendResponse = response.data;
|
||||||
|
if (!backendResponse || backendResponse.code !== 0) {
|
||||||
|
console.error('❌ [createEntryModule] 后端返回错误:', backendResponse?.msg);
|
||||||
|
return { error: backendResponse?.msg || '创建失败' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdModule = backendResponse.data;
|
||||||
|
if (!createdModule) {
|
||||||
|
console.error('❌ [createEntryModule] 响应数据格式错误');
|
||||||
|
return { error: '创建失败:响应数据格式错误' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data: createdModule };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("创建入口模块失败:", error);
|
console.error("❌ [createEntryModule] 创建入口模块失败:", error);
|
||||||
return { error: error instanceof Error ? error.message : "创建入口模块失败" };
|
return { error: error instanceof Error ? error.message : "创建入口模块失败" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,30 +252,69 @@ export async function createEntryModule(
|
|||||||
* 更新入口模块
|
* 更新入口模块
|
||||||
* @param id 入口模块ID
|
* @param id 入口模块ID
|
||||||
* @param module 更新的入口模块数据
|
* @param module 更新的入口模块数据
|
||||||
* @param jwtToken JWT令牌
|
|
||||||
* @returns 更新的入口模块
|
* @returns 更新的入口模块
|
||||||
|
*
|
||||||
|
* 注意:不需要传递 JWT,axios-client 会自动从 localStorage 读取并添加
|
||||||
*/
|
*/
|
||||||
export async function updateEntryModule(
|
export async function updateEntryModule(
|
||||||
id: number,
|
id: number,
|
||||||
module: Partial<Omit<EntryModule, "id" | "created_at" | "updated_at">>,
|
module: {
|
||||||
jwtToken?: string | null
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
path?: string | null;
|
||||||
|
areas?: string[] | AreaConfig[]; // 兼容两种格式
|
||||||
|
}
|
||||||
): Promise<{ data?: EntryModule; error?: string }> {
|
): Promise<{ data?: EntryModule; error?: string }> {
|
||||||
try {
|
try {
|
||||||
const result = await postgrestPut<EntryModule[], Partial<EntryModule>>(
|
// 转换 areas 格式(如果是字符串数组,转换为 AreaConfig 数组)
|
||||||
"entry_modules",
|
let areasConfig: AreaConfig[] | undefined;
|
||||||
module,
|
if (module.areas && Array.isArray(module.areas)) {
|
||||||
{ id: `eq.${id}` },
|
if (typeof module.areas[0] === 'string') {
|
||||||
jwtToken
|
// 字符串数组转换为 AreaConfig 数组
|
||||||
);
|
areasConfig = (module.areas as string[]).map((area, index) => ({
|
||||||
|
area,
|
||||||
if (result.error) {
|
enabled: true,
|
||||||
return { error: result.error };
|
sort_order: index + 1
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
// 已经是 AreaConfig 数组
|
||||||
|
areasConfig = module.areas as AreaConfig[];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedModule = Array.isArray(result.data) ? result.data[0] : result.data;
|
// 构建请求体(只包含需要更新的字段)
|
||||||
return { data: updatedModule as EntryModule };
|
const requestBody: any = {};
|
||||||
|
if (module.name !== undefined) requestBody.name = module.name;
|
||||||
|
if (module.description !== undefined) requestBody.description = module.description;
|
||||||
|
if (module.path !== undefined) requestBody.path = module.path;
|
||||||
|
if (areasConfig !== undefined) requestBody.areas = areasConfig;
|
||||||
|
|
||||||
|
// 调用 API
|
||||||
|
const response = await put<ApiResponse<EntryModule>>(
|
||||||
|
`/api/v3/entry-modules/${id}`,
|
||||||
|
requestBody
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error('❌ [updateEntryModule] API错误:', response.error);
|
||||||
|
return { error: response.error };
|
||||||
|
}
|
||||||
|
|
||||||
|
const backendResponse = response.data;
|
||||||
|
if (!backendResponse || backendResponse.code !== 0) {
|
||||||
|
console.error('❌ [updateEntryModule] 后端返回错误:', backendResponse?.msg);
|
||||||
|
return { error: backendResponse?.msg || '更新失败' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedModule = backendResponse.data;
|
||||||
|
if (!updatedModule) {
|
||||||
|
console.error('❌ [updateEntryModule] 响应数据格式错误');
|
||||||
|
return { error: '更新失败:响应数据格式错误' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data: updatedModule };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("更新入口模块失败:", error);
|
console.error("❌ [updateEntryModule] 更新入口模块失败:", error);
|
||||||
return { error: error instanceof Error ? error.message : "更新入口模块失败" };
|
return { error: error instanceof Error ? error.message : "更新入口模块失败" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,27 +322,33 @@ export async function updateEntryModule(
|
|||||||
/**
|
/**
|
||||||
* 删除入口模块
|
* 删除入口模块
|
||||||
* @param id 入口模块ID
|
* @param id 入口模块ID
|
||||||
* @param jwtToken JWT令牌
|
|
||||||
* @returns 是否成功
|
* @returns 是否成功
|
||||||
|
*
|
||||||
|
* 注意:不需要传递 JWT,axios-client 会自动从 localStorage 读取并添加
|
||||||
*/
|
*/
|
||||||
export async function deleteEntryModule(
|
export async function deleteEntryModule(
|
||||||
id: number,
|
id: number
|
||||||
jwtToken?: string | null
|
|
||||||
): Promise<{ success: boolean; error?: string }> {
|
): Promise<{ success: boolean; error?: string }> {
|
||||||
try {
|
try {
|
||||||
const result = await postgrestDelete(
|
// 调用 API
|
||||||
"entry_modules",
|
const response = await del<ApiResponse<{ id: number; deleted: boolean }>>(
|
||||||
{ id: `eq.${id}` },
|
`/api/v3/entry-modules/${id}`
|
||||||
jwtToken
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.error) {
|
if (response.error) {
|
||||||
return { success: false, error: result.error };
|
console.error('❌ [deleteEntryModule] API错误:', response.error);
|
||||||
|
return { success: false, error: response.error };
|
||||||
|
}
|
||||||
|
|
||||||
|
const backendResponse = response.data;
|
||||||
|
if (!backendResponse || backendResponse.code !== 0) {
|
||||||
|
console.error('❌ [deleteEntryModule] 删除失败:', backendResponse?.msg);
|
||||||
|
return { success: false, error: backendResponse?.msg || '删除失败' };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("删除入口模块失败:", error);
|
console.error("❌ [deleteEntryModule] 删除入口模块失败:", error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: error instanceof Error ? error.message : "删除入口模块失败"
|
error: error instanceof Error ? error.message : "删除入口模块失败"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { postgrestGet, type PostgrestParams, postgrestPut, postgrestPost } from
|
|||||||
import {getDocumentWithNoUserId} from "~/api/files/documents";
|
import {getDocumentWithNoUserId} from "~/api/files/documents";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { getUserSession } from "~/api/login/auth.server";
|
import { getUserSession } from "~/api/login/auth.server";
|
||||||
|
import { apiRequest } from "../axios-client";
|
||||||
// import { formatDate } from "~/utils";
|
// import { formatDate } from "~/utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -959,3 +960,97 @@ export async function confirmReviewResults(documentId: string, request: Request)
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🆕 从后端API获取评查点数据(新版本)
|
||||||
|
* @param fileId 评查文件ID
|
||||||
|
* @param request Remix请求对象,用于获取用户会话
|
||||||
|
* @returns 评查点结果列表和统计数据
|
||||||
|
*
|
||||||
|
* 🔥 接口文档: auth_doc/review_points_api_frontend.md
|
||||||
|
* 📍 API地址: GET /api/v3/review-points/{document_id}
|
||||||
|
*
|
||||||
|
* ✅ 优势:
|
||||||
|
* - 单次API调用替代原7次请求
|
||||||
|
* - 后端统一处理权限验证
|
||||||
|
* - 数据格式与原方法完全一致
|
||||||
|
*/
|
||||||
|
export async function getReviewPoints_fromApi(fileId: string, request: Request) {
|
||||||
|
try {
|
||||||
|
// 获取用户会话信息(用于JWT认证)
|
||||||
|
const { userInfo, frontendJWT } = await getUserSession(request);
|
||||||
|
|
||||||
|
// const test_jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo1LCJ1c2VybmFtZSI6ImFkbWluIiwidXNlcl9yb2xlIjoicHJvdmluY2lhbF9hZG1pbiIsImV4cCI6MTc2NDE2NTI2MCwiaWF0IjoxNzY0MTQzNjYwLCJpc3N1ZWRfdGltZSI6IjIwMjUtMTEtMjYgMTU6NTQ6MjAiLCJhdWQiOiJkb2NyZXZpZXctZnJvbnRlbmQiLCJzdWIiOiIwMDAiLCJuaWNrX25hbWUiOiJhZG1pbiIsIm91X2lkIjoiMDAwIiwib3VfbmFtZSI6InRlc3QiLCJpc19sZWFkZXIiOnRydWUsImFyZWEiOiJcdTY4ODVcdTVkZGUifQ.VRuMvMh98CMeoWDhX1gVczg6DzBNbWwFK9g4kkKqPpc'
|
||||||
|
if (!userInfo?.user_id) {
|
||||||
|
console.error("用户身份验证失败");
|
||||||
|
return { error: '用户身份验证失败', status: 401 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log(`📡 [getReviewPoints_fromApi] 调用后端API获取文档 ${fileId} 的评查点数据`);
|
||||||
|
// console.log(`🔑 [getReviewPoints_fromApi] 使用JWT Token (前10位): ${frontendJWT}`);
|
||||||
|
|
||||||
|
// 🔑 调用后端API(服务端环境需要手动传递Authorization header)
|
||||||
|
const response = await apiRequest<{
|
||||||
|
data: ReviewPointResult[];
|
||||||
|
stats: StatsData;
|
||||||
|
reviewInfo: {
|
||||||
|
reviewTime: string;
|
||||||
|
reviewModel: string;
|
||||||
|
ruleGroup: string;
|
||||||
|
result: string;
|
||||||
|
issueCount: number;
|
||||||
|
};
|
||||||
|
document: unknown;
|
||||||
|
comparison_document: unknown;
|
||||||
|
scoring_proposals: ScoringProposal[];
|
||||||
|
}>(
|
||||||
|
`/api/v3/review-points/${fileId}`,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${frontendJWT}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 处理错误响应
|
||||||
|
if (response.error) {
|
||||||
|
console.error('❌ [getReviewPoints_fromApi] API调用失败:', response.error);
|
||||||
|
return { error: response.error, status: response.status || 500 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 成功响应
|
||||||
|
if (response.data) {
|
||||||
|
// console.log('✅ [getReviewPoints_fromApi] API调用成功,返回数据结构:', JSON.stringify({
|
||||||
|
// // 评查点数量: response.data.data?.length || 0,
|
||||||
|
// // 统计信息: response.data.stats,
|
||||||
|
// // 评查信息: response.data.reviewInfo,
|
||||||
|
// 有文档数据: response.data.document,
|
||||||
|
// // 有比对数据: !!response.data.comparison_document,
|
||||||
|
// // 评分提案数量: response.data.scoring_proposals?.length || 0
|
||||||
|
// }));
|
||||||
|
|
||||||
|
// 返回数据格式与原方法完全一致
|
||||||
|
return {
|
||||||
|
data: response.data.data,
|
||||||
|
stats: response.data.stats,
|
||||||
|
reviewInfo: response.data.reviewInfo,
|
||||||
|
document: response.data.document,
|
||||||
|
comparison_document: response.data.comparison_document,
|
||||||
|
scoring_proposals: response.data.scoring_proposals
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据为空
|
||||||
|
console.error('❌ [getReviewPoints_fromApi] API响应数据为空');
|
||||||
|
return { error: 'API响应数据为空', status: 500 };
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ [getReviewPoints_fromApi] 调用失败:', error);
|
||||||
|
return {
|
||||||
|
error: error instanceof Error ? error.message : '获取评查点数据失败',
|
||||||
|
status: 500
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1204,8 +1204,6 @@ export async function getEvaluationPointGroups(
|
|||||||
...(token ? { headers: { 'Authorization': `Bearer ${token}` } } : {})
|
...(token ? { headers: { 'Authorization': `Bearer ${token}` } } : {})
|
||||||
});
|
});
|
||||||
|
|
||||||
// 🔍 调试:打印完整响应
|
|
||||||
console.log('📦 getEvaluationPointGroups 完整响应:', JSON.stringify(response, null, 2));
|
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
console.error('❌ getEvaluationPointGroups 错误:', response.error);
|
console.error('❌ getEvaluationPointGroups 错误:', response.error);
|
||||||
@@ -1213,8 +1211,6 @@ export async function getEvaluationPointGroups(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
console.log('📊 response.data:', response.data);
|
|
||||||
console.log('📊 response.data.data:', response.data.data);
|
|
||||||
|
|
||||||
if (!response.data.data) {
|
if (!response.data.data) {
|
||||||
console.error('❌ response.data.data 不存在!完整 response.data:', response.data);
|
console.error('❌ response.data.data 不存在!完整 response.data:', response.data);
|
||||||
@@ -1222,7 +1218,6 @@ export async function getEvaluationPointGroups(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ruleGroups = response.data.data.map(convertApiGroupToRuleGroup);
|
const ruleGroups = response.data.data.map(convertApiGroupToRuleGroup);
|
||||||
console.log('✅ 转换后的 ruleGroups:', ruleGroups);
|
|
||||||
return {
|
return {
|
||||||
data: ruleGroups,
|
data: ruleGroups,
|
||||||
totalCount: response.data.total
|
totalCount: response.data.total
|
||||||
@@ -1415,7 +1410,6 @@ export async function createEvaluationPointGroup(
|
|||||||
|
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
const ruleGroup = convertApiGroupToRuleGroup(response.data);
|
const ruleGroup = convertApiGroupToRuleGroup(response.data);
|
||||||
console.log('✅ createEvaluationPointGroup 成功:', ruleGroup);
|
|
||||||
return { data: ruleGroup };
|
return { data: ruleGroup };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1477,7 +1471,6 @@ export async function updateEvaluationPointGroup(
|
|||||||
|
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
const ruleGroup = convertApiGroupToRuleGroup(response.data);
|
const ruleGroup = convertApiGroupToRuleGroup(response.data);
|
||||||
console.log('✅ updateEvaluationPointGroup 成功:', ruleGroup);
|
|
||||||
return { data: ruleGroup };
|
return { data: ruleGroup };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1520,7 +1513,6 @@ export async function deleteEvaluationPointGroup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
console.log('✅ deleteEvaluationPointGroup 成功:', response.data);
|
|
||||||
return {
|
return {
|
||||||
success: response.data.success,
|
success: response.data.success,
|
||||||
message: response.data.message,
|
message: response.data.message,
|
||||||
@@ -1579,7 +1571,6 @@ export async function batchUpdateEvaluationPointGroupStatus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
console.log('✅ batchUpdateEvaluationPointGroupStatus 成功:', response.data);
|
|
||||||
return {
|
return {
|
||||||
success: response.data.success,
|
success: response.data.success,
|
||||||
updated_count: response.data.updated_count,
|
updated_count: response.data.updated_count,
|
||||||
@@ -1635,7 +1626,6 @@ export async function batchDeleteEvaluationPointGroups(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
console.log('✅ batchDeleteEvaluationPointGroups 成功:', response.data);
|
|
||||||
return {
|
return {
|
||||||
success: response.data.success,
|
success: response.data.success,
|
||||||
deleted_groups: response.data.deleted_groups,
|
deleted_groups: response.data.deleted_groups,
|
||||||
|
|||||||
@@ -11,32 +11,6 @@
|
|||||||
*/
|
*/
|
||||||
const RBAC_API_BASE = '/api/v3/rbac';
|
const RBAC_API_BASE = '/api/v3/rbac';
|
||||||
|
|
||||||
/**
|
|
||||||
* RBAC专用API客户端 - 使用fetch直接请求Remix API路由
|
|
||||||
*/
|
|
||||||
async function rbacFetch<T>(url: string, options: RequestInit = {}): Promise<T> {
|
|
||||||
console.log('🔗 [RBAC Fetch] 请求:', url, options.method || 'GET');
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
...options,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
...options.headers
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('📡 [RBAC Fetch] 响应状态:', response.status, response.statusText);
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
console.log('📦 [RBAC Fetch] 响应数据:', data);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(data.detail || data.message || `HTTP ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 统一响应处理函数
|
* 统一响应处理函数
|
||||||
* 处理后端返回的统一格式响应
|
* 处理后端返回的统一格式响应
|
||||||
@@ -180,278 +154,6 @@ export interface RolePermissionDetail {
|
|||||||
data_scope: 'ALL' | 'DEPT' | 'SELF';
|
data_scope: 'ALL' | 'DEPT' | 'SELF';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 模拟数据 ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 模拟路由数据(树形结构)
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 模拟角色数据(与数据库实际数据一致)
|
|
||||||
* 仅用于开发阶段API失败时的降级方案
|
|
||||||
*/
|
|
||||||
const mockRoles: RoleInfo[] = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
role_key: 'admin',
|
|
||||||
role_name: '市级管理员',
|
|
||||||
data_scope: 'DEPT',
|
|
||||||
description: '负责本地区的所有业务管理,不包括系统设置和角色权限管理',
|
|
||||||
priority: 0,
|
|
||||||
is_system_role: false,
|
|
||||||
created_at: '2025-07-18 10:35:39',
|
|
||||||
updated_at: '2025-07-18 10:35:39'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
role_key: 'common',
|
|
||||||
role_name: '普通员工',
|
|
||||||
data_scope: 'SELF',
|
|
||||||
description: '仅能操作自己的数据',
|
|
||||||
priority: 0,
|
|
||||||
is_system_role: false,
|
|
||||||
created_at: '2025-07-18 10:35:39',
|
|
||||||
updated_at: '2025-07-18 10:35:39'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 52,
|
|
||||||
role_key: 'provincial_admin',
|
|
||||||
role_name: '省级管理员',
|
|
||||||
data_scope: 'ALL',
|
|
||||||
description: '拥有全部权限,可以管理所有地区的评查点规则、提示词、动态按钮、评查组',
|
|
||||||
priority: 1,
|
|
||||||
is_system_role: true,
|
|
||||||
created_at: '2025-11-19 17:25:45',
|
|
||||||
updated_at: '2025-11-19 17:25:45'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 模拟角色-路由权限关联数据
|
|
||||||
*/
|
|
||||||
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 函数 ====================
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有角色列表
|
* 获取所有角色列表
|
||||||
* @param params 查询参数
|
* @param params 查询参数
|
||||||
@@ -467,11 +169,9 @@ export async function getRoles(params?: {
|
|||||||
// 导入 axios-client 的 get 函数
|
// 导入 axios-client 的 get 函数
|
||||||
const { get } = await import('~/api/axios-client');
|
const { get } = await import('~/api/axios-client');
|
||||||
|
|
||||||
console.log('🔍 [getRoles] 开始调用后端API:', `/api/v3/rbac/roles`, params);
|
|
||||||
|
|
||||||
// 使用 axios-client 的 get 函数调用真实后端API
|
// 使用 axios-client 的 get 函数调用真实后端API
|
||||||
const response = await get<any>(`/api/v3/rbac/roles`, params || {});
|
const response = await get<any>(`/api/v3/rbac/roles`, params || {});
|
||||||
console.log('📦 [getRoles] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
@@ -485,7 +185,6 @@ export async function getRoles(params?: {
|
|||||||
items = response.data.items;
|
items = response.data.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ [getRoles] 解析出的角色数组:', items);
|
|
||||||
|
|
||||||
// 数据格式转换(后端字段 -> 前端字段)
|
// 数据格式转换(后端字段 -> 前端字段)
|
||||||
const roles = items.map(role => ({
|
const roles = items.map(role => ({
|
||||||
@@ -501,7 +200,6 @@ export async function getRoles(params?: {
|
|||||||
updated_at: role.updated_at
|
updated_at: role.updated_at
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log('✅ [getRoles] 最终返回的角色列表,共', roles.length, '个角色');
|
|
||||||
return roles;
|
return roles;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ [getRoles] 获取角色列表失败:', error);
|
console.error('❌ [getRoles] 获取角色列表失败:', error);
|
||||||
@@ -545,7 +243,6 @@ export async function getRoutes(): Promise<RouteInfo[]> {
|
|||||||
try {
|
try {
|
||||||
const { get } = await import('~/api/axios-client');
|
const { get } = await import('~/api/axios-client');
|
||||||
|
|
||||||
console.log('🔍 [getRoutes] 开始调用后端API: /rbac/user/routes');
|
|
||||||
|
|
||||||
// 调用后端API获取当前用户的路由(provincial_admin应该有所有路由权限)
|
// 调用后端API获取当前用户的路由(provincial_admin应该有所有路由权限)
|
||||||
const response = await get<any>('/rbac/user/routes');
|
const response = await get<any>('/rbac/user/routes');
|
||||||
@@ -564,8 +261,6 @@ export async function getRoutes(): Promise<RouteInfo[]> {
|
|||||||
routes = response.data.routes;
|
routes = response.data.routes;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ [getRoutes] 成功获取路由数据,共', routes.length, '个顶级路由');
|
|
||||||
|
|
||||||
// 将后端数据转换为前端RouteInfo格式
|
// 将后端数据转换为前端RouteInfo格式
|
||||||
const mapRouteData = (route: any): RouteInfo => ({
|
const mapRouteData = (route: any): RouteInfo => ({
|
||||||
id: route.id,
|
id: route.id,
|
||||||
@@ -599,11 +294,8 @@ export async function getRoleRoutePermissions(roleId: number): Promise<RoleRoute
|
|||||||
// 导入 axios-client 的 get 函数
|
// 导入 axios-client 的 get 函数
|
||||||
const { get } = await import('~/api/axios-client');
|
const { get } = await import('~/api/axios-client');
|
||||||
|
|
||||||
console.log('🔍 [getRoleRoutePermissions] 开始调用后端API:', `/rbac/roles/${roleId}/routes`);
|
|
||||||
|
|
||||||
// 使用 axios-client 的 get 函数调用真实后端API
|
// 使用 axios-client 的 get 函数调用真实后端API
|
||||||
const response = await get<any>(`/rbac/roles/${roleId}/routes`);
|
const response = await get<any>(`/rbac/roles/${roleId}/routes`);
|
||||||
console.log('📦 [getRoleRoutePermissions] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
@@ -624,7 +316,6 @@ export async function getRoleRoutePermissions(roleId: number): Promise<RoleRoute
|
|||||||
created_at: new Date().toISOString()
|
created_at: new Date().toISOString()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log('✅ [getRoleRoutePermissions] 获取角色路由权限成功:', permissions);
|
|
||||||
return permissions;
|
return permissions;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ [getRoleRoutePermissions] 获取角色路由权限失败:', error);
|
console.error('❌ [getRoleRoutePermissions] 获取角色路由权限失败:', error);
|
||||||
@@ -646,10 +337,7 @@ export async function getRoleRoutesWithPermissions(roleId: number): Promise<{
|
|||||||
try {
|
try {
|
||||||
const { get } = await import('~/api/axios-client');
|
const { get } = await import('~/api/axios-client');
|
||||||
|
|
||||||
console.log('🔍 [getRoleRoutesWithPermissions] 开始调用后端API:', `/rbac/roles/${roleId}/routes`);
|
|
||||||
|
|
||||||
const response = await get<any>(`/rbac/roles/${roleId}/routes`);
|
const response = await get<any>(`/rbac/roles/${roleId}/routes`);
|
||||||
console.log('📦 [getRoleRoutesWithPermissions] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
@@ -718,11 +406,6 @@ export async function getRoleRoutesWithPermissions(roleId: number): Promise<{
|
|||||||
const selectedRouteIds = collectRouteIds(mappedRoutes);
|
const selectedRouteIds = collectRouteIds(mappedRoutes);
|
||||||
const selectedPermissionIds = collectPermissionIds(mappedRoutes);
|
const selectedPermissionIds = collectPermissionIds(mappedRoutes);
|
||||||
|
|
||||||
console.log('✅ [getRoleRoutesWithPermissions] 成功获取路由权限数据');
|
|
||||||
console.log(' - 路由数量:', mappedRoutes.length);
|
|
||||||
console.log(' - 已选路由ID:', selectedRouteIds);
|
|
||||||
console.log(' - 已选权限ID:', selectedPermissionIds);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
routes: mappedRoutes,
|
routes: mappedRoutes,
|
||||||
selectedRouteIds,
|
selectedRouteIds,
|
||||||
@@ -750,8 +433,6 @@ export async function saveRoleApiPermissions(
|
|||||||
try {
|
try {
|
||||||
const { post } = await import('~/api/axios-client');
|
const { post } = await import('~/api/axios-client');
|
||||||
|
|
||||||
console.log('🔍 [saveRoleApiPermissions] 开始调用后端API:', `/api/v3/rbac/roles/${roleId}/permissions`, permissionIds);
|
|
||||||
|
|
||||||
// 构建权限配置
|
// 构建权限配置
|
||||||
const permissions = permissionIds.map(id => ({
|
const permissions = permissionIds.map(id => ({
|
||||||
permission_id: id,
|
permission_id: id,
|
||||||
@@ -764,8 +445,6 @@ export async function saveRoleApiPermissions(
|
|||||||
replace: true // 替换模式:先删除现有权限,再插入新权限
|
replace: true // 替换模式:先删除现有权限,再插入新权限
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('📦 [saveRoleApiPermissions] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
}
|
}
|
||||||
@@ -778,7 +457,6 @@ export async function saveRoleApiPermissions(
|
|||||||
message = `成功分配 ${assigned_count} 个API权限`;
|
message = `成功分配 ${assigned_count} 个API权限`;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ [saveRoleApiPermissions] API权限保存成功');
|
|
||||||
return { success: true, message };
|
return { success: true, message };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ [saveRoleApiPermissions] 保存API权限失败:', error);
|
console.error('❌ [saveRoleApiPermissions] 保存API权限失败:', error);
|
||||||
@@ -802,14 +480,12 @@ export async function updateRoleRoutePermissions(
|
|||||||
// 导入 axios-client 的 put 函数
|
// 导入 axios-client 的 put 函数
|
||||||
const { put } = await import('~/api/axios-client');
|
const { put } = await import('~/api/axios-client');
|
||||||
|
|
||||||
console.log('🔍 [updateRoleRoutePermissions] 开始调用后端API:', `/rbac/roles/${roleId}/routes`, routeIds);
|
|
||||||
|
|
||||||
// 使用 axios-client 的 put 函数调用真实后端API
|
// 使用 axios-client 的 put 函数调用真实后端API
|
||||||
const response = await put<any>(`/rbac/roles/${roleId}/routes`, {
|
const response = await put<any>(`/rbac/roles/${roleId}/routes`, {
|
||||||
route_ids: routeIds,
|
route_ids: routeIds,
|
||||||
permission: 'RW'
|
permission: 'RW'
|
||||||
});
|
});
|
||||||
console.log('📦 [updateRoleRoutePermissions] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
@@ -824,7 +500,6 @@ export async function updateRoleRoutePermissions(
|
|||||||
message = `成功分配 ${assigned_count} 个路由,移除了 ${removed_count} 个旧路由`;
|
message = `成功分配 ${assigned_count} 个路由,移除了 ${removed_count} 个旧路由`;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ [updateRoleRoutePermissions] 角色权限更新成功');
|
|
||||||
return { success: true, message };
|
return { success: true, message };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ [updateRoleRoutePermissions] 更新角色权限失败:', error);
|
console.error('❌ [updateRoleRoutePermissions] 更新角色权限失败:', error);
|
||||||
@@ -855,8 +530,6 @@ export async function getRoleUsers(
|
|||||||
// 导入 axios-client 的 get 函数
|
// 导入 axios-client 的 get 函数
|
||||||
const { get } = await import('~/api/axios-client');
|
const { get } = await import('~/api/axios-client');
|
||||||
|
|
||||||
console.log('🔍 [getRoleUsers] 开始调用后端API:', `/api/v3/rbac/roles/${roleId}/users`, params);
|
|
||||||
|
|
||||||
// 构建查询参数对象
|
// 构建查询参数对象
|
||||||
const queryParams: Record<string, any> = {};
|
const queryParams: Record<string, any> = {};
|
||||||
if (params?.page) queryParams.page = params.page;
|
if (params?.page) queryParams.page = params.page;
|
||||||
@@ -866,7 +539,6 @@ export async function getRoleUsers(
|
|||||||
|
|
||||||
// 使用 axios-client 的 get 函数调用真实后端API
|
// 使用 axios-client 的 get 函数调用真实后端API
|
||||||
const response = await get<any>(`/api/v3/rbac/roles/${roleId}/users`, queryParams);
|
const response = await get<any>(`/api/v3/rbac/roles/${roleId}/users`, queryParams);
|
||||||
console.log('📦 [getRoleUsers] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
@@ -880,8 +552,6 @@ export async function getRoleUsers(
|
|||||||
items = response.data.items;
|
items = response.data.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ [getRoleUsers] 解析出的用户数组:', items);
|
|
||||||
|
|
||||||
const users = items.map((user: any) => ({
|
const users = items.map((user: any) => ({
|
||||||
id: user.user_id || user.id,
|
id: user.user_id || user.id,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
@@ -893,7 +563,6 @@ export async function getRoleUsers(
|
|||||||
is_leader: user.is_leader || false
|
is_leader: user.is_leader || false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log('✅ [getRoleUsers] 最终返回的用户列表:', users);
|
|
||||||
return users;
|
return users;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ [getRoleUsers] 获取角色用户列表失败:', error);
|
console.error('❌ [getRoleUsers] 获取角色用户列表失败:', error);
|
||||||
@@ -915,8 +584,6 @@ export async function getAllUsers(params?: {
|
|||||||
// 导入 axios-client 的 get 函数
|
// 导入 axios-client 的 get 函数
|
||||||
const { get } = await import('~/api/axios-client');
|
const { get } = await import('~/api/axios-client');
|
||||||
|
|
||||||
console.log('🔍 [getAllUsers] 开始调用后端API:', '/admin/users/users', params);
|
|
||||||
|
|
||||||
// 构建查询参数对象
|
// 构建查询参数对象
|
||||||
const queryParams: Record<string, any> = {};
|
const queryParams: Record<string, any> = {};
|
||||||
if (params?.page) queryParams.page = params.page;
|
if (params?.page) queryParams.page = params.page;
|
||||||
@@ -925,7 +592,6 @@ export async function getAllUsers(params?: {
|
|||||||
|
|
||||||
// 使用 axios-client 的 get 函数,会自动添加 baseURL 和 Authorization
|
// 使用 axios-client 的 get 函数,会自动添加 baseURL 和 Authorization
|
||||||
const response = await get<any>('/admin/users/users', queryParams);
|
const response = await get<any>('/admin/users/users', queryParams);
|
||||||
console.log('📦 [getAllUsers] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
@@ -946,8 +612,6 @@ export async function getAllUsers(params?: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ [getAllUsers] 解析出的用户数组:', users);
|
|
||||||
console.log('✅ [getAllUsers] 用户数量:', users.length);
|
|
||||||
|
|
||||||
const userList = users.map(user => ({
|
const userList = users.map(user => ({
|
||||||
id: user.user_id || user.id, // 优先使用 user_id,兼容不同的后端响应格式
|
id: user.user_id || user.id, // 优先使用 user_id,兼容不同的后端响应格式
|
||||||
@@ -960,7 +624,6 @@ export async function getAllUsers(params?: {
|
|||||||
is_leader: user.is_leader || false
|
is_leader: user.is_leader || false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log('✅ [getAllUsers] 最终返回的用户列表:', userList);
|
|
||||||
return userList;
|
return userList;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ [getAllUsers] 获取用户列表失败:', error);
|
console.error('❌ [getAllUsers] 获取用户列表失败:', error);
|
||||||
@@ -981,13 +644,10 @@ export async function assignUserRoles(
|
|||||||
// 导入 axios-client 的 post 函数
|
// 导入 axios-client 的 post 函数
|
||||||
const { post } = await import('~/api/axios-client');
|
const { post } = await import('~/api/axios-client');
|
||||||
|
|
||||||
console.log('🔍 [assignUserRoles] 开始调用后端API:', `/api/v3/rbac/users/${userId}/roles`, roleIds);
|
|
||||||
|
|
||||||
// 使用 axios-client 的 post 函数调用真实后端API
|
// 使用 axios-client 的 post 函数调用真实后端API
|
||||||
const response = await post<any>(`/api/v3/rbac/users/${userId}/roles`, {
|
const response = await post<any>(`/api/v3/rbac/users/${userId}/roles`, {
|
||||||
role_ids: roleIds
|
role_ids: roleIds
|
||||||
});
|
});
|
||||||
console.log('📦 [assignUserRoles] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
@@ -998,8 +658,6 @@ export async function assignUserRoles(
|
|||||||
if (response.data && response.data.message) {
|
if (response.data && response.data.message) {
|
||||||
message = response.data.message;
|
message = response.data.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ [assignUserRoles] 角色分配成功');
|
|
||||||
return { success: true, message };
|
return { success: true, message };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ [assignUserRoles] 分配用户角色失败:', error);
|
console.error('❌ [assignUserRoles] 分配用户角色失败:', error);
|
||||||
@@ -1023,11 +681,8 @@ export async function revokeUserRole(
|
|||||||
// 导入 axios-client 的 del 函数
|
// 导入 axios-client 的 del 函数
|
||||||
const { del } = await import('~/api/axios-client');
|
const { del } = await import('~/api/axios-client');
|
||||||
|
|
||||||
console.log('🔍 [revokeUserRole] 开始调用后端API:', `/api/v3/rbac/users/${userId}/roles/${roleId}`);
|
|
||||||
|
|
||||||
// 使用 axios-client 的 del 函数调用真实后端API
|
// 使用 axios-client 的 del 函数调用真实后端API
|
||||||
const response = await del<any>(`/api/v3/rbac/users/${userId}/roles/${roleId}`);
|
const response = await del<any>(`/api/v3/rbac/users/${userId}/roles/${roleId}`);
|
||||||
console.log('📦 [revokeUserRole] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
@@ -1039,7 +694,6 @@ export async function revokeUserRole(
|
|||||||
message = response.data.message;
|
message = response.data.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ [revokeUserRole] 角色移除成功');
|
|
||||||
return { success: true, message };
|
return { success: true, message };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ [revokeUserRole] 移除用户角色失败:', error);
|
console.error('❌ [revokeUserRole] 移除用户角色失败:', error);
|
||||||
@@ -1061,8 +715,6 @@ export async function createRole(
|
|||||||
// 导入 axios-client 的 post 函数
|
// 导入 axios-client 的 post 函数
|
||||||
const { post } = await import('~/api/axios-client');
|
const { post } = await import('~/api/axios-client');
|
||||||
|
|
||||||
console.log('🔍 [createRole] 开始调用后端API:', `/api/v3/rbac/roles`, roleData);
|
|
||||||
|
|
||||||
// 使用 axios-client 的 post 函数调用真实后端API
|
// 使用 axios-client 的 post 函数调用真实后端API
|
||||||
const response = await post<any>(`/api/v3/rbac/roles`, {
|
const response = await post<any>(`/api/v3/rbac/roles`, {
|
||||||
role_key: roleData.role_key,
|
role_key: roleData.role_key,
|
||||||
@@ -1072,8 +724,6 @@ export async function createRole(
|
|||||||
metadata: {}
|
metadata: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('📦 [createRole] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
}
|
}
|
||||||
@@ -1127,12 +777,8 @@ export async function updateRole(
|
|||||||
if (roleData.priority !== undefined) updatePayload.priority = roleData.priority;
|
if (roleData.priority !== undefined) updatePayload.priority = roleData.priority;
|
||||||
if (roleData.parent_role_id !== undefined) updatePayload.parent_role_id = roleData.parent_role_id;
|
if (roleData.parent_role_id !== undefined) updatePayload.parent_role_id = roleData.parent_role_id;
|
||||||
|
|
||||||
console.log('🔍 [updateRole] 开始调用后端API:', `/api/v3/rbac/roles/${roleId}`, updatePayload);
|
|
||||||
|
|
||||||
const response = await put<any>(`/api/v3/rbac/roles/${roleId}`, updatePayload);
|
const response = await put<any>(`/api/v3/rbac/roles/${roleId}`, updatePayload);
|
||||||
|
|
||||||
console.log('📦 [updateRole] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
}
|
}
|
||||||
@@ -1162,12 +808,8 @@ export async function deleteRole(
|
|||||||
|
|
||||||
const url = `/api/v3/rbac/roles/${roleId}${force ? '?force=true' : ''}`;
|
const url = `/api/v3/rbac/roles/${roleId}${force ? '?force=true' : ''}`;
|
||||||
|
|
||||||
console.log('🔍 [deleteRole] 开始调用后端API:', url);
|
|
||||||
|
|
||||||
const response = await del<any>(url);
|
const response = await del<any>(url);
|
||||||
|
|
||||||
console.log('📦 [deleteRole] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
}
|
}
|
||||||
@@ -1321,10 +963,7 @@ export async function getRolePermissions(roleId: number): Promise<RolePermission
|
|||||||
try {
|
try {
|
||||||
const { get } = await import('~/api/axios-client');
|
const { get } = await import('~/api/axios-client');
|
||||||
|
|
||||||
console.log('🔍 [getRolePermissions] 开始调用后端API:', `/api/v3/rbac/roles/${roleId}/permissions`);
|
|
||||||
|
|
||||||
const response = await get<any>(`/api/v3/rbac/roles/${roleId}/permissions`);
|
const response = await get<any>(`/api/v3/rbac/roles/${roleId}/permissions`);
|
||||||
console.log('📦 [getRolePermissions] 后端API完整响应:', JSON.stringify(response, null, 2));
|
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
@@ -1342,8 +981,6 @@ export async function getRolePermissions(roleId: number): Promise<RolePermission
|
|||||||
permissions = response.data;
|
permissions = response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ [getRolePermissions] 解析出的权限数组:', permissions);
|
|
||||||
|
|
||||||
return permissions.map(perm => ({
|
return permissions.map(perm => ({
|
||||||
id: perm.id,
|
id: perm.id,
|
||||||
permission_id: perm.permission_id || perm.id, // 兼容:如果没有 permission_id,使用 id
|
permission_id: perm.permission_id || perm.id, // 兼容:如果没有 permission_id,使用 id
|
||||||
@@ -1451,8 +1088,6 @@ export async function getUserRoles(userId: number): Promise<RoleInfo[]> {
|
|||||||
try {
|
try {
|
||||||
const { get } = await import('~/api/axios-client');
|
const { get } = await import('~/api/axios-client');
|
||||||
|
|
||||||
console.log('🔍 [getUserRoles] 开始调用后端API:', `/api/v3/rbac/users/${userId}/roles`);
|
|
||||||
|
|
||||||
const response = await get<any>(`/api/v3/rbac/users/${userId}/roles`);
|
const response = await get<any>(`/api/v3/rbac/users/${userId}/roles`);
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
@@ -1469,8 +1104,6 @@ export async function getUserRoles(userId: number): Promise<RoleInfo[]> {
|
|||||||
roles = response.data.roles;
|
roles = response.data.roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ [getUserRoles] 成功获取用户角色,共', roles.length, '个角色');
|
|
||||||
|
|
||||||
// 将后端数据转换为RoleInfo格式
|
// 将后端数据转换为RoleInfo格式
|
||||||
return roles.map(role => ({
|
return roles.map(role => ({
|
||||||
id: role.id || role.role_id,
|
id: role.id || role.role_id,
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ interface FileContent {
|
|||||||
page_offset?: number;
|
page_offset?: number;
|
||||||
};
|
};
|
||||||
}; // 添加ocrResult属性
|
}; // 添加ocrResult属性
|
||||||
|
ocr_result?:{
|
||||||
|
__meta?: {
|
||||||
|
page_offset?: number;
|
||||||
|
};
|
||||||
|
},
|
||||||
parties: {
|
parties: {
|
||||||
partyA: {
|
partyA: {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -193,7 +198,8 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
|
|||||||
// 如果是PDF文件,直接使用PdfPreview组件
|
// 如果是PDF文件,直接使用PdfPreview组件
|
||||||
if (isPdf && real_path) {
|
if (isPdf && real_path) {
|
||||||
// console.log('[FilePreview] 渲染PDF预览', { real_path, targetPage, charPositions });
|
// console.log('[FilePreview] 渲染PDF预览', { real_path, targetPage, charPositions });
|
||||||
const pageOffset = fileContent.ocrResult?.__meta?.page_offset || 0;
|
// console.log('[FilePreview] 渲染PDF预览', { fileContent });
|
||||||
|
const pageOffset = fileContent.ocrResult?.__meta?.page_offset || fileContent.ocr_result?.__meta?.page_offset || 0;
|
||||||
return (
|
return (
|
||||||
<PdfPreview
|
<PdfPreview
|
||||||
filePath={real_path}
|
filePath={real_path}
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
const filePath = url.searchParams.get("path");
|
const filePath = url.searchParams.get("path");
|
||||||
const isPreview = url.searchParams.get("preview") === "true";
|
const isPreview = url.searchParams.get("preview") === "true";
|
||||||
|
|
||||||
console.log("📄 [PDF Proxy] 请求参数:", {
|
// console.log("📄 [PDF Proxy] 请求参数:", {
|
||||||
path: filePath,
|
// path: filePath,
|
||||||
preview: url.searchParams.get("preview"),
|
// preview: url.searchParams.get("preview"),
|
||||||
isPreview: isPreview
|
// isPreview: isPreview
|
||||||
});
|
// });
|
||||||
|
|
||||||
if (!filePath) {
|
if (!filePath) {
|
||||||
return new Response("缺少文件路径参数", { status: 400 });
|
return new Response("缺少文件路径参数", { status: 400 });
|
||||||
@@ -70,14 +70,14 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
if (isPreview) {
|
if (isPreview) {
|
||||||
// 在浏览器中预览
|
// 在浏览器中预览
|
||||||
headers['Content-Disposition'] = `inline; filename="${encodeURIComponent(fileName)}"`;
|
headers['Content-Disposition'] = `inline; filename="${encodeURIComponent(fileName)}"`;
|
||||||
console.log("📄 [PDF Proxy] 设置为预览模式 (inline)");
|
// console.log("📄 [PDF Proxy] 设置为预览模式 (inline)");
|
||||||
} else {
|
} else {
|
||||||
// 强制下载(默认行为)
|
// 强制下载(默认行为)
|
||||||
headers['Content-Disposition'] = `attachment; filename="${encodeURIComponent(fileName)}"`;
|
headers['Content-Disposition'] = `attachment; filename="${encodeURIComponent(fileName)}"`;
|
||||||
console.log("📄 [PDF Proxy] 设置为下载模式 (attachment)");
|
// console.log("📄 [PDF Proxy] 设置为下载模式 (attachment)");
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("📄 [PDF Proxy] 响应头:", headers);
|
// console.log("📄 [PDF Proxy] 响应头:", headers);
|
||||||
|
|
||||||
// 返回文件
|
// 返回文件
|
||||||
return new Response(blob, { headers });
|
return new Response(blob, { headers });
|
||||||
|
|||||||
@@ -356,17 +356,17 @@ export default function CrossCheckingResult() {
|
|||||||
const fileInfo = {
|
const fileInfo = {
|
||||||
fileName: document.name || "未知文件名",
|
fileName: document.name || "未知文件名",
|
||||||
path: document.path || "未知路径",
|
path: document.path || "未知路径",
|
||||||
contractNumber: document.documentNumber || "未知编号",
|
contractNumber: document.documentNumber || document.document_number || "未知编号",
|
||||||
fileSize: document.size ? formatFileSize(document.size) : "未知大小",
|
fileSize: document.size ? formatFileSize(document.size) : document.file_size ? formatFileSize(document.file_size) : "未知大小",
|
||||||
// 文件格式类型
|
// 文件格式类型
|
||||||
fileFormat: document.fileType ? document.fileType.toUpperCase() : "未知格式",
|
fileFormat: document.fileType ? document.fileType.toUpperCase() : "未知格式",
|
||||||
pageCount: document.pageCount || 0,
|
pageCount: document.pageCount || document.page_count || 0,
|
||||||
uploadTime: document.uploadTime || "未知时间",
|
uploadTime: document.uploadTime || document.created_at || "未知时间",
|
||||||
uploadUser: document.uploadUser || "未知用户",
|
uploadUser: document.uploadUser || "未知用户",
|
||||||
auditStatus: document.auditStatus || 0,
|
auditStatus: document.auditStatus || 0,
|
||||||
legalBasis: document.legalBasis || {},
|
legalBasis: document.legalBasis || {},
|
||||||
// 文件类型(1:合同,2:卷宗。。。)
|
// 文件类型(1:合同,2:卷宗。。。)
|
||||||
fileType: document.type || ""
|
fileType: document.type || document.type_id ? document.type_id.toString() : ''
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建包含真实文档数据的评查数据对象
|
// 创建包含真实文档数据的评查数据对象
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useSearchParams, useNavigate, useLoaderData, useRouteLoaderData } from "@remix-run/react";
|
import { useSearchParams, useNavigate, useLoaderData, useRouteLoaderData, useRevalidator } from "@remix-run/react";
|
||||||
import { ActionFunctionArgs, LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
|
import { ClientLoaderFunctionArgs, MetaFunction } from "@remix-run/react";
|
||||||
import { Table } from "~/components/ui/Table";
|
import { Table } from "~/components/ui/Table";
|
||||||
import { Card } from "~/components/ui/Card";
|
import { Card } from "~/components/ui/Card";
|
||||||
import { Button } from "~/components/ui/Button";
|
import { Button } from "~/components/ui/Button";
|
||||||
@@ -45,91 +45,56 @@ interface LoaderData {
|
|||||||
pageSize: number;
|
pageSize: number;
|
||||||
currentPage: number;
|
currentPage: number;
|
||||||
error?: string;
|
error?: string;
|
||||||
frontendJWT?: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载函数 - 获取入口模块列表
|
// 🔑 客户端加载函数 - 在浏览器端执行,axios-client 会自动添加 JWT
|
||||||
export async function loader({ request }: LoaderFunctionArgs) {
|
export async function clientLoader({ request }: ClientLoaderFunctionArgs) {
|
||||||
try {
|
try {
|
||||||
// 获取用户会话信息
|
|
||||||
const { getUserSession } = await import("~/api/login/auth.server");
|
|
||||||
const { frontendJWT } = await getUserSession(request);
|
|
||||||
|
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
const name = url.searchParams.get('name') || undefined;
|
const name = url.searchParams.get('name') || undefined;
|
||||||
const area = url.searchParams.get('area') || undefined;
|
const area = url.searchParams.get('area') || undefined;
|
||||||
const page = parseInt(url.searchParams.get('page') || '1', 10);
|
const page = parseInt(url.searchParams.get('page') || '1', 10);
|
||||||
const pageSize = parseInt(url.searchParams.get('pageSize') || '10', 10);
|
const pageSize = parseInt(url.searchParams.get('pageSize') || '10', 10);
|
||||||
|
|
||||||
// 构建搜索参数
|
// 构建搜索参数(注意:API使用page_size而不是pageSize)
|
||||||
const searchParams: EntryModuleSearchParams = {
|
const searchParams: EntryModuleSearchParams = {
|
||||||
name,
|
name,
|
||||||
area,
|
area,
|
||||||
page,
|
page,
|
||||||
pageSize
|
page_size: pageSize // API使用page_size
|
||||||
};
|
};
|
||||||
|
|
||||||
const modulesResponse = await getEntryModules(searchParams, frontendJWT);
|
// ✅ 不需要传递 JWT,axios-client 会自动从 localStorage 读取并添加
|
||||||
|
const modulesResponse = await getEntryModules(searchParams);
|
||||||
|
|
||||||
if (modulesResponse.error) {
|
if (modulesResponse.error) {
|
||||||
console.error("获取入口模块失败:", modulesResponse.error);
|
console.error("❌ [clientLoader] 获取入口模块失败:", modulesResponse.error);
|
||||||
throw new Error(modulesResponse.error);
|
throw new Error(modulesResponse.error);
|
||||||
}
|
}
|
||||||
const modulesResult = modulesResponse.data?.modules || [];
|
|
||||||
|
|
||||||
return Response.json({
|
const modulesResult = modulesResponse.data?.modules || [];
|
||||||
|
const totalCount = modulesResponse.data?.total || modulesResult.length;
|
||||||
|
|
||||||
|
return {
|
||||||
modules: modulesResult,
|
modules: modulesResult,
|
||||||
total: modulesResponse.data?.total || modulesResult.length,
|
total: totalCount,
|
||||||
pageSize,
|
pageSize,
|
||||||
currentPage: page,
|
currentPage: page
|
||||||
frontendJWT
|
};
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("加载入口模块列表失败:", error);
|
console.error("加载入口模块列表失败:", error);
|
||||||
return Response.json(
|
return {
|
||||||
{
|
|
||||||
modules: [],
|
modules: [],
|
||||||
total: 0,
|
total: 0,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
error: error instanceof Error ? error.message : "加载入口模块列表失败"
|
error: error instanceof Error ? error.message : "加载入口模块列表失败"
|
||||||
},
|
};
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 动作函数 - 处理删除请求
|
|
||||||
export async function action({ request }: ActionFunctionArgs) {
|
|
||||||
// 获取表单数据
|
|
||||||
const formData = await request.formData();
|
|
||||||
const id = formData.get("id") as string;
|
|
||||||
const intent = formData.get("intent") as string;
|
|
||||||
const { getUserSession } = await import("~/api/login/auth.server");
|
|
||||||
const { frontendJWT } = await getUserSession(request);
|
|
||||||
|
|
||||||
if (intent === "delete" && id) {
|
|
||||||
try {
|
|
||||||
const result = await deleteEntryModule(parseInt(id), frontendJWT || undefined);
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
return Response.json({ success: false, error: result.error }, { status: 500 });
|
|
||||||
}
|
|
||||||
|
|
||||||
return Response.json({ success: true });
|
|
||||||
} catch (error) {
|
|
||||||
return Response.json(
|
|
||||||
{ success: false, error: error instanceof Error ? error.message : "删除入口模块失败" },
|
|
||||||
{ status: 500 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Response.json({ success: false, error: "无效的操作" }, { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 地区选项
|
// 地区选项
|
||||||
const AREA_OPTIONS = [
|
const AREA_OPTIONS = [
|
||||||
{ value: "", label: "全部地区" },
|
|
||||||
{ value: "梅州", label: "梅州" },
|
{ value: "梅州", label: "梅州" },
|
||||||
{ value: "云浮", label: "云浮" },
|
{ value: "云浮", label: "云浮" },
|
||||||
{ value: "揭阳", label: "揭阳" },
|
{ value: "揭阳", label: "揭阳" },
|
||||||
@@ -142,9 +107,11 @@ export default function EntryModulesList() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
|
const revalidator = useRevalidator();
|
||||||
|
|
||||||
// 获取加载器数据
|
// 获取加载器数据
|
||||||
const { modules, total, error, frontendJWT } = useLoaderData<LoaderData>();
|
const loaderData = useLoaderData<LoaderData>();
|
||||||
|
const { modules, total, error } = loaderData;
|
||||||
|
|
||||||
// 获取用户角色并判断权限
|
// 获取用户角色并判断权限
|
||||||
const rootData = useRouteLoaderData("root") as { userRole: string };
|
const rootData = useRouteLoaderData("root") as { userRole: string };
|
||||||
@@ -218,30 +185,23 @@ export default function EntryModulesList() {
|
|||||||
type: "warning",
|
type: "warning",
|
||||||
confirmText: "删除",
|
confirmText: "删除",
|
||||||
cancelText: "取消",
|
cancelText: "取消",
|
||||||
confirmDelay: 4,
|
confirmDelay: 3,
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
// 直接调用 API 删除函数
|
||||||
formData.append('id', id.toString());
|
const result = await deleteEntryModule(id);
|
||||||
formData.append('intent', 'delete');
|
|
||||||
|
|
||||||
const response = await fetch('/entry-modules', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
toastService.success('删除成功!');
|
toastService.success('删除成功!');
|
||||||
// 刷新页面
|
// 重新验证数据,刷新表格
|
||||||
window.location.reload();
|
revalidator.revalidate();
|
||||||
} else {
|
} else {
|
||||||
toastService.error(`删除失败: ${result.error || '未知错误'}`);
|
toastService.error(`删除失败: ${result.error || '未知错误'}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('删除入口模块失败:', error);
|
||||||
toastService.error(`删除失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
toastService.error(`删除失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||||
} finally {
|
} finally {
|
||||||
setIsDeleting(false);
|
setIsDeleting(false);
|
||||||
@@ -272,42 +232,36 @@ export default function EntryModulesList() {
|
|||||||
|
|
||||||
// 表格列定义
|
// 表格列定义
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
|
||||||
key: 'id',
|
|
||||||
title: 'ID',
|
|
||||||
width: '80px',
|
|
||||||
render: (row: EntryModule) => row.id
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
title: '模块名称',
|
title: '模块名称',
|
||||||
width: '200px',
|
width: '200px',
|
||||||
render: (row: EntryModule) => (
|
render: (_: any, record: EntryModule) => (
|
||||||
<span className="font-medium text-gray-900">{row.name}</span>
|
<span className="font-medium text-gray-900">{record.name}</span>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'description',
|
key: 'description',
|
||||||
title: '描述',
|
title: '描述',
|
||||||
width: '250px',
|
width: '250px',
|
||||||
render: (row: EntryModule) => (
|
render: (_: any, record: EntryModule) => (
|
||||||
<span className="text-gray-600">{row.description || '-'}</span>
|
<span className="text-gray-600">{record.description || '-'}</span>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'logo',
|
key: 'logo',
|
||||||
title: 'Logo图片',
|
title: 'Logo图片',
|
||||||
width: '150px',
|
width: '150px',
|
||||||
render: (row: EntryModule) => {
|
render: (_: any, record: EntryModule) => {
|
||||||
if (!row.path) {
|
if (!record.path) {
|
||||||
return <span className="text-gray-400">未上传</span>;
|
return <span className="text-gray-400">未上传</span>;
|
||||||
}
|
}
|
||||||
const logoUrl = `${DOCUMENT_URL}${row.path}`;
|
const logoUrl = `${DOCUMENT_URL}${record.path}`;
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<img
|
<img
|
||||||
src={logoUrl}
|
src={logoUrl}
|
||||||
alt={row.name}
|
alt={record.name}
|
||||||
className="h-8 w-8 object-contain rounded"
|
className="h-8 w-8 object-contain rounded"
|
||||||
onError={(e) => {
|
onError={(e) => {
|
||||||
(e.target as HTMLImageElement).style.display = 'none';
|
(e.target as HTMLImageElement).style.display = 'none';
|
||||||
@@ -330,15 +284,18 @@ export default function EntryModulesList() {
|
|||||||
key: 'areas',
|
key: 'areas',
|
||||||
title: '适用地区',
|
title: '适用地区',
|
||||||
width: '200px',
|
width: '200px',
|
||||||
render: (row: EntryModule) => (
|
render: (_: any, record: EntryModule) => (
|
||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex flex-wrap gap-1">
|
||||||
{row.areas && row.areas.length > 0 ? (
|
{record.areas && record.areas.length > 0 ? (
|
||||||
row.areas.map((area, index) => (
|
record.areas
|
||||||
|
.filter(areaConfig => areaConfig.enabled !== false) // 只显示启用的地区
|
||||||
|
.sort((a, b) => a.sort_order - b.sort_order) // 按排序号排序
|
||||||
|
.map((areaConfig, index) => (
|
||||||
<span
|
<span
|
||||||
key={index}
|
key={index}
|
||||||
className="inline-block px-2 py-1 text-xs bg-blue-100 text-blue-800 rounded"
|
className="inline-block px-2 py-1 text-xs bg-blue-100 text-blue-800 rounded"
|
||||||
>
|
>
|
||||||
{area}
|
{areaConfig.area}
|
||||||
</span>
|
</span>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
@@ -351,34 +308,34 @@ export default function EntryModulesList() {
|
|||||||
key: 'created_at',
|
key: 'created_at',
|
||||||
title: '创建时间',
|
title: '创建时间',
|
||||||
width: '180px',
|
width: '180px',
|
||||||
render: (row: EntryModule) =>
|
render: (_: any, record: EntryModule) =>
|
||||||
row.created_at ? new Date(row.created_at).toLocaleString('zh-CN') : '-'
|
record.created_at ? new Date(record.created_at).toLocaleString('zh-CN') : '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: '150px',
|
width: '180px',
|
||||||
render: (row: EntryModule) => (
|
render: (_: any, record: EntryModule) => (
|
||||||
<div className="flex items-center space-x-2">
|
<div className="operations-cell">
|
||||||
<Button
|
<button
|
||||||
type="link"
|
onClick={() => handleEdit(record.id!)}
|
||||||
size="small"
|
className="operation-btn"
|
||||||
onClick={() => handleEdit(row.id!)}
|
|
||||||
disabled={!hasEditPermission}
|
disabled={!hasEditPermission}
|
||||||
title={hasEditPermission ? "编辑" : "无权限"}
|
title={hasEditPermission ? "编辑入口模块" : "无权限"}
|
||||||
>
|
>
|
||||||
编辑
|
<i className="ri-edit-line"></i> {hasEditPermission ? '编辑' : '查看'}
|
||||||
</Button>
|
</button>
|
||||||
<Button
|
{hasEditPermission && (
|
||||||
type="link"
|
<button
|
||||||
size="small"
|
type="button"
|
||||||
danger
|
className="operation-btn !text-[--color-error]"
|
||||||
onClick={() => handleDelete(row.id!)}
|
onClick={() => handleDelete(record.id!)}
|
||||||
disabled={isDeleting || !hasEditPermission}
|
disabled={isDeleting}
|
||||||
title={hasEditPermission ? "删除" : "无权限"}
|
title="删除入口模块"
|
||||||
>
|
>
|
||||||
删除
|
<i className="ri-delete-bin-line"></i> 删除
|
||||||
</Button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -406,11 +363,6 @@ export default function EntryModulesList() {
|
|||||||
|
|
||||||
{/* 筛选面板 */}
|
{/* 筛选面板 */}
|
||||||
<FilterPanel onReset={handleReset}>
|
<FilterPanel onReset={handleReset}>
|
||||||
<SearchFilter
|
|
||||||
placeholder="请输入入口模块名称"
|
|
||||||
defaultValue={name}
|
|
||||||
onSearch={handleNameSearch}
|
|
||||||
/>
|
|
||||||
<FilterSelect
|
<FilterSelect
|
||||||
label="适用地区"
|
label="适用地区"
|
||||||
name="area"
|
name="area"
|
||||||
@@ -418,12 +370,20 @@ export default function EntryModulesList() {
|
|||||||
options={AREA_OPTIONS}
|
options={AREA_OPTIONS}
|
||||||
onChange={handleFilterChange}
|
onChange={handleFilterChange}
|
||||||
/>
|
/>
|
||||||
|
<SearchFilter
|
||||||
|
label="模块名称"
|
||||||
|
placeholder="请输入入口模块名称"
|
||||||
|
value={name}
|
||||||
|
onSearch={handleNameSearch}
|
||||||
|
className="filter-item-wide"
|
||||||
|
/>
|
||||||
</FilterPanel>
|
</FilterPanel>
|
||||||
|
|
||||||
{/* 表格 */}
|
{/* 表格 */}
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={modules || []}
|
dataSource={modules || []}
|
||||||
|
rowKey="id"
|
||||||
loading={false}
|
loading={false}
|
||||||
emptyText="暂无入口模块数据"
|
emptyText="暂无入口模块数据"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { useNavigate, useSearchParams, useLoaderData } from "@remix-run/react";
|
import { useNavigate, useSearchParams, useLoaderData } from "@remix-run/react";
|
||||||
import { ActionFunctionArgs, LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
|
import { ClientLoaderFunctionArgs, MetaFunction } from "@remix-run/react";
|
||||||
import { Card } from "~/components/ui/Card";
|
import { Card } from "~/components/ui/Card";
|
||||||
import { Button } from "~/components/ui/Button";
|
import { Button } from "~/components/ui/Button";
|
||||||
import { toastService } from "~/components/ui/Toast";
|
import { toastService } from "~/components/ui/Toast";
|
||||||
@@ -9,9 +9,18 @@ import {
|
|||||||
getEntryModuleById,
|
getEntryModuleById,
|
||||||
createEntryModule,
|
createEntryModule,
|
||||||
updateEntryModule,
|
updateEntryModule,
|
||||||
type EntryModule
|
type EntryModule,
|
||||||
|
type AreaConfig
|
||||||
} from "~/api/entry-modules/entry-modules";
|
} from "~/api/entry-modules/entry-modules";
|
||||||
import { API_BASE_URL, DOCUMENT_URL } from "~/config/api-config";
|
import { API_BASE_URL, DOCUMENT_URL } from "~/config/api-config";
|
||||||
|
import entryModulesStyles from "~/styles/pages/entry-modules.css?url";
|
||||||
|
|
||||||
|
// 引入CSS样式
|
||||||
|
export function links() {
|
||||||
|
return [
|
||||||
|
{ rel: "stylesheet", href: entryModulesStyles }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
// 页面元数据
|
// 页面元数据
|
||||||
export const meta: MetaFunction = () => {
|
export const meta: MetaFunction = () => {
|
||||||
@@ -33,38 +42,31 @@ export const handle = {
|
|||||||
interface LoaderData {
|
interface LoaderData {
|
||||||
module?: EntryModule;
|
module?: EntryModule;
|
||||||
error?: string;
|
error?: string;
|
||||||
frontendJWT?: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载函数 - 获取入口模块数据(编辑模式)
|
// 🔑 客户端加载函数 - 在浏览器端执行,axios-client 会自动添加 JWT
|
||||||
export async function loader({ request }: LoaderFunctionArgs) {
|
export async function clientLoader({ request }: ClientLoaderFunctionArgs) {
|
||||||
try {
|
try {
|
||||||
const { getUserSession } = await import("~/api/login/auth.server");
|
|
||||||
const { frontendJWT } = await getUserSession(request);
|
|
||||||
|
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
const id = url.searchParams.get('id');
|
const id = url.searchParams.get('id');
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
const moduleResponse = await getEntryModuleById(parseInt(id), frontendJWT);
|
// ✅ 不需要传递 JWT,axios-client 会自动处理
|
||||||
|
const moduleResponse = await getEntryModuleById(parseInt(id));
|
||||||
if (moduleResponse.error) {
|
if (moduleResponse.error) {
|
||||||
throw new Error(moduleResponse.error);
|
throw new Error(moduleResponse.error);
|
||||||
}
|
}
|
||||||
return Response.json({
|
return {
|
||||||
module: moduleResponse.data,
|
module: moduleResponse.data
|
||||||
frontendJWT
|
};
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.json({ frontendJWT });
|
return {};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("加载入口模块失败:", error);
|
console.error("加载入口模块失败:", error);
|
||||||
return Response.json(
|
return {
|
||||||
{
|
error: error instanceof Error ? error.message : "加载入口模块失败"
|
||||||
error: error || "加载入口模块失败",
|
};
|
||||||
status: 500
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +83,7 @@ const AREA_OPTIONS = [
|
|||||||
export default function EntryModuleNew() {
|
export default function EntryModuleNew() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const { module, error, frontendJWT } = useLoaderData<LoaderData>();
|
const { module, error } = useLoaderData<LoaderData>();
|
||||||
|
|
||||||
const id = searchParams.get('id');
|
const id = searchParams.get('id');
|
||||||
const isEditMode = !!id;
|
const isEditMode = !!id;
|
||||||
@@ -89,7 +91,10 @@ export default function EntryModuleNew() {
|
|||||||
// 表单状态
|
// 表单状态
|
||||||
const [name, setName] = useState(module?.name || '');
|
const [name, setName] = useState(module?.name || '');
|
||||||
const [description, setDescription] = useState(module?.description || '');
|
const [description, setDescription] = useState(module?.description || '');
|
||||||
const [selectedAreas, setSelectedAreas] = useState<string[]>(module?.areas || []);
|
// 🔑 从 AreaConfig[] 提取地区名称数组
|
||||||
|
const [selectedAreas, setSelectedAreas] = useState<string[]>(
|
||||||
|
module?.areas ? module.areas.map(a => a.area) : []
|
||||||
|
);
|
||||||
const [logoFile, setLogoFile] = useState<File | null>(null);
|
const [logoFile, setLogoFile] = useState<File | null>(null);
|
||||||
const [logoPreview, setLogoPreview] = useState<string | null>(
|
const [logoPreview, setLogoPreview] = useState<string | null>(
|
||||||
module?.path ? `${DOCUMENT_URL}${module.path}` : null
|
module?.path ? `${DOCUMENT_URL}${module.path}` : null
|
||||||
@@ -168,10 +173,14 @@ export default function EntryModuleNew() {
|
|||||||
formData.append('file', logoFile);
|
formData.append('file', logoFile);
|
||||||
formData.append('folder', 'entryModule');
|
formData.append('folder', 'entryModule');
|
||||||
|
|
||||||
|
// ✅ 不需要手动添加 Authorization 头
|
||||||
|
// fetch 可以自动使用浏览器的认证信息,或者我们也可以使用 axios
|
||||||
|
const token = localStorage.getItem('access_token');
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE_URL}/admin/upload`, {
|
const response = await fetch(`${API_BASE_URL}/admin/upload`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${frontendJWT}`
|
'Authorization': `Bearer ${token}`
|
||||||
},
|
},
|
||||||
body: formData
|
body: formData
|
||||||
});
|
});
|
||||||
@@ -210,18 +219,21 @@ export default function EntryModuleNew() {
|
|||||||
logoPath = await uploadLogo();
|
logoPath = await uploadLogo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔑 准备提交数据
|
||||||
|
// areas 字段会在 API 层自动转换为 AreaConfig[] 格式
|
||||||
const moduleData = {
|
const moduleData = {
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
description: description.trim() || undefined,
|
description: description.trim() || undefined,
|
||||||
path: logoPath,
|
path: logoPath,
|
||||||
areas: selectedAreas
|
areas: selectedAreas // 字符串数组,API会自动转换
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ✅ 不需要传递 JWT,axios-client 会自动处理
|
||||||
let result;
|
let result;
|
||||||
if (isEditMode) {
|
if (isEditMode) {
|
||||||
result = await updateEntryModule(parseInt(id!), moduleData, frontendJWT);
|
result = await updateEntryModule(parseInt(id!), moduleData);
|
||||||
} else {
|
} else {
|
||||||
result = await createEntryModule(moduleData, frontendJWT);
|
result = await createEntryModule(moduleData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
@@ -280,7 +292,7 @@ export default function EntryModuleNew() {
|
|||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
placeholder="请输入模块名称,如:合同管理"
|
placeholder="请输入模块名称,如:合同管理"
|
||||||
maxLength={255}
|
maxLength={255}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="w-full px-3 py-2 border border-gray-300 rounded-md"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -291,7 +303,7 @@ export default function EntryModuleNew() {
|
|||||||
value={description}
|
value={description}
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
placeholder="请输入模块描述"
|
placeholder="请输入模块描述"
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="w-full px-3 py-2 border border-gray-300 rounded-md"
|
||||||
rows={4}
|
rows={4}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -349,7 +361,7 @@ export default function EntryModuleNew() {
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={selectedAreas.includes(option.value)}
|
checked={selectedAreas.includes(option.value)}
|
||||||
onChange={() => handleAreaToggle(option.value)}
|
onChange={() => handleAreaToggle(option.value)}
|
||||||
className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
|
className="w-4 h-4 border-gray-300 rounded cursor-pointer"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-gray-700">{option.label}</span>
|
<span className="text-sm text-gray-700">{option.label}</span>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
+35
-15
@@ -29,7 +29,7 @@ import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs } f
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useNavigate, useLoaderData, useFetcher } from "@remix-run/react";
|
import { useNavigate, useLoaderData, useFetcher } from "@remix-run/react";
|
||||||
import reviewsStyles from "~/styles/reviews.css?url";
|
import reviewsStyles from "~/styles/reviews.css?url";
|
||||||
import { getReviewPoints, updateReviewResult, confirmReviewResults } from "~/api/evaluation_points/reviews";
|
import { getReviewPoints, getReviewPoints_fromApi, updateReviewResult, confirmReviewResults } from "~/api/evaluation_points/reviews";
|
||||||
import { toastService } from "~/components/ui/Toast";
|
import { toastService } from "~/components/ui/Toast";
|
||||||
|
|
||||||
// 导入评查详情页面组件
|
// 导入评查详情页面组件
|
||||||
@@ -189,8 +189,11 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
const { getUserSession } = await import("~/api/login/auth.server");
|
const { getUserSession } = await import("~/api/login/auth.server");
|
||||||
const { userInfo, frontendJWT } = await getUserSession(request);
|
const { userInfo, frontendJWT } = await getUserSession(request);
|
||||||
|
|
||||||
// 获取评查点数据,传递request对象
|
// 🆕 使用新的后端API获取评查点数据(单次请求替代原7次请求)
|
||||||
const reviewData = await getReviewPoints(id, request);
|
const reviewData = await getReviewPoints_fromApi(id, request);
|
||||||
|
|
||||||
|
// ⚠️ 原方法已注释(保留以备回退)
|
||||||
|
// const reviewData = await getReviewPoints(id, request);
|
||||||
|
|
||||||
if ('error' in reviewData && reviewData.error) {
|
if ('error' in reviewData && reviewData.error) {
|
||||||
console.error("[Reviews Loader] 获取评查点数据错误:", reviewData.error);
|
console.error("[Reviews Loader] 获取评查点数据错误:", reviewData.error);
|
||||||
@@ -309,6 +312,23 @@ export default function ReviewDetails() {
|
|||||||
message: string;
|
message: string;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
|
// 🐛 调试:打印 loader 返回的完整数据到浏览器控制台
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
console.group('📦 [Reviews] Loader 数据');
|
||||||
|
// console.log('完整数据:', loaderData);
|
||||||
|
console.log('文档信息:', document);
|
||||||
|
// console.log('评查点数量:', reviewPoints?.length);
|
||||||
|
// console.log('评查点数量:', reviewPoints);
|
||||||
|
// console.log('统计信息:', statistics);
|
||||||
|
// console.log('评查信息:', reviewInfo);
|
||||||
|
// console.log('比对文档:', comparison_document);
|
||||||
|
// console.log('用户信息:', loaderData.userInfo);
|
||||||
|
// console.log('JWT Token (前20位):', frontendJWT?.substring(0, 20) + '...');
|
||||||
|
console.groupEnd();
|
||||||
|
}
|
||||||
|
}, [loaderData, document, reviewPoints, statistics, reviewInfo, comparison_document, frontendJWT]);
|
||||||
|
|
||||||
// loader 数据加载出错
|
// loader 数据加载出错
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
loadingBarService.hide();
|
loadingBarService.hide();
|
||||||
@@ -340,17 +360,17 @@ export default function ReviewDetails() {
|
|||||||
const fileInfo = {
|
const fileInfo = {
|
||||||
fileName: document.name || "未知文件名",
|
fileName: document.name || "未知文件名",
|
||||||
path: document.path || "未知路径",
|
path: document.path || "未知路径",
|
||||||
contractNumber: document.documentNumber || "未知编号",
|
contractNumber: document.documentNumber || document.document_number || "未知编号",
|
||||||
fileSize: document.size ? formatFileSize(document.size) : "未知大小",
|
fileSize: document.size ? formatFileSize(document.size) : document.file_size ? formatFileSize(document.file_size) : "未知大小",
|
||||||
// 文件格式类型
|
// 文件格式类型
|
||||||
fileFormat: document.fileType ? document.fileType.toUpperCase() : "未知格式",
|
fileFormat: document.fileType ? document.fileType.toUpperCase() : "未知格式",
|
||||||
pageCount: document.pageCount || 0,
|
pageCount: document.pageCount || document.page_count || 0,
|
||||||
uploadTime: document.uploadTime || "未知时间",
|
uploadTime: document.uploadTime || document.created_at || "未知时间",
|
||||||
uploadUser: document.uploadUser || "未知用户",
|
uploadUser: document.uploadUser || "未知用户",
|
||||||
auditStatus: document.auditStatus || 0,
|
auditStatus: document.auditStatus || 0,
|
||||||
legalBasis: document.legalBasis || {},
|
legalBasis: document.legalBasis || {},
|
||||||
// 文件类型(1:合同,2:卷宗。。。)
|
// 文件类型(1:合同,2:卷宗。。。)
|
||||||
fileType: document.type || ""
|
fileType: document.type || document.type_id ? document.type_id.toString() : ''
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建包含真实文档数据的评查数据对象
|
// 创建包含真实文档数据的评查数据对象
|
||||||
@@ -725,13 +745,13 @@ export default function ReviewDetails() {
|
|||||||
{/* 左侧:文件预览 */}
|
{/* 左侧:文件预览 */}
|
||||||
<div className="w-full lg:w-[65%]">
|
<div className="w-full lg:w-[65%]">
|
||||||
{(() => {
|
{(() => {
|
||||||
console.log('[Reviews] 准备渲染FilePreview', {
|
// console.log('[Reviews] 准备渲染FilePreview', {
|
||||||
hasDocument: !!document,
|
// hasDocument: !!document,
|
||||||
documentPath: document?.path,
|
// documentPath: document?.path,
|
||||||
targetPage,
|
// targetPage,
|
||||||
hasCharPositions: !!charPositions,
|
// hasCharPositions: !!charPositions,
|
||||||
charPositionsLength: charPositions?.length
|
// charPositionsLength: charPositions?.length
|
||||||
});
|
// });
|
||||||
return (
|
return (
|
||||||
<FilePreview
|
<FilePreview
|
||||||
fileContent={document}
|
fileContent={document}
|
||||||
|
|||||||
@@ -881,11 +881,6 @@ export default function RolePermissions() {
|
|||||||
// 从 getRolePermissions 结果中提取已分配的权限ID
|
// 从 getRolePermissions 结果中提取已分配的权限ID
|
||||||
const assignedPermissionIds = rolePermissions.map(p => p.permission_id);
|
const assignedPermissionIds = rolePermissions.map(p => p.permission_id);
|
||||||
|
|
||||||
console.log('🔍 [handleSelectRole] 角色权限数据:');
|
|
||||||
console.log(' - routePermissionsMap:', permMap);
|
|
||||||
console.log(' - rolePermissions:', rolePermissions);
|
|
||||||
console.log(' - assignedPermissionIds:', assignedPermissionIds);
|
|
||||||
|
|
||||||
setRoutePermissionsMap(permMap);
|
setRoutePermissionsMap(permMap);
|
||||||
setSelectedRouteIds(routeIds);
|
setSelectedRouteIds(routeIds);
|
||||||
setSelectedPermissionIds(assignedPermissionIds); // 使用实际已分配的权限ID
|
setSelectedPermissionIds(assignedPermissionIds); // 使用实际已分配的权限ID
|
||||||
|
|||||||
@@ -10,6 +10,30 @@
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 筛选面板 - 搜索框加宽 */
|
||||||
|
.entry-modules-page .filter-item-wide {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-modules-page .filter-item-wide .filter-control {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 操作按钮样式 */
|
||||||
|
.entry-modules-page .operations-cell {
|
||||||
|
@apply flex space-x-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-modules-page .operation-btn {
|
||||||
|
@apply text-sm flex items-center text-[--color-primary] bg-transparent hover:underline p-2 cursor-pointer border-none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-modules-page .operation-btn:disabled {
|
||||||
|
@apply opacity-50 cursor-not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
/* 页面头部 */
|
/* 页面头部 */
|
||||||
.page-header {
|
.page-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -40,6 +64,50 @@
|
|||||||
border-top: 1px solid #e5e7eb;
|
border-top: 1px solid #e5e7eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 表单输入框样式 - 参照rule-groups.new */
|
||||||
|
/* 使用更高优先级的选择器覆盖Tailwind */
|
||||||
|
.entry-modules-new-page .form-content input[type="text"],
|
||||||
|
.entry-modules-new-page .form-content input[type="file"],
|
||||||
|
.entry-modules-new-page .form-content textarea,
|
||||||
|
.entry-modules-new-page .form-content select {
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus状态 - 绿色主题 */
|
||||||
|
.entry-modules-new-page .form-content input[type="text"]:focus,
|
||||||
|
.entry-modules-new-page .form-content textarea:focus,
|
||||||
|
.entry-modules-new-page .form-content select:focus {
|
||||||
|
border-color: #00684a !important;
|
||||||
|
box-shadow: 0 0 0 1px rgba(0, 104, 74, 0.5) !important;
|
||||||
|
outline: 2px solid transparent !important;
|
||||||
|
outline-offset: 2px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover状态 */
|
||||||
|
.entry-modules-new-page .form-content input[type="text"]:hover,
|
||||||
|
.entry-modules-new-page .form-content textarea:hover,
|
||||||
|
.entry-modules-new-page .form-content select:hover {
|
||||||
|
border-color: #00684a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通用input覆盖 - 排除checkbox和radio */
|
||||||
|
.entry-modules-new-page input:is(:not([type="checkbox"]):not([type="radio"]):not([type="file"])):focus {
|
||||||
|
border-color: #00684a !important;
|
||||||
|
box-shadow: 0 0 0 1px rgba(0, 104, 74, 0.5) !important;
|
||||||
|
outline: 2px solid transparent !important;
|
||||||
|
outline-offset: 2px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 复选框样式 - 绿色主题 */
|
||||||
|
.entry-modules-new-page input[type="checkbox"] {
|
||||||
|
accent-color: #00684a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-modules-new-page input[type="checkbox"]:focus {
|
||||||
|
outline: 2px solid #00684a;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Logo预览样式 */
|
/* Logo预览样式 */
|
||||||
.logo-preview {
|
.logo-preview {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|||||||
Reference in New Issue
Block a user