diff --git a/app/api/axios-client.ts b/app/api/axios-client.ts index d10a326..30f5ba2 100644 --- a/app/api/axios-client.ts +++ b/app/api/axios-client.ts @@ -178,13 +178,9 @@ axiosInstance.interceptors.response.use( if (isAxiosError(error) && error.response?.status === 403) { console.warn('⚠️ [403 Forbidden] 无权限访问:', error.config?.url); - // 只在客户端显示 toast 提示 - if (typeof window !== 'undefined') { - toastService.warning('无权限访问该资源'); - } - // 修改错误消息为友好提示,避免显示原始的 "Request failed with status code 403" - error.message = '无权限访问该资源'; + // 注意:不在这里显示 toast,由组件层统一处理,避免重复提示 + error.message = '无权限'; } return Promise.reject(error); diff --git a/app/api/entry-modules/entry-modules.ts b/app/api/entry-modules/entry-modules.ts index d5e6508..e729d8d 100644 --- a/app/api/entry-modules/entry-modules.ts +++ b/app/api/entry-modules/entry-modules.ts @@ -1,9 +1,19 @@ /** * 入口模块管理 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 { id?: number; name: string; - description?: string; - path?: string; // logo图片路径 - areas?: string[]; // 地区数组 + description?: string | null; + path?: string | null; // logo图片路径 + areas?: AreaConfig[] | null; // 地区配置列表 created_at?: string; updated_at?: string; } @@ -25,7 +35,7 @@ export interface EntryModuleSearchParams { name?: string; area?: string; page?: number; - pageSize?: number; + page_size?: number; // 注意:API使用page_size而不是pageSize } /** @@ -36,83 +46,94 @@ export interface EntryModulesResponse { total: number; } +/** + * API统一响应格式 + * 注意:后端返回的字段是 msg 而不是 message,code 为 0 表示成功 + */ +interface ApiResponse { + code: number; // 0 表示成功 + msg: string; // 消息(不是 message) + data: T; +} + +/** + * 列表数据响应格式 + */ +interface ListResponse { + total: number; + page: number; + page_size: number; + items: T[]; +} + /** * 获取入口模块列表 * @param searchParams 搜索参数 - * @param jwtToken JWT令牌 * @returns 入口模块列表和总数 + * + * 注意:不需要传递 JWT,axios-client 会自动从 localStorage 读取并添加 */ export async function getEntryModules( - searchParams: EntryModuleSearchParams = {}, - jwtToken?: string | null + searchParams: EntryModuleSearchParams = {} ): Promise<{ data?: EntryModulesResponse; error?: string }> { try { - const { name, area, page = 1, pageSize = 10 } = searchParams; + const { name, area, page = 1, page_size = 10 } = searchParams; - // 构建过滤条件 - const filter: Record = {}; + // 构建查询参数 + const queryParams: Record = { + page, + page_size + }; if (name) { - filter.name = `ilike.*${name}*`; + queryParams.name = name; } - // 如果有地区筛选,使用 JSONB 查询 if (area) { - filter.areas = `cs.{"${area}"}`; // cs = contains (JSONB数组包含) + queryParams.area = area; } - // 计算分页 - const offset = (page - 1) * pageSize; + // 调用 API + const response = await get>>( + '/api/v3/entry-modules', + queryParams + ); - // 构建查询参数(一次请求获取数据和总数) - const queryParams: any = { - select: "*", - order: "created_at.desc", - limit: pageSize, - offset: offset, - headers: { - 'Prefer': 'count=exact' - }, - token: jwtToken - }; - - // 只在有过滤条件时添加 filter - if (Object.keys(filter).length > 0) { - queryParams.filter = filter; + if (response.error) { + console.error('❌ [getEntryModules] API错误:', response.error); + return { error: response.error }; } - // 获取分页数据 - const result = await postgrestGet("entry_modules", queryParams); + // 🔑 解析响应数据 + // axios-client 返回: { data: {后端JSON}, status: 200 } + // 后端返回: { code: 0, msg: "成功", data: { total, items } } + const backendResponse = response.data; - if (result.error) { - return { error: result.error }; + if (!backendResponse) { + console.error('❌ [getEntryModules] 响应为空'); + return { error: '响应为空' }; } - // 从 Content-Range 头获取总数 - let totalCount = 0; - const responseWithHeaders = result as { - data: unknown; - headers?: Record; - }; + // 检查后端返回的 code(0 表示成功) + if (backendResponse.code !== 0) { + console.error('❌ [getEntryModules] 后端返回错误:', backendResponse.msg); + return { error: backendResponse.msg || '请求失败' }; + } - if (responseWithHeaders.headers) { - const rangeHeader = responseWithHeaders.headers['content-range']; - if (rangeHeader) { - const total = rangeHeader.split('/')[1]; - if (total !== '*') { - totalCount = parseInt(total, 10); - } - } + const apiData = backendResponse.data; + if (!apiData) { + console.error('❌ [getEntryModules] data 字段为空'); + return { error: '数据为空' }; } return { data: { - modules: result.data || [], - total: totalCount || (result.data?.length || 0) + modules: apiData.items || [], + total: apiData.total || 0 } }; } catch (error) { - console.error("获取入口模块列表失败:", error); + console.error("❌ [getEntryModules] 获取入口模块列表失败:", error); return { error: error instanceof Error ? error.message : "获取入口模块列表失败" }; } } @@ -120,31 +141,39 @@ export async function getEntryModules( /** * 根据ID获取入口模块 * @param id 入口模块ID - * @param jwtToken JWT令牌 * @returns 入口模块数据 + * + * 注意:不需要传递 JWT,axios-client 会自动从 localStorage 读取并添加 */ export async function getEntryModuleById( - id: number, - jwtToken?: string | null + id: number ): Promise<{ data?: EntryModule; error?: string }> { try { - const result = await postgrestGet("entry_modules", { - filter: { id: `eq.${id}` }, - token: jwtToken - }); + // 调用 API + const response = await get>( + `/api/v3/entry-modules/${id}` + ); - if (result.error) { - return { error: result.error }; + if (response.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) { + console.error('❌ [getEntryModuleById] 入口模块不存在'); return { error: "入口模块不存在" }; } return { data: module }; } catch (error) { - console.error("获取入口模块失败:", error); + console.error("❌ [getEntryModuleById] 获取入口模块失败:", error); return { error: error instanceof Error ? error.message : "获取入口模块失败" }; } } @@ -152,28 +181,69 @@ export async function getEntryModuleById( /** * 创建入口模块 * @param module 入口模块数据 - * @param jwtToken JWT令牌 * @returns 创建的入口模块 + * + * 注意:不需要传递 JWT,axios-client 会自动从 localStorage 读取并添加 */ export async function createEntryModule( - module: Omit, - jwtToken?: string | null + module: { + name: string; + description?: string; + path?: string | null; + areas?: string[] | AreaConfig[]; // 兼容两种格式 + } ): Promise<{ data?: EntryModule; error?: string }> { try { - const result = await postgrestPost( - "entry_modules", - module as EntryModule, - jwtToken - ); - - if (result.error) { - return { error: result.error }; + // 转换 areas 格式(如果是字符串数组,转换为 AreaConfig 数组) + let areasConfig: AreaConfig[] | undefined; + if (module.areas && Array.isArray(module.areas)) { + if (typeof module.areas[0] === 'string') { + // 字符串数组转换为 AreaConfig 数组 + areasConfig = (module.areas as string[]).map((area, index) => ({ + area, + 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>( + '/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) { - console.error("创建入口模块失败:", error); + console.error("❌ [createEntryModule] 创建入口模块失败:", error); return { error: error instanceof Error ? error.message : "创建入口模块失败" }; } } @@ -182,30 +252,69 @@ export async function createEntryModule( * 更新入口模块 * @param id 入口模块ID * @param module 更新的入口模块数据 - * @param jwtToken JWT令牌 * @returns 更新的入口模块 + * + * 注意:不需要传递 JWT,axios-client 会自动从 localStorage 读取并添加 */ export async function updateEntryModule( id: number, - module: Partial>, - jwtToken?: string | null + module: { + name?: string; + description?: string; + path?: string | null; + areas?: string[] | AreaConfig[]; // 兼容两种格式 + } ): Promise<{ data?: EntryModule; error?: string }> { try { - const result = await postgrestPut>( - "entry_modules", - module, - { id: `eq.${id}` }, - jwtToken - ); - - if (result.error) { - return { error: result.error }; + // 转换 areas 格式(如果是字符串数组,转换为 AreaConfig 数组) + let areasConfig: AreaConfig[] | undefined; + if (module.areas && Array.isArray(module.areas)) { + if (typeof module.areas[0] === 'string') { + // 字符串数组转换为 AreaConfig 数组 + areasConfig = (module.areas as string[]).map((area, index) => ({ + area, + enabled: true, + 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>( + `/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) { - console.error("更新入口模块失败:", error); + console.error("❌ [updateEntryModule] 更新入口模块失败:", error); return { error: error instanceof Error ? error.message : "更新入口模块失败" }; } } @@ -213,27 +322,33 @@ export async function updateEntryModule( /** * 删除入口模块 * @param id 入口模块ID - * @param jwtToken JWT令牌 * @returns 是否成功 + * + * 注意:不需要传递 JWT,axios-client 会自动从 localStorage 读取并添加 */ export async function deleteEntryModule( - id: number, - jwtToken?: string | null + id: number ): Promise<{ success: boolean; error?: string }> { try { - const result = await postgrestDelete( - "entry_modules", - { id: `eq.${id}` }, - jwtToken + // 调用 API + const response = await del>( + `/api/v3/entry-modules/${id}` ); - if (result.error) { - return { success: false, error: result.error }; + if (response.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 }; } catch (error) { - console.error("删除入口模块失败:", error); + console.error("❌ [deleteEntryModule] 删除入口模块失败:", error); return { success: false, error: error instanceof Error ? error.message : "删除入口模块失败" diff --git a/app/api/evaluation_points/reviews.ts b/app/api/evaluation_points/reviews.ts index c95f1a2..851cf89 100644 --- a/app/api/evaluation_points/reviews.ts +++ b/app/api/evaluation_points/reviews.ts @@ -2,6 +2,7 @@ import { postgrestGet, type PostgrestParams, postgrestPut, postgrestPost } from import {getDocumentWithNoUserId} from "~/api/files/documents"; import dayjs from "dayjs"; import { getUserSession } from "~/api/login/auth.server"; +import { apiRequest } from "../axios-client"; // 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 + }; + } +} diff --git a/app/api/evaluation_points/rule-groups.ts b/app/api/evaluation_points/rule-groups.ts index 19e02f3..610e4fc 100644 --- a/app/api/evaluation_points/rule-groups.ts +++ b/app/api/evaluation_points/rule-groups.ts @@ -1204,8 +1204,6 @@ export async function getEvaluationPointGroups( ...(token ? { headers: { 'Authorization': `Bearer ${token}` } } : {}) }); - // 🔍 调试:打印完整响应 - console.log('📦 getEvaluationPointGroups 完整响应:', JSON.stringify(response, null, 2)); if (response.error) { console.error('❌ getEvaluationPointGroups 错误:', response.error); @@ -1213,8 +1211,6 @@ export async function getEvaluationPointGroups( } if (response.data) { - console.log('📊 response.data:', response.data); - console.log('📊 response.data.data:', response.data.data); if (!response.data.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); - console.log('✅ 转换后的 ruleGroups:', ruleGroups); return { data: ruleGroups, totalCount: response.data.total @@ -1415,7 +1410,6 @@ export async function createEvaluationPointGroup( if (response.data) { const ruleGroup = convertApiGroupToRuleGroup(response.data); - console.log('✅ createEvaluationPointGroup 成功:', ruleGroup); return { data: ruleGroup }; } @@ -1477,7 +1471,6 @@ export async function updateEvaluationPointGroup( if (response.data) { const ruleGroup = convertApiGroupToRuleGroup(response.data); - console.log('✅ updateEvaluationPointGroup 成功:', ruleGroup); return { data: ruleGroup }; } @@ -1520,7 +1513,6 @@ export async function deleteEvaluationPointGroup( } if (response.data) { - console.log('✅ deleteEvaluationPointGroup 成功:', response.data); return { success: response.data.success, message: response.data.message, @@ -1579,7 +1571,6 @@ export async function batchUpdateEvaluationPointGroupStatus( } if (response.data) { - console.log('✅ batchUpdateEvaluationPointGroupStatus 成功:', response.data); return { success: response.data.success, updated_count: response.data.updated_count, @@ -1635,7 +1626,6 @@ export async function batchDeleteEvaluationPointGroups( } if (response.data) { - console.log('✅ batchDeleteEvaluationPointGroups 成功:', response.data); return { success: response.data.success, deleted_groups: response.data.deleted_groups, diff --git a/app/api/role-permissions/role-permissions.ts b/app/api/role-permissions/role-permissions.ts index 893fe61..bfdf264 100644 --- a/app/api/role-permissions/role-permissions.ts +++ b/app/api/role-permissions/role-permissions.ts @@ -11,32 +11,6 @@ */ const RBAC_API_BASE = '/api/v3/rbac'; -/** - * RBAC专用API客户端 - 使用fetch直接请求Remix API路由 - */ -async function rbacFetch(url: string, options: RequestInit = {}): Promise { - 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'; } -// ==================== 模拟数据 ==================== - -/** - * 模拟路由数据(树形结构) - */ -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 查询参数 @@ -467,11 +169,9 @@ export async function getRoles(params?: { // 导入 axios-client 的 get 函数 const { get } = await import('~/api/axios-client'); - console.log('🔍 [getRoles] 开始调用后端API:', `/api/v3/rbac/roles`, params); // 使用 axios-client 的 get 函数调用真实后端API const response = await get(`/api/v3/rbac/roles`, params || {}); - console.log('📦 [getRoles] 后端API完整响应:', JSON.stringify(response, null, 2)); if (response.error) { throw new Error(response.error); @@ -485,7 +185,6 @@ export async function getRoles(params?: { items = response.data.items; } - console.log('✅ [getRoles] 解析出的角色数组:', items); // 数据格式转换(后端字段 -> 前端字段) const roles = items.map(role => ({ @@ -501,7 +200,6 @@ export async function getRoles(params?: { updated_at: role.updated_at })); - console.log('✅ [getRoles] 最终返回的角色列表,共', roles.length, '个角色'); return roles; } catch (error) { console.error('❌ [getRoles] 获取角色列表失败:', error); @@ -545,7 +243,6 @@ export async function getRoutes(): Promise { try { const { get } = await import('~/api/axios-client'); - console.log('🔍 [getRoutes] 开始调用后端API: /rbac/user/routes'); // 调用后端API获取当前用户的路由(provincial_admin应该有所有路由权限) const response = await get('/rbac/user/routes'); @@ -564,8 +261,6 @@ export async function getRoutes(): Promise { routes = response.data.routes; } - console.log('✅ [getRoutes] 成功获取路由数据,共', routes.length, '个顶级路由'); - // 将后端数据转换为前端RouteInfo格式 const mapRouteData = (route: any): RouteInfo => ({ id: route.id, @@ -599,11 +294,8 @@ export async function getRoleRoutePermissions(roleId: number): Promise(`/rbac/roles/${roleId}/routes`); - console.log('📦 [getRoleRoutePermissions] 后端API完整响应:', JSON.stringify(response, null, 2)); if (response.error) { throw new Error(response.error); @@ -624,7 +316,6 @@ export async function getRoleRoutePermissions(roleId: number): Promise(`/rbac/roles/${roleId}/routes`); - console.log('📦 [getRoleRoutesWithPermissions] 后端API完整响应:', JSON.stringify(response, null, 2)); if (response.error) { throw new Error(response.error); @@ -718,11 +406,6 @@ export async function getRoleRoutesWithPermissions(roleId: number): Promise<{ const selectedRouteIds = collectRouteIds(mappedRoutes); const selectedPermissionIds = collectPermissionIds(mappedRoutes); - console.log('✅ [getRoleRoutesWithPermissions] 成功获取路由权限数据'); - console.log(' - 路由数量:', mappedRoutes.length); - console.log(' - 已选路由ID:', selectedRouteIds); - console.log(' - 已选权限ID:', selectedPermissionIds); - return { routes: mappedRoutes, selectedRouteIds, @@ -750,8 +433,6 @@ export async function saveRoleApiPermissions( try { const { post } = await import('~/api/axios-client'); - console.log('🔍 [saveRoleApiPermissions] 开始调用后端API:', `/api/v3/rbac/roles/${roleId}/permissions`, permissionIds); - // 构建权限配置 const permissions = permissionIds.map(id => ({ permission_id: id, @@ -764,8 +445,6 @@ export async function saveRoleApiPermissions( replace: true // 替换模式:先删除现有权限,再插入新权限 }); - console.log('📦 [saveRoleApiPermissions] 后端API完整响应:', JSON.stringify(response, null, 2)); - if (response.error) { throw new Error(response.error); } @@ -778,7 +457,6 @@ export async function saveRoleApiPermissions( message = `成功分配 ${assigned_count} 个API权限`; } - console.log('✅ [saveRoleApiPermissions] API权限保存成功'); return { success: true, message }; } catch (error) { console.error('❌ [saveRoleApiPermissions] 保存API权限失败:', error); @@ -802,14 +480,12 @@ export async function updateRoleRoutePermissions( // 导入 axios-client 的 put 函数 const { put } = await import('~/api/axios-client'); - console.log('🔍 [updateRoleRoutePermissions] 开始调用后端API:', `/rbac/roles/${roleId}/routes`, routeIds); // 使用 axios-client 的 put 函数调用真实后端API const response = await put(`/rbac/roles/${roleId}/routes`, { route_ids: routeIds, permission: 'RW' }); - console.log('📦 [updateRoleRoutePermissions] 后端API完整响应:', JSON.stringify(response, null, 2)); if (response.error) { throw new Error(response.error); @@ -824,7 +500,6 @@ export async function updateRoleRoutePermissions( message = `成功分配 ${assigned_count} 个路由,移除了 ${removed_count} 个旧路由`; } - console.log('✅ [updateRoleRoutePermissions] 角色权限更新成功'); return { success: true, message }; } catch (error) { console.error('❌ [updateRoleRoutePermissions] 更新角色权限失败:', error); @@ -855,8 +530,6 @@ export async function getRoleUsers( // 导入 axios-client 的 get 函数 const { get } = await import('~/api/axios-client'); - console.log('🔍 [getRoleUsers] 开始调用后端API:', `/api/v3/rbac/roles/${roleId}/users`, params); - // 构建查询参数对象 const queryParams: Record = {}; if (params?.page) queryParams.page = params.page; @@ -866,7 +539,6 @@ export async function getRoleUsers( // 使用 axios-client 的 get 函数调用真实后端API const response = await get(`/api/v3/rbac/roles/${roleId}/users`, queryParams); - console.log('📦 [getRoleUsers] 后端API完整响应:', JSON.stringify(response, null, 2)); if (response.error) { throw new Error(response.error); @@ -880,8 +552,6 @@ export async function getRoleUsers( items = response.data.items; } - console.log('✅ [getRoleUsers] 解析出的用户数组:', items); - const users = items.map((user: any) => ({ id: user.user_id || user.id, username: user.username, @@ -893,7 +563,6 @@ export async function getRoleUsers( is_leader: user.is_leader || false })); - console.log('✅ [getRoleUsers] 最终返回的用户列表:', users); return users; } catch (error) { console.error('❌ [getRoleUsers] 获取角色用户列表失败:', error); @@ -915,8 +584,6 @@ export async function getAllUsers(params?: { // 导入 axios-client 的 get 函数 const { get } = await import('~/api/axios-client'); - console.log('🔍 [getAllUsers] 开始调用后端API:', '/admin/users/users', params); - // 构建查询参数对象 const queryParams: Record = {}; if (params?.page) queryParams.page = params.page; @@ -925,7 +592,6 @@ export async function getAllUsers(params?: { // 使用 axios-client 的 get 函数,会自动添加 baseURL 和 Authorization const response = await get('/admin/users/users', queryParams); - console.log('📦 [getAllUsers] 后端API完整响应:', JSON.stringify(response, null, 2)); if (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 => ({ id: user.user_id || user.id, // 优先使用 user_id,兼容不同的后端响应格式 @@ -960,7 +624,6 @@ export async function getAllUsers(params?: { is_leader: user.is_leader || false })); - console.log('✅ [getAllUsers] 最终返回的用户列表:', userList); return userList; } catch (error) { console.error('❌ [getAllUsers] 获取用户列表失败:', error); @@ -981,13 +644,10 @@ export async function assignUserRoles( // 导入 axios-client 的 post 函数 const { post } = await import('~/api/axios-client'); - console.log('🔍 [assignUserRoles] 开始调用后端API:', `/api/v3/rbac/users/${userId}/roles`, roleIds); - // 使用 axios-client 的 post 函数调用真实后端API const response = await post(`/api/v3/rbac/users/${userId}/roles`, { role_ids: roleIds }); - console.log('📦 [assignUserRoles] 后端API完整响应:', JSON.stringify(response, null, 2)); if (response.error) { throw new Error(response.error); @@ -998,8 +658,6 @@ export async function assignUserRoles( if (response.data && response.data.message) { message = response.data.message; } - - console.log('✅ [assignUserRoles] 角色分配成功'); return { success: true, message }; } catch (error) { console.error('❌ [assignUserRoles] 分配用户角色失败:', error); @@ -1023,11 +681,8 @@ export async function revokeUserRole( // 导入 axios-client 的 del 函数 const { del } = await import('~/api/axios-client'); - console.log('🔍 [revokeUserRole] 开始调用后端API:', `/api/v3/rbac/users/${userId}/roles/${roleId}`); - // 使用 axios-client 的 del 函数调用真实后端API const response = await del(`/api/v3/rbac/users/${userId}/roles/${roleId}`); - console.log('📦 [revokeUserRole] 后端API完整响应:', JSON.stringify(response, null, 2)); if (response.error) { throw new Error(response.error); @@ -1039,7 +694,6 @@ export async function revokeUserRole( message = response.data.message; } - console.log('✅ [revokeUserRole] 角色移除成功'); return { success: true, message }; } catch (error) { console.error('❌ [revokeUserRole] 移除用户角色失败:', error); @@ -1061,8 +715,6 @@ export async function createRole( // 导入 axios-client 的 post 函数 const { post } = await import('~/api/axios-client'); - console.log('🔍 [createRole] 开始调用后端API:', `/api/v3/rbac/roles`, roleData); - // 使用 axios-client 的 post 函数调用真实后端API const response = await post(`/api/v3/rbac/roles`, { role_key: roleData.role_key, @@ -1072,8 +724,6 @@ export async function createRole( metadata: {} }); - console.log('📦 [createRole] 后端API完整响应:', JSON.stringify(response, null, 2)); - if (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.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(`/api/v3/rbac/roles/${roleId}`, updatePayload); - console.log('📦 [updateRole] 后端API完整响应:', JSON.stringify(response, null, 2)); - if (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' : ''}`; - console.log('🔍 [deleteRole] 开始调用后端API:', url); - const response = await del(url); - console.log('📦 [deleteRole] 后端API完整响应:', JSON.stringify(response, null, 2)); - if (response.error) { throw new Error(response.error); } @@ -1321,10 +963,7 @@ export async function getRolePermissions(roleId: number): Promise(`/api/v3/rbac/roles/${roleId}/permissions`); - console.log('📦 [getRolePermissions] 后端API完整响应:', JSON.stringify(response, null, 2)); if (response.error) { throw new Error(response.error); @@ -1342,8 +981,6 @@ export async function getRolePermissions(roleId: number): Promise ({ id: perm.id, permission_id: perm.permission_id || perm.id, // 兼容:如果没有 permission_id,使用 id @@ -1451,8 +1088,6 @@ export async function getUserRoles(userId: number): Promise { try { const { get } = await import('~/api/axios-client'); - console.log('🔍 [getUserRoles] 开始调用后端API:', `/api/v3/rbac/users/${userId}/roles`); - const response = await get(`/api/v3/rbac/users/${userId}/roles`); if (response.error) { @@ -1469,8 +1104,6 @@ export async function getUserRoles(userId: number): Promise { roles = response.data.roles; } - console.log('✅ [getUserRoles] 成功获取用户角色,共', roles.length, '个角色'); - // 将后端数据转换为RoleInfo格式 return roles.map(role => ({ id: role.id || role.role_id, diff --git a/app/components/reviews/FilePreview.tsx b/app/components/reviews/FilePreview.tsx index 222ed12..91b5ee2 100644 --- a/app/components/reviews/FilePreview.tsx +++ b/app/components/reviews/FilePreview.tsx @@ -21,6 +21,11 @@ interface FileContent { page_offset?: number; }; }; // 添加ocrResult属性 + ocr_result?:{ + __meta?: { + page_offset?: number; + }; + }, parties: { partyA: { name: string; @@ -193,7 +198,8 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage // 如果是PDF文件,直接使用PdfPreview组件 if (isPdf && real_path) { // 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 ( (); + const loaderData = useLoaderData(); + const { modules, total, error } = loaderData; // 获取用户角色并判断权限 const rootData = useRouteLoaderData("root") as { userRole: string }; @@ -218,30 +185,23 @@ export default function EntryModulesList() { type: "warning", confirmText: "删除", cancelText: "取消", - confirmDelay: 4, + confirmDelay: 3, onConfirm: async () => { setIsDeleting(true); try { - const formData = new FormData(); - formData.append('id', id.toString()); - formData.append('intent', 'delete'); - - const response = await fetch('/entry-modules', { - method: 'POST', - body: formData - }); - - const result = await response.json(); + // 直接调用 API 删除函数 + const result = await deleteEntryModule(id); if (result.success) { toastService.success('删除成功!'); - // 刷新页面 - window.location.reload(); + // 重新验证数据,刷新表格 + revalidator.revalidate(); } else { toastService.error(`删除失败: ${result.error || '未知错误'}`); } } catch (error) { + console.error('删除入口模块失败:', error); toastService.error(`删除失败: ${error instanceof Error ? error.message : '未知错误'}`); } finally { setIsDeleting(false); @@ -272,42 +232,36 @@ export default function EntryModulesList() { // 表格列定义 const columns = [ - { - key: 'id', - title: 'ID', - width: '80px', - render: (row: EntryModule) => row.id - }, { key: 'name', title: '模块名称', width: '200px', - render: (row: EntryModule) => ( - {row.name} + render: (_: any, record: EntryModule) => ( + {record.name} ) }, { key: 'description', title: '描述', width: '250px', - render: (row: EntryModule) => ( - {row.description || '-'} + render: (_: any, record: EntryModule) => ( + {record.description || '-'} ) }, { key: 'logo', title: 'Logo图片', width: '150px', - render: (row: EntryModule) => { - if (!row.path) { + render: (_: any, record: EntryModule) => { + if (!record.path) { return 未上传; } - const logoUrl = `${DOCUMENT_URL}${row.path}`; + const logoUrl = `${DOCUMENT_URL}${record.path}`; return (
{row.name} { (e.target as HTMLImageElement).style.display = 'none'; @@ -330,17 +284,20 @@ export default function EntryModulesList() { key: 'areas', title: '适用地区', width: '200px', - render: (row: EntryModule) => ( + render: (_: any, record: EntryModule) => (
- {row.areas && row.areas.length > 0 ? ( - row.areas.map((area, index) => ( - - {area} - - )) + {record.areas && record.areas.length > 0 ? ( + record.areas + .filter(areaConfig => areaConfig.enabled !== false) // 只显示启用的地区 + .sort((a, b) => a.sort_order - b.sort_order) // 按排序号排序 + .map((areaConfig, index) => ( + + {areaConfig.area} + + )) ) : ( 未设置 )} @@ -351,34 +308,34 @@ export default function EntryModulesList() { key: 'created_at', title: '创建时间', width: '180px', - render: (row: EntryModule) => - row.created_at ? new Date(row.created_at).toLocaleString('zh-CN') : '-' + render: (_: any, record: EntryModule) => + record.created_at ? new Date(record.created_at).toLocaleString('zh-CN') : '-' }, { key: 'actions', title: '操作', - width: '150px', - render: (row: EntryModule) => ( -
- - + {hasEditPermission ? '编辑' : '查看'} + + {hasEditPermission && ( + + )}
) } @@ -406,11 +363,6 @@ export default function EntryModulesList() { {/* 筛选面板 */} - + {/* 表格 */} diff --git a/app/routes/entry-modules.new.tsx b/app/routes/entry-modules.new.tsx index 4b11b79..2716185 100644 --- a/app/routes/entry-modules.new.tsx +++ b/app/routes/entry-modules.new.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useRef } from "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 { Button } from "~/components/ui/Button"; import { toastService } from "~/components/ui/Toast"; @@ -9,9 +9,18 @@ import { getEntryModuleById, createEntryModule, updateEntryModule, - type EntryModule + type EntryModule, + type AreaConfig } from "~/api/entry-modules/entry-modules"; 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 = () => { @@ -33,38 +42,31 @@ export const handle = { interface LoaderData { module?: EntryModule; error?: string; - frontendJWT?: string | null; } -// 加载函数 - 获取入口模块数据(编辑模式) -export async function loader({ request }: LoaderFunctionArgs) { +// 🔑 客户端加载函数 - 在浏览器端执行,axios-client 会自动添加 JWT +export async function clientLoader({ request }: ClientLoaderFunctionArgs) { try { - const { getUserSession } = await import("~/api/login/auth.server"); - const { frontendJWT } = await getUserSession(request); - const url = new URL(request.url); const id = url.searchParams.get('id'); if (id) { - const moduleResponse = await getEntryModuleById(parseInt(id), frontendJWT); + // ✅ 不需要传递 JWT,axios-client 会自动处理 + const moduleResponse = await getEntryModuleById(parseInt(id)); if (moduleResponse.error) { throw new Error(moduleResponse.error); } - return Response.json({ - module: moduleResponse.data, - frontendJWT - }); + return { + module: moduleResponse.data + }; } - return Response.json({ frontendJWT }); + return {}; } catch (error) { console.error("加载入口模块失败:", error); - return Response.json( - { - error: error || "加载入口模块失败", - status: 500 - } - ); + return { + error: error instanceof Error ? error.message : "加载入口模块失败" + }; } } @@ -81,7 +83,7 @@ const AREA_OPTIONS = [ export default function EntryModuleNew() { const navigate = useNavigate(); const [searchParams] = useSearchParams(); - const { module, error, frontendJWT } = useLoaderData(); + const { module, error } = useLoaderData(); const id = searchParams.get('id'); const isEditMode = !!id; @@ -89,7 +91,10 @@ export default function EntryModuleNew() { // 表单状态 const [name, setName] = useState(module?.name || ''); const [description, setDescription] = useState(module?.description || ''); - const [selectedAreas, setSelectedAreas] = useState(module?.areas || []); + // 🔑 从 AreaConfig[] 提取地区名称数组 + const [selectedAreas, setSelectedAreas] = useState( + module?.areas ? module.areas.map(a => a.area) : [] + ); const [logoFile, setLogoFile] = useState(null); const [logoPreview, setLogoPreview] = useState( module?.path ? `${DOCUMENT_URL}${module.path}` : null @@ -168,10 +173,14 @@ export default function EntryModuleNew() { formData.append('file', logoFile); formData.append('folder', 'entryModule'); + // ✅ 不需要手动添加 Authorization 头 + // fetch 可以自动使用浏览器的认证信息,或者我们也可以使用 axios + const token = localStorage.getItem('access_token'); + const response = await fetch(`${API_BASE_URL}/admin/upload`, { method: 'POST', headers: { - 'Authorization': `Bearer ${frontendJWT}` + 'Authorization': `Bearer ${token}` }, body: formData }); @@ -210,18 +219,21 @@ export default function EntryModuleNew() { logoPath = await uploadLogo(); } + // 🔑 准备提交数据 + // areas 字段会在 API 层自动转换为 AreaConfig[] 格式 const moduleData = { name: name.trim(), description: description.trim() || undefined, path: logoPath, - areas: selectedAreas + areas: selectedAreas // 字符串数组,API会自动转换 }; + // ✅ 不需要传递 JWT,axios-client 会自动处理 let result; if (isEditMode) { - result = await updateEntryModule(parseInt(id!), moduleData, frontendJWT); + result = await updateEntryModule(parseInt(id!), moduleData); } else { - result = await createEntryModule(moduleData, frontendJWT); + result = await createEntryModule(moduleData); } if (result.error) { @@ -280,7 +292,7 @@ export default function EntryModuleNew() { onChange={(e) => setName(e.target.value)} placeholder="请输入模块名称,如:合同管理" 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" /> @@ -291,7 +303,7 @@ export default function EntryModuleNew() { value={description} onChange={(e) => setDescription(e.target.value)} 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} /> @@ -349,7 +361,7 @@ export default function EntryModuleNew() { type="checkbox" checked={selectedAreas.includes(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" /> {option.label} diff --git a/app/routes/reviews.tsx b/app/routes/reviews.tsx index 4802f02..86d213a 100644 --- a/app/routes/reviews.tsx +++ b/app/routes/reviews.tsx @@ -29,7 +29,7 @@ import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs } f import { useState, useEffect } from "react"; import { useNavigate, useLoaderData, useFetcher } from "@remix-run/react"; 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"; // 导入评查详情页面组件 @@ -189,8 +189,11 @@ export async function loader({ request }: LoaderFunctionArgs) { const { getUserSession } = await import("~/api/login/auth.server"); const { userInfo, frontendJWT } = await getUserSession(request); - // 获取评查点数据,传递request对象 - const reviewData = await getReviewPoints(id, request); + // 🆕 使用新的后端API获取评查点数据(单次请求替代原7次请求) + const reviewData = await getReviewPoints_fromApi(id, request); + + // ⚠️ 原方法已注释(保留以备回退) + // const reviewData = await getReviewPoints(id, request); if ('error' in reviewData && reviewData.error) { console.error("[Reviews Loader] 获取评查点数据错误:", reviewData.error); @@ -309,6 +312,23 @@ export default function ReviewDetails() { message: string; } | 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 数据加载出错 useEffect(()=>{ loadingBarService.hide(); @@ -340,17 +360,17 @@ export default function ReviewDetails() { const fileInfo = { fileName: document.name || "未知文件名", path: document.path || "未知路径", - contractNumber: document.documentNumber || "未知编号", - fileSize: document.size ? formatFileSize(document.size) : "未知大小", + contractNumber: document.documentNumber || document.document_number || "未知编号", + fileSize: document.size ? formatFileSize(document.size) : document.file_size ? formatFileSize(document.file_size) : "未知大小", // 文件格式类型 fileFormat: document.fileType ? document.fileType.toUpperCase() : "未知格式", - pageCount: document.pageCount || 0, - uploadTime: document.uploadTime || "未知时间", + pageCount: document.pageCount || document.page_count || 0, + uploadTime: document.uploadTime || document.created_at || "未知时间", uploadUser: document.uploadUser || "未知用户", auditStatus: document.auditStatus || 0, legalBasis: document.legalBasis || {}, // 文件类型(1:合同,2:卷宗。。。) - fileType: document.type || "" + fileType: document.type || document.type_id ? document.type_id.toString() : '' }; // 创建包含真实文档数据的评查数据对象 @@ -725,13 +745,13 @@ export default function ReviewDetails() { {/* 左侧:文件预览 */}
{(() => { - console.log('[Reviews] 准备渲染FilePreview', { - hasDocument: !!document, - documentPath: document?.path, - targetPage, - hasCharPositions: !!charPositions, - charPositionsLength: charPositions?.length - }); + // console.log('[Reviews] 准备渲染FilePreview', { + // hasDocument: !!document, + // documentPath: document?.path, + // targetPage, + // hasCharPositions: !!charPositions, + // charPositionsLength: charPositions?.length + // }); return ( p.permission_id); - console.log('🔍 [handleSelectRole] 角色权限数据:'); - console.log(' - routePermissionsMap:', permMap); - console.log(' - rolePermissions:', rolePermissions); - console.log(' - assignedPermissionIds:', assignedPermissionIds); - setRoutePermissionsMap(permMap); setSelectedRouteIds(routeIds); setSelectedPermissionIds(assignedPermissionIds); // 使用实际已分配的权限ID diff --git a/app/styles/pages/entry-modules.css b/app/styles/pages/entry-modules.css index fd49db6..9d98f45 100644 --- a/app/styles/pages/entry-modules.css +++ b/app/styles/pages/entry-modules.css @@ -10,6 +10,30 @@ 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 { display: flex; @@ -40,6 +64,50 @@ 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-preview { display: inline-block;