feat: 1. 完善全局路由的访问权限的验证。 2. 完善接口返回的树形路由结构 3.优化评查点列表的查询,改用表连接的方式,废弃使用数据库的rpc函数,同时进行地区隔离和权限隔离。

4. 删除冗余的评查文件列表。      5.完善上传文档 页面初始化查询数据的时候 查询文件类型(改成动态指定)  6. 添加获取入口模块的查询接口。    7.完善服务端中判断token的有效性,失效则跳转到登录页。
8. 重构layout和sidebar的页面,改成由动态权限路由来渲染对应的菜单栏。       9.重构入口页面,通过动态查询根据不同地区的人返回不同的入口。
This commit is contained in:
2025-11-20 01:35:30 +08:00
parent adfb84a31d
commit 2edde8a8ab
23 changed files with 1201 additions and 2154 deletions
+110 -26
View File
@@ -26,7 +26,7 @@
*/
import { type MetaFunction } from "@remix-run/node";
import { useState, useEffect, useCallback } from "react";
import { useState, useEffect, useCallback, useRef } from "react";
import { BasicInfo } from "~/components/rules/new/BasicInfo";
import { ExtractionSettings } from "~/components/rules/new/ExtractionSettings";
import { ReviewSettings } from "~/components/rules/new/ReviewSettings";
@@ -138,13 +138,17 @@ export default function RuleNew() {
const navigate = useNavigate();
const location = useLocation();
const [isEditMode, setIsEditMode] = useState(false);
const [isCopyMode, setIsCopyMode] = useState(false); // 添加复制模式状态
const [isLoading, setIsLoading] = useState(false);
const [instanceKey, setInstanceKey] = useState<string>('new');
// 从root路由获取用户角色和JWT token
const rootData = useRouteLoaderData("root") as { userRole: UserRole; frontendJWT?: string };
const userRole = rootData?.userRole || 'common';
const frontendJWT = rootData?.frontendJWT;
// 使用 ref 跟踪当前加载的 URL,避免重复加载
const loadedUrlRef = useRef<string>('');
const [formData, setFormData] = useState<EvaluationPoint>({});
const [evaluationPointGroups, setEvaluationPointGroups] = useState<EvaluationPointGroup[]>([]);
@@ -266,11 +270,12 @@ export default function RuleNew() {
* 获取评查点数据
* 编辑模式下从API获取指定ID的评查点数据
* @param id 评查点ID
* @param isCopy 是否为复制模式
*/
const fetchEvaluationPoint = useCallback(async (id: number) => {
const fetchEvaluationPoint = useCallback(async (id: number, isCopy = false) => {
try {
setIsLoading(true);
// console.log(`获取评查点数据,ID: ${id}`);
// console.log(`获取评查点数据,ID: ${id}, 复制模式: ${isCopy}`);
// 使用 postgrestGet 替代直接调用 fetch
const postgrestParams = {
filter: {
@@ -283,23 +288,43 @@ export default function RuleNew() {
if (response.data) {
// 使用extractApiData从响应中提取数据
const evaluationPoints = extractApiData<EvaluationPoint[]>(response.data);
if (evaluationPoints && Array.isArray(evaluationPoints) && evaluationPoints.length > 0) {
try {
// 使用JSON序列化和反序列化来进行深拷贝,避免浏览器差异
const originalData = evaluationPoints[0];
const jsonString = JSON.stringify(originalData);
const data = JSON.parse(jsonString);
// 🔄 复制模式:删除不应该复制的字段
if (isCopy) {
delete data.id;
delete data.created_at;
delete data.updated_at;
delete data.usage_count;
// console.log('📋 复制模式:已清除不应复制的字段(id, created_at, updated_at, usage_count');
}
// 🔑 清洗评查点编码:移除最后一个 '--' 及其后面的字符
// 例如:'code-mis--mz' --> 'code-mis', 'code-mbs--alsi--gz' --> 'code-mbs--alsi'
if (data.code) {
const lastDoubleHyphenIndex = data.code.lastIndexOf('--');
if (lastDoubleHyphenIndex !== -1) {
data.code = data.code.substring(0, lastDoubleHyphenIndex);
// console.log('🔑 已清洗评查点编码:', data.code);
}
}
// 设置表单数据
setFormData(data);
// 初始化extractionFields
const extractedFields = extractFieldsFromFormData(data);
setExtractionFields(extractedFields);
// 设置编辑模式的实例键
setInstanceKey(`edit_${id}_${Date.now()}`);
// 设置实例键
setInstanceKey(isCopy ? `copy_${id}_${Date.now()}` : `edit_${id}_${Date.now()}`);
} catch (jsonError) {
console.error('JSON处理错误:', jsonError);
toastService.error(`数据处理错误: ${jsonError instanceof Error ? jsonError.message : '未知错误'}`);
@@ -332,14 +357,30 @@ export default function RuleNew() {
*/
const fetchEvaluationPointGroups = useCallback(async () => {
try {
// console.log("获取评查点组数据");
// console.log("🔍 [fetchEvaluationPointGroups] 开始获取评查点组数据");
const response = await postgrestGet('evaluation_point_groups', { token: frontendJWT });
if (response.data && Array.isArray(response.data) && response.data.length > 0) {
setEvaluationPointGroups(response.data);
// console.log("🔍 [fetchEvaluationPointGroups] API响应:", response);
if (response.data) {
// 使用 extractApiData 提取数据(处理可能的包装格式)
const extractedData = extractApiData<EvaluationPointGroup[]>(response.data);
// console.log("🔍 [fetchEvaluationPointGroups] 提取后的数据:", extractedData);
if (extractedData && Array.isArray(extractedData) && extractedData.length > 0) {
setEvaluationPointGroups(extractedData);
// console.log(`✅ [fetchEvaluationPointGroups] 成功加载 ${extractedData.length} 个评查点组`);
} else {
console.warn("⚠️ [fetchEvaluationPointGroups] 提取的数据为空或格式不正确");
setEvaluationPointGroups([]);
}
} else if (response.error) {
console.error('❌ [fetchEvaluationPointGroups] API返回错误:', response.error);
setEvaluationPointGroups([]);
}
} catch (error) {
console.error('获取评查点组数据失败:', error);
console.error('❌ [fetchEvaluationPointGroups] 获取评查点组数据失败:', error);
setEvaluationPointGroups([]);
// 显示错误提示但不影响应用继续使用
toastService.error(`获取评查点组数据失败: ${error instanceof Error ? error.message : '未知错误'}\n将使用默认数据`);
}
@@ -761,6 +802,7 @@ export default function RuleNew() {
let response;
if (isEditMode) {
response = await postgrestPut('evaluation_points', finalData, {id: formData.id!}, frontendJWT);
// console.log("最终提交的数据", finalData)
} else {
response = await postgrestPost('evaluation_points', finalData, frontendJWT);
}
@@ -909,46 +951,85 @@ export default function RuleNew() {
* 3. 获取评查点组数据(用于表单选择项)
*/
useEffect(() => {
const searchParams = new URLSearchParams(location.search);
const id = searchParams.get('id');
const currentUrl = location.search;
const currentPathname = location.pathname;
const fullUrl = `${currentPathname}${currentUrl}`;
// 🔑 如果 URL 没有变化,不重复加载(避免无限循环)
// 使用完整路径(pathname + search)进行比较,避免新增页面被拦截
if (loadedUrlRef.current === fullUrl) {
console.log('🔄 [useEffect] URL未变化,跳过重复加载:', fullUrl);
return;
}
// console.log('🔄 [useEffect] URL变化,开始加载数据:', { previous: loadedUrlRef.current, current: fullUrl });
const searchParams = new URLSearchParams(currentUrl);
const id = searchParams.get('id');
const mode = searchParams.get('mode');
// 判断是否为复制模式
const isCopy = mode === 'copy';
// 编辑或复制模式下设置加载状态
if (id || mode === 'copy') {
if (id || isCopy) {
setIsLoading(true);
}
// 设置编辑模式
if (mode && mode === 'copy') {
// 设置复制模式状态
setIsCopyMode(isCopy);
// 设置编辑模式(复制模式不是编辑模式)
if (isCopy) {
setIsEditMode(false);
} else {
setIsEditMode(!!id);
}
if (id) {
// 编辑模式:获取数据
fetchEvaluationPoint(parseInt(id));
// 编辑或复制模式:获取数据(传入复制模式标志)
console.log('📝 [useEffect] 编辑/复制模式,加载评查点数据,ID:', id);
fetchEvaluationPoint(parseInt(id), isCopy);
} else {
// 新建模式:重置表单数据
console.log('📝 [useEffect] 新建模式,重置表单数据');
resetFormData();
}
// 获取评查点组数据
// console.log('📝 [useEffect] 获取评查点组数据');
fetchEvaluationPointGroups();
// 获取VLM字段类型选项
// console.log('📝 [useEffect] 获取VLM字段类型选项');
fetchVlmFieldTypeOptions();
}, [location.search, fetchEvaluationPoint, fetchEvaluationPointGroups, fetchVlmFieldTypeOptions, resetFormData]);
// 记录已加载的 URL(使用完整路径)
loadedUrlRef.current = fullUrl;
}, [location.search, location.pathname, fetchEvaluationPoint, fetchEvaluationPointGroups, fetchVlmFieldTypeOptions, resetFormData]);
// 渲染页面内容
return (
<div className="container">
{/* 页面标题和右上角保存按钮 */}
<PageHeader
title={isEditMode ? (isReadOnly ? "查看评查点" : "编辑评查点") : "新增评查点"}
title={isCopyMode ? "复制评查点" : (isEditMode ? (isReadOnly ? "查看评查点" : "编辑评查点") : "新增评查点")}
onSave={handleSave}
showSaveButton={!isReadOnly}
/>
{/* 复制模式提示 */}
{isCopyMode && !isLoading && (
<div className="mb-4 p-1 bg-blue-50 border border-blue-200 rounded-md">
<div className="flex items-center">
<i className="ri-information-line text-blue-500 text-xl mr-2"></i>
<div className="text-sm text-blue-800">
<span className="font-medium"></span>
</div>
</div>
</div>
)}
{/* 加载状态显示 */}
{isLoading ? (
<div className="flex justify-center items-center p-12">
@@ -965,6 +1046,7 @@ export default function RuleNew() {
{/* 评查点基本信息设置 */}
<div className="mb-8">
<BasicInfo
key={instanceKey}
onChange={handleBasicInfoChange}
initialData={formData}
evaluationPointGroups={evaluationPointGroups}
@@ -975,6 +1057,7 @@ export default function RuleNew() {
{/* 抽取设置 - 配置从文档中提取的字段 */}
<div className="mb-8">
<ExtractionSettings
key={instanceKey}
onChange={handleExtractionSettingsChange}
initialData={formData}
promptTypeOptions={EVALUATION_OPTIONS.llmPromptTypeOptions}
@@ -985,6 +1068,7 @@ export default function RuleNew() {
{/* 评查设置 - 配置评查规则、消息等 */}
<div className="mb-8">
<ReviewSettings
key={instanceKey}
onChange={handleReviewSettingsChange}
initialData={{
rules: formData.evaluation_config?.rules || [],