feat: 1. 完善全局路由的访问权限的验证。 2. 完善接口返回的树形路由结构 3.优化评查点列表的查询,改用表连接的方式,废弃使用数据库的rpc函数,同时进行地区隔离和权限隔离。
4. 删除冗余的评查文件列表。 5.完善上传文档 页面初始化查询数据的时候 查询文件类型(改成动态指定) 6. 添加获取入口模块的查询接口。 7.完善服务端中判断token的有效性,失效则跳转到登录页。 8. 重构layout和sidebar的页面,改成由动态权限路由来渲染对应的菜单栏。 9.重构入口页面,通过动态查询根据不同地区的人返回不同的入口。
This commit is contained in:
+113
-79
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { type MetaFunction, type LoaderFunctionArgs } from "@remix-run/node";
|
||||
import { useLoaderData, useSearchParams, Link, useNavigate, useFetcher, useRouteLoaderData, useLocation } from "@remix-run/react";
|
||||
import { Button } from '~/components/ui/Button';
|
||||
@@ -38,6 +38,10 @@ export const meta: MetaFunction = () => {
|
||||
];
|
||||
};
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: "评查点列表"
|
||||
};
|
||||
|
||||
// 声明loader返回的数据类型
|
||||
export type LoaderData = {
|
||||
rules: Rule[];
|
||||
@@ -70,9 +74,17 @@ interface ActionResponse {
|
||||
}
|
||||
|
||||
function mapApiRuleToModel(apiRule: ApiRule): Rule {
|
||||
// 🔑 清洗评查点编码:移除最后一个 '--' 及其后面的字符
|
||||
// 例如:'code-mis--mz' --> 'code-mis', 'code-mbs--alsi--gz' --> 'code-mbs--alsi'
|
||||
let cleanedCode = apiRule.code;
|
||||
const lastDoubleHyphenIndex = cleanedCode.lastIndexOf('--');
|
||||
if (lastDoubleHyphenIndex !== -1) {
|
||||
cleanedCode = cleanedCode.substring(0, lastDoubleHyphenIndex);
|
||||
}
|
||||
|
||||
return {
|
||||
id: apiRule.id,
|
||||
code: apiRule.code,
|
||||
code: cleanedCode,
|
||||
name: apiRule.name,
|
||||
ruleType: apiRule.ruleType as RuleType, // 类型转换
|
||||
ruleGroupId: apiRule.groupId,
|
||||
@@ -105,22 +117,13 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const { getUserSession } = await import("~/api/login/auth.server");
|
||||
const { frontendJWT } = await getUserSession(request);
|
||||
|
||||
// 获取评查点类型列表,供前端筛选使用
|
||||
const typeResponse = await getRuleTypes(undefined, frontendJWT);
|
||||
|
||||
if (typeResponse.error) {
|
||||
console.error('获取评查点类型失败:', typeResponse.error);
|
||||
}
|
||||
|
||||
const ruleTypes = typeResponse.error ? [] : typeResponse.data;
|
||||
|
||||
// 返回初始空数据,客户端将根据 sessionStorage 中的 reviewType 加载实际数据
|
||||
// 返回初始空数据,客户端将根据 sessionStorage 中的 documentTypeIds 加载实际数据
|
||||
return Response.json({
|
||||
rules: [],
|
||||
totalCount: 0,
|
||||
currentPage: params.page,
|
||||
pageSize: params.pageSize,
|
||||
ruleTypes,
|
||||
ruleTypes: [], // 服务端无法访问 sessionStorage,客户端加载
|
||||
initialLoad: true,
|
||||
frontendJWT
|
||||
}, {
|
||||
@@ -198,16 +201,16 @@ export default function RulesIndex() {
|
||||
// 状态管理
|
||||
const [ruleGroups, setRuleGroups] = useState<RuleGroup[]>([]);
|
||||
const [loadingGroups, setLoadingGroups] = useState(false);
|
||||
const [reviewType, setReviewType] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [filteredRules, setFilteredRules] = useState<Rule[]>(initialRules);
|
||||
const [filteredTotalCount, setFilteredTotalCount] = useState<number>(initialTotalCount);
|
||||
const [ruleTypes, setRuleTypes] = useState<ApiRuleType[]>(initialRuleTypes);
|
||||
|
||||
// 添加一个路由变化计数器
|
||||
const [routeChangeCount, setRouteChangeCount] = useState(0);
|
||||
|
||||
// 添加一个状态来跟踪是否执行了删除操作
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
// 使用 ref 跟踪是否正在加载数据,避免重复加载
|
||||
const isLoadingRef = useRef(false);
|
||||
|
||||
// 查询参数记忆 key 与保存/恢复
|
||||
const SEARCH_PARAMS_STORAGE_KEY = 'rules.searchParams';
|
||||
@@ -231,19 +234,13 @@ export default function RulesIndex() {
|
||||
|
||||
// 获取当前的ruleType值
|
||||
const ruleTypeParam = searchParams.get('ruleType');
|
||||
|
||||
// 追踪路由变化
|
||||
useEffect(() => {
|
||||
// console.log("路由变化:", location.key);
|
||||
setRouteChangeCount(prev => prev + 1);
|
||||
}, [location]);
|
||||
|
||||
|
||||
// 判断是否禁用规则组选择
|
||||
const isRuleGroupSelectDisabled = loadingGroups || !ruleTypeParam || ruleGroups.length === 0;
|
||||
|
||||
// 检查用户是否为开发者角色
|
||||
const userRole = rootData?.userRole || 'common';
|
||||
const isDeveloper = userRole === 'admin';
|
||||
const isDeveloper = userRole.includes('admin');
|
||||
|
||||
// 调试日志
|
||||
// console.log("🔑 [Rules List] rootData:", rootData);
|
||||
@@ -261,56 +258,93 @@ export default function RulesIndex() {
|
||||
useEffect(() => {
|
||||
if(loaderData.error) {
|
||||
toastService.error(loaderData.error);
|
||||
}else if(loaderData.ruleTypes.length === 0){
|
||||
toastService.error("评查点类型数据为空");
|
||||
}
|
||||
}, [loaderData.error,loaderData.ruleTypes]);
|
||||
// ❌ 不再检查 loaderData.ruleTypes,因为服务端永远返回空数组
|
||||
// 如果需要检查评查点类型数据,应该在 fetchData 完成后检查状态 ruleTypes
|
||||
}, [loaderData.error]);
|
||||
|
||||
// 客户端数据加载函数
|
||||
const fetchData = useCallback(async () => {
|
||||
try {
|
||||
// 从sessionStorage获取reviewType
|
||||
const storedReviewType = typeof window !== 'undefined' ? sessionStorage.getItem('reviewType') : null;
|
||||
const typeToUse = reviewType || storedReviewType;
|
||||
|
||||
if (!typeToUse) {
|
||||
console.warn('无法加载评查点数据:未找到reviewType');
|
||||
// 🔑 如果正在加载,避免重复调用
|
||||
if (isLoadingRef.current) {
|
||||
console.log('📋 [fetchData] 正在加载中,跳过重复调用');
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log("fetchData被调用,加载评查点数据", typeToUse);
|
||||
|
||||
// 🔑 从 sessionStorage 获取 documentTypeIds
|
||||
const typeIdsStr = typeof window !== 'undefined' ? sessionStorage.getItem('documentTypeIds') : null;
|
||||
const documentTypeIds = typeIdsStr ? JSON.parse(typeIdsStr) : null;
|
||||
|
||||
if (!documentTypeIds || documentTypeIds.length === 0) {
|
||||
console.warn('无法加载评查点数据:未找到 documentTypeIds');
|
||||
return;
|
||||
}
|
||||
|
||||
isLoadingRef.current = true;
|
||||
|
||||
// 🔑 从 localStorage 获取 user_info 中的 area
|
||||
let userArea: string | undefined = undefined;
|
||||
if (typeof window !== 'undefined') {
|
||||
try {
|
||||
const userInfoStr = localStorage.getItem('user_info');
|
||||
if (userInfoStr) {
|
||||
const userInfo = JSON.parse(userInfoStr);
|
||||
userArea = userInfo.area;
|
||||
console.log("📋 [fetchData] 从 localStorage 获取到用户地区:", userArea);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析 user_info 失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("📋 [fetchData] 开始加载评查点数据, documentTypeIds:", documentTypeIds, "area:", userArea);
|
||||
setLoading(true);
|
||||
|
||||
// 获取评查点类型
|
||||
|
||||
// 🔑 获取评查点类型(通过 documentTypeIds)
|
||||
let loadedRuleTypes: ApiRuleType[] = [];
|
||||
try {
|
||||
const typeResponse = await getRuleTypes(typeToUse, loaderData.frontendJWT);
|
||||
const typeResponse = await getRuleTypes(documentTypeIds, loaderData.frontendJWT);
|
||||
if (typeResponse.data) {
|
||||
setRuleTypes(typeResponse.data);
|
||||
loadedRuleTypes = typeResponse.data;
|
||||
setRuleTypes(loadedRuleTypes);
|
||||
console.log("📋 [fetchData] 获取到评查点类型:", loadedRuleTypes);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载评查点类型失败:', error);
|
||||
}
|
||||
|
||||
|
||||
// 构建查询参数
|
||||
// 🔑 当选择"全部"或未选择评查点类型时,使用下拉框中所有评查点类型的 id 组合
|
||||
let finalRuleType: string | undefined = undefined;
|
||||
if (ruleTypeParam && ruleTypeParam !== 'all') {
|
||||
// 选择了具体的评查点类型
|
||||
finalRuleType = ruleTypeParam;
|
||||
} else if (loadedRuleTypes && loadedRuleTypes.length > 0) {
|
||||
// 选择"全部"或未选择,使用刚加载的评查点类型的 id
|
||||
finalRuleType = loadedRuleTypes.map(type => type.id).join(',');
|
||||
console.log("📋 [fetchData] 选择全部类型,使用 loadedRuleTypes 的 id 组合:", finalRuleType);
|
||||
}
|
||||
|
||||
const queryParams = {
|
||||
ruleType: ruleTypeParam || undefined,
|
||||
ruleType: finalRuleType,
|
||||
groupId: searchParams.get('groupId') || undefined,
|
||||
isActive: searchParams.get('isActive') ? searchParams.get('isActive') === 'true' : undefined,
|
||||
keyword: searchParams.get('keyword') || undefined,
|
||||
area: userArea, // 添加地区过滤
|
||||
page: currentPage,
|
||||
pageSize,
|
||||
reviewType: typeToUse,
|
||||
token: loaderData.frontendJWT
|
||||
};
|
||||
|
||||
|
||||
// 调用 API 获取数据
|
||||
const response = await getRulesList(queryParams);
|
||||
|
||||
|
||||
if (response.data) {
|
||||
const apiRules = response.data.rules || [];
|
||||
const total = response.data.totalCount || 0;
|
||||
const mappedRules = apiRules.map((apiRule: ApiRule) => mapApiRuleToModel(apiRule));
|
||||
|
||||
|
||||
setFilteredRules(mappedRules);
|
||||
setFilteredTotalCount(total);
|
||||
}
|
||||
@@ -319,8 +353,9 @@ export default function RulesIndex() {
|
||||
toastService.error('加载评查点列表失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
isLoadingRef.current = false;
|
||||
}
|
||||
}, [reviewType, ruleTypeParam, searchParams, currentPage, pageSize]);
|
||||
}, [ruleTypeParam, searchParams, currentPage, pageSize, loaderData.frontendJWT]);
|
||||
|
||||
// 当评查点类型变化时,加载对应的规则组
|
||||
useEffect(() => {
|
||||
@@ -380,55 +415,54 @@ export default function RulesIndex() {
|
||||
}
|
||||
}, [fetcher.data, fetcher.state, fetchData, isDeleting]);
|
||||
|
||||
// 在组件挂载时从 sessionStorage 获取 reviewType 并加载数据
|
||||
// 在组件挂载时从 sessionStorage 获取 documentTypeIds 并加载数据
|
||||
useEffect(() => {
|
||||
try {
|
||||
if (typeof window !== 'undefined') {
|
||||
const storedReviewType = sessionStorage.getItem('reviewType');
|
||||
// console.log("组件挂载,从sessionStorage获取reviewType:", storedReviewType);
|
||||
|
||||
if (storedReviewType !== reviewType) {
|
||||
setReviewType(storedReviewType);
|
||||
}
|
||||
|
||||
// 无论如何,都加载数据,不依赖于reviewType的变化
|
||||
if (storedReviewType) {
|
||||
// 使用setTimeout确保该操作在其他状态更新之后执行
|
||||
const typeIdsStr = sessionStorage.getItem('documentTypeIds');
|
||||
const documentTypeIds = typeIdsStr ? JSON.parse(typeIdsStr) : null;
|
||||
console.log("📋 组件挂载,从 sessionStorage 获取 documentTypeIds:", documentTypeIds);
|
||||
|
||||
// 如果有 documentTypeIds,加载数据
|
||||
if (documentTypeIds && documentTypeIds.length > 0) {
|
||||
// 使用 setTimeout 确保该操作在其他状态更新之后执行
|
||||
setTimeout(() => {
|
||||
fetchData();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取 sessionStorage 中的 reviewType 失败:', error);
|
||||
console.error('获取 sessionStorage 中的 documentTypeIds 失败:', error);
|
||||
}
|
||||
}, [initialLoad, fetchData]);
|
||||
|
||||
// 监听路由变化,每次路由到此页面时刷新数据
|
||||
useEffect(() => {
|
||||
if (routeChangeCount > 0) {
|
||||
// console.log("路由变化触发数据刷新,计数:", routeChangeCount);
|
||||
const storedReviewType = typeof window !== 'undefined' ? sessionStorage.getItem('reviewType') : null;
|
||||
// console.log("storedReviewType:", storedReviewType);
|
||||
if (storedReviewType) {
|
||||
if (storedReviewType !== reviewType) {
|
||||
setReviewType(storedReviewType);
|
||||
}
|
||||
|
||||
// 使用setTimeout确保该操作在其他状态更新之后执行
|
||||
setTimeout(() => {
|
||||
fetchData();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}, [routeChangeCount, fetchData]);
|
||||
// 注释掉重复的路由监听逻辑,避免与searchParams监听重复触发
|
||||
// useEffect(() => {
|
||||
// if (routeChangeCount > 0) {
|
||||
// console.log("📋 路由变化触发数据刷新,计数:", routeChangeCount);
|
||||
// const typeIdsStr = typeof window !== 'undefined' ? sessionStorage.getItem('documentTypeIds') : null;
|
||||
// const documentTypeIds = typeIdsStr ? JSON.parse(typeIdsStr) : null;
|
||||
// console.log("📋 documentTypeIds:", documentTypeIds);
|
||||
|
||||
// if (documentTypeIds && documentTypeIds.length > 0) {
|
||||
// // 使用 setTimeout 确保该操作在其他状态更新之后执行
|
||||
// setTimeout(() => {
|
||||
// fetchData();
|
||||
// }, 0);
|
||||
// }
|
||||
// }
|
||||
// }, [routeChangeCount, fetchData]);
|
||||
|
||||
// 监听 URL 参数变化,重新获取数据
|
||||
useEffect(() => {
|
||||
if (reviewType) {
|
||||
// 检查是否有 documentTypeIds
|
||||
const typeIdsStr = typeof window !== 'undefined' ? sessionStorage.getItem('documentTypeIds') : null;
|
||||
const documentTypeIds = typeIdsStr ? JSON.parse(typeIdsStr) : null;
|
||||
|
||||
if (documentTypeIds && documentTypeIds.length > 0) {
|
||||
fetchData();
|
||||
}
|
||||
}, [searchParams, fetchData, reviewType]);
|
||||
}, [searchParams, fetchData]);
|
||||
|
||||
// 筛选评查点
|
||||
const handleFilterChange = (e: React.ChangeEvent<HTMLSelectElement | HTMLInputElement>) => {
|
||||
|
||||
Reference in New Issue
Block a user