优化数据隔离,进行权限控制

This commit is contained in:
2025-06-03 15:17:09 +08:00
parent 15ef4a3ced
commit 057563ba5e
10 changed files with 244 additions and 94 deletions
+15
View File
@@ -68,6 +68,21 @@ export function Layout({ children, userRole = 'developer' }: LayoutProps) {
} }
}, []); }, []);
// 路由变化时,检查并更新应用模块
useEffect(() => {
if (typeof window !== 'undefined') {
try {
const reviewType = sessionStorage.getItem('reviewType');
console.log('Layout 路由变化, reviewType:', reviewType, '路径:', location.pathname);
if (reviewType && REVIEW_TYPE_TO_APP[reviewType]) {
setSelectedApp(REVIEW_TYPE_TO_APP[reviewType]);
}
} catch (error) {
console.error('路由变化时获取reviewType失败:', error);
}
}
}, [location.pathname]);
const toggleSidebar = () => { const toggleSidebar = () => {
const newState = !sidebarCollapsed; const newState = !sidebarCollapsed;
setSidebarCollapsed(newState); setSidebarCollapsed(newState);
+55 -51
View File
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { Link, useLocation } from '@remix-run/react'; import { Link, useLocation, useNavigate } from '@remix-run/react';
import type { UserRole } from '~/root'; import type { UserRole } from '~/root';
interface MenuItem { interface MenuItem {
@@ -44,31 +44,24 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = 'contract
const location = useLocation(); const location = useLocation();
const [expandedMenus, setExpandedMenus] = useState<Record<string, boolean>>({}); const [expandedMenus, setExpandedMenus] = useState<Record<string, boolean>>({});
const [currentApp, setCurrentApp] = useState<string>(selectedApp); const [currentApp, setCurrentApp] = useState<string>(selectedApp);
const navigate = useNavigate();
// 组件挂载后从 sessionStorage 读取初始 reviewType
useEffect(() => {
try {
const reviewType = sessionStorage.getItem('reviewType');
// console.log('初始 reviewType:', reviewType);
if (reviewType) {
setCurrentApp(reviewType);
}
} catch (error) {
console.error('读取 reviewType 失败:', error);
}
}, []);
// 从 sessionStorage 获取 reviewType 并设置当前应用模块 // 从 sessionStorage 获取 reviewType 并设置当前应用模块
useEffect(() => { useEffect(() => {
// 初始加载时获取 reviewType // 监听 sessionStorage 变化(主要用于多标签页情况)
const updateReviewType = () => {
if (typeof window !== 'undefined') {
const reviewType = sessionStorage.getItem('reviewType');
if (reviewType) {
setCurrentApp(reviewType);
}
}
};
// 首次执行
updateReviewType();
// 设置轮询,每秒检查一次 reviewType 变化
const intervalId = setInterval(updateReviewType, 1000);
// 添加自定义事件监听
const handleReviewTypeChange = () => {
updateReviewType();
};
// 监听 sessionStorage 变化
const handleStorageChange = (e: StorageEvent) => { const handleStorageChange = (e: StorageEvent) => {
if (e.key === 'reviewType' && e.newValue) { if (e.key === 'reviewType' && e.newValue) {
setCurrentApp(e.newValue); setCurrentApp(e.newValue);
@@ -76,23 +69,23 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = 'contract
}; };
// 添加事件监听器 // 添加事件监听器
window.addEventListener('reviewTypeChange', handleReviewTypeChange);
window.addEventListener('storage', handleStorageChange); window.addEventListener('storage', handleStorageChange);
return () => { return () => {
clearInterval(intervalId);
window.removeEventListener('reviewTypeChange', handleReviewTypeChange);
window.removeEventListener('storage', handleStorageChange); window.removeEventListener('storage', handleStorageChange);
}; };
}, []); }, []);
// 监听路由变化,重新检查 reviewType // 监听路由变化,重新检查 reviewType
useEffect(() => { useEffect(() => {
if (typeof window !== 'undefined') { try {
const reviewType = sessionStorage.getItem('reviewType'); const reviewType = sessionStorage.getItem('reviewType');
// console.log('路由变化, 检查 reviewType:', reviewType, '路径:', location.pathname);
if (reviewType) { if (reviewType) {
setCurrentApp(reviewType); setCurrentApp(reviewType);
} }
} catch (error) {
console.error('路由变化时读取 reviewType 失败:', error);
} }
}, [location.pathname]); }, [location.pathname]);
@@ -110,26 +103,6 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = 'contract
path: '/home', path: '/home',
icon: 'ri-home-line' icon: 'ri-home-line'
}, },
{
id: 'contract-template',
title: '合同模板',
path: '/contract-template',
icon: 'ri-file-search-line',
children: [
{
id: 'contract-search-ai',
title: '智能搜索',
path: '/contract-template/search',
icon: 'ri-search-line'
},
{
id: 'contract-list',
title: '合同列表',
path: '/contract-template/list',
icon: 'ri-folder-line'
}
]
},
{ {
id: 'file-management', id: 'file-management',
title: '文件管理', title: '文件管理',
@@ -178,6 +151,7 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = 'contract
id: 'rule-new', id: 'rule-new',
title: '新增评查点', title: '新增评查点',
path: '/rules-new', path: '/rules-new',
requiredRole: 'developer',
icon: 'ri-add-circle-line' icon: 'ri-add-circle-line'
}, },
// { // {
@@ -188,6 +162,26 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = 'contract
// } // }
] ]
}, },
{
id: 'contract-template',
title: '合同模板',
path: '/contract-template',
icon: 'ri-file-search-line',
children: [
{
id: 'contract-search-ai',
title: '智能搜索',
path: '/contract-template/search',
icon: 'ri-search-line'
},
{
id: 'contract-list',
title: '合同列表',
path: '/contract-template/list',
icon: 'ri-folder-line'
}
]
},
{ {
id: 'system-settings', id: 'system-settings',
title: '系统设置', title: '系统设置',
@@ -270,6 +264,7 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = 'contract
// 获取当前应用模式下应显示的菜单ID列表 // 获取当前应用模式下应显示的菜单ID列表
const visibleMenuIds = APP_MENU_MAP[currentApp as keyof typeof APP_MENU_MAP] || APP_MENU_MAP['contract']; const visibleMenuIds = APP_MENU_MAP[currentApp as keyof typeof APP_MENU_MAP] || APP_MENU_MAP['contract'];
// console.log('当前应用模式:', currentApp, '可见菜单ID:', visibleMenuIds);
// 根据用户角色和当前应用模式过滤菜单项 // 根据用户角色和当前应用模式过滤菜单项
const filteredMenuItems = menuItems.filter(item => { const filteredMenuItems = menuItems.filter(item => {
@@ -289,7 +284,19 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = 'contract
return ( return (
<div className={`sidebar ${collapsed ? 'collapsed' : ''}`}> <div className={`sidebar ${collapsed ? 'collapsed' : ''}`}>
<div className="py-6 px-4 border-b border-gray-100 flex justify-between items-center"> <div className="py-6 px-4 border-b border-gray-100 flex justify-between items-center">
<div className="flex items-center"> <div className="flex items-center"
onClick={() => {
navigate('/');
}}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
navigate('/');
}
}}
>
<img src="/logo.svg" alt="智慧法务" className="w-12 h-12 mr-2" /> <img src="/logo.svg" alt="智慧法务" className="w-12 h-12 mr-2" />
{!collapsed && <h2 className="text-lg font-medium"></h2>} {!collapsed && <h2 className="text-lg font-medium"></h2>}
</div> </div>
@@ -309,9 +316,6 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = 'contract
<i className={`${APP_ICON_MAP[currentApp] || 'ri-file-list-2-fill'} mr-2 text-xl`}></i> <i className={`${APP_ICON_MAP[currentApp] || 'ri-file-list-2-fill'} mr-2 text-xl`}></i>
<span className="font-medium">{APP_NAME_MAP[currentApp] || '合同管理'}</span> <span className="font-medium">{APP_NAME_MAP[currentApp] || '合同管理'}</span>
</div> </div>
<div className="text-xs text-gray-500 mt-1">
: {APP_NAME_MAP[currentApp] || '合同管理'}
</div>
</div> </div>
)} )}
+1
View File
@@ -168,6 +168,7 @@ export function links() {
{ rel: "stylesheet", href: styles }, { rel: "stylesheet", href: styles },
{ rel: "stylesheet", href: messageModalStyles }, { rel: "stylesheet", href: messageModalStyles },
{ rel: "stylesheet", href: toastStyles }, { rel: "stylesheet", href: toastStyles },
{ rel: "icon", type: "image/svg+xml", href: "/logo.svg" },
// { rel: "preconnect", href: "https://fonts.googleapis.com" }, // { rel: "preconnect", href: "https://fonts.googleapis.com" },
// { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" }, // { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" },
// { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap" } // { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap" }
+16 -9
View File
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useNavigate, Form } from '@remix-run/react'; import { useNavigate, Form, useLoaderData } from '@remix-run/react';
import { type MetaFunction, type ActionFunctionArgs, LoaderFunctionArgs, redirect } from "@remix-run/node"; import { type MetaFunction, type ActionFunctionArgs, LoaderFunctionArgs, redirect } from "@remix-run/node";
import styles from "~/styles/pages/home.css?url"; import styles from "~/styles/pages/home.css?url";
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -30,21 +30,27 @@ export async function action({ request }: ActionFunctionArgs) {
// 验证用户登录状态 // 验证用户登录状态
export async function loader({ request }: LoaderFunctionArgs) { export async function loader({ request }: LoaderFunctionArgs) {
const { isAuthenticated } = await getUserSession(request); const { isAuthenticated, userRole } = await getUserSession(request);
if (!isAuthenticated) { if (!isAuthenticated) {
return redirect("/login"); return redirect("/login");
} }
return null; return Response.json({ userRole });
} }
export default function Index() { export default function Index() {
const navigate = useNavigate(); const navigate = useNavigate();
const { userRole } = useLoaderData<typeof loader>();
const [currentDateTime, setCurrentDateTime] = useState({ const [currentDateTime, setCurrentDateTime] = useState({
date: '', date: '',
time: '' time: ''
}); });
// 打印服务器端传递的用户角色
useEffect(() => {
console.log('_index 服务器返回的用户角色:', userRole);
}, [userRole]);
// 更新日期时间 // 更新日期时间
useEffect(() => { useEffect(() => {
const updateDateTime = () => { const updateDateTime = () => {
@@ -83,10 +89,8 @@ export default function Index() {
// 处理登出 // 处理登出
const handleLogout = () => { const handleLogout = () => {
// 清除sessionStorage中的用户角色信息 // 清除sessionStorage中的所有数据
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
sessionStorage.removeItem('userRole');
// 可以根据需要清除其他会话数据
sessionStorage.clear(); sessionStorage.clear();
} }
@@ -94,6 +98,9 @@ export default function Index() {
const form = document.getElementById('logout-form') as HTMLFormElement; const form = document.getElementById('logout-form') as HTMLFormElement;
if (form) { if (form) {
form.submit(); form.submit();
} else {
// 如果找不到表单,直接导航到登录页
navigate('/login');
} }
}; };
@@ -117,7 +124,7 @@ export default function Index() {
<span className="datetime">{currentDateTime.date} {currentDateTime.time}</span> <span className="datetime">{currentDateTime.date} {currentDateTime.time}</span>
<div className="user"> <div className="user">
<img src="/avatar.png" alt="用户头像" className="avatar" /> <img src="/avatar.png" alt="用户头像" className="avatar" />
<span className="username"></span> <span className="username">{userRole === 'developer' ? '系统管理员' : '普通用户'}</span>
<button <button
onClick={handleLogout} onClick={handleLogout}
className="logout-button" className="logout-button"
@@ -163,8 +170,8 @@ export default function Index() {
{/* 智慧法务大模型模块 */} {/* 智慧法务大模型模块 */}
<div <div
className="module-card" className="module-card"
onClick={() => handleModuleClick('/prompts', 'model')} onClick={() => handleModuleClick('/', 'model')}
onKeyDown={(e) => handleKeyDown('/prompts', 'model', e)} onKeyDown={(e) => handleKeyDown('/', 'model', e)}
role="button" role="button"
tabIndex={0} tabIndex={0}
aria-label="智慧法务大模型" aria-label="智慧法务大模型"
+17 -2
View File
@@ -22,10 +22,12 @@ import {
DocumentStatus DocumentStatus
} from "~/api/files/files-upload"; } from "~/api/files/files-upload";
import { updateDocumentAuditStatus } from "~/api/evaluation_points/rules-files"; import { updateDocumentAuditStatus } from "~/api/evaluation_points/rules-files";
import { links as fileTypeTagLinks } from "~/components/ui/FileTypeTag";
export function links() { export function links() {
return [ return [
{ rel: "stylesheet", href: uploadStyles } { rel: "stylesheet", href: uploadStyles },
...fileTypeTagLinks()
]; ];
} }
@@ -1446,8 +1448,21 @@ export default function FilesUpload() {
width: "15%", width: "15%",
render: (_: unknown, record: Document) => { render: (_: unknown, record: Document) => {
const typeName = getDocumentTypeName(record.type_id); const typeName = getDocumentTypeName(record.type_id);
// 根据typeName判断应用哪种样式类名
let typeClass = "file-type-badge";
if (typeName.includes('合同')) {
typeClass += " file-type-tag-contract";
} else if (typeName.includes('许可') || typeName.includes('行政许可')) {
typeClass += " file-type-tag-license-doc";
} else if (typeName.includes('处罚') || typeName.includes('行政处罚')) {
typeClass += " file-type-tag-punishment-doc";
} else {
typeClass += " file-type-tag-other";
}
return ( return (
<span className="file-type-badge"> <span className={typeClass}>
{typeName} {typeName}
</span> </span>
); );
+69 -14
View File
@@ -1,6 +1,6 @@
// import React from 'react'; // import React from 'react';
import { type MetaFunction } from "@remix-run/node"; import { type MetaFunction } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react"; import { useLoaderData, useNavigate, Form } 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 { FileTag, links as fileTagLinks } from "~/components/ui/FileTag"; import { FileTag, links as fileTagLinks } from "~/components/ui/FileTag";
@@ -11,6 +11,9 @@ import { getDocuments, type DocumentUI, type DocumentSearchParams } from "~/api/
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { getHomeData } from "~/api/home/home"; import { getHomeData } from "~/api/home/home";
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import type { UserRole } from '~/root';
import { type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/node";
import { logout, getUserSession } from "~/root";
// import { getUserSession } from "~/root"; // import { getUserSession } from "~/root";
// 文件处理状态选项 // 文件处理状态选项
@@ -43,15 +46,11 @@ export const meta: MetaFunction = () => {
// } // }
// 添加认证检查 // 添加认证检查
export async function loader() { export async function loader({ request }: LoaderFunctionArgs) {
// 检查用户登录状态
// const { isAuthenticated } = await getUserSession(request);
// if (!isAuthenticated) {
// return redirect("/login");
// }
try { try {
// 从根loader获取用户角色
const { userRole } = await getUserSession(request);
// 返回默认值,实际数据将在客户端根据 sessionStorage 加载 // 返回默认值,实际数据将在客户端根据 sessionStorage 加载
return Response.json({ return Response.json({
homeData: { homeData: {
@@ -64,7 +63,8 @@ export async function loader() {
issuesGrowth: { value: 0, isUp: true } issuesGrowth: { value: 0, isUp: true }
}, },
recentFiles: [], recentFiles: [],
reviewType: null reviewType: null,
userRole: userRole
}); });
} catch (error) { } catch (error) {
// 错误处理 // 错误处理
@@ -76,8 +76,21 @@ export async function loader() {
} }
} }
// 处理登出请求
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const intent = formData.get("intent");
if (intent === "logout") {
return logout(request);
}
return null;
}
export default function Home() { export default function Home() {
const { homeData: initialHomeData, recentFiles: initialRecentFiles } = useLoaderData<typeof loader>(); const navigate = useNavigate();
const { homeData: initialHomeData, recentFiles: initialRecentFiles, userRole: serverUserRole } = useLoaderData<typeof loader>();
const [recentFiles, setRecentFiles] = useState<DocumentUI[]>(initialRecentFiles || []); const [recentFiles, setRecentFiles] = useState<DocumentUI[]>(initialRecentFiles || []);
const [homeData, setHomeData] = useState(initialHomeData); const [homeData, setHomeData] = useState(initialHomeData);
const [currentDateTime, setCurrentDateTime] = useState({ const [currentDateTime, setCurrentDateTime] = useState({
@@ -85,6 +98,12 @@ export default function Home() {
time: '' time: ''
}); });
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const userRole = serverUserRole as UserRole;
// 打印服务器端传递的用户角色
useEffect(() => {
console.log('服务器返回的用户角色:', serverUserRole);
}, [serverUserRole]);
// 更新当前时间 // 更新当前时间
useEffect(() => { useEffect(() => {
@@ -107,6 +126,27 @@ export default function Home() {
return () => clearInterval(timerID); return () => clearInterval(timerID);
}, []); }, []);
// 处理登出操作
const handleLogout = () => {
// 清除sessionStorage中的所有数据
if (typeof window !== 'undefined') {
sessionStorage.removeItem('userRole');
sessionStorage.removeItem('reviewType');
sessionStorage.removeItem('previousReviewType');
// 可以根据需要清除其他会话数据
sessionStorage.clear();
}
// 使用Form组件提交登出请求
const form = document.getElementById('logout-form') as HTMLFormElement;
if (form) {
form.submit();
} else {
// 如果找不到表单,直接导航到登录页
navigate('/login');
}
};
// 在客户端挂载时,根据 sessionStorage 中的 reviewType 加载正确的数据 // 在客户端挂载时,根据 sessionStorage 中的 reviewType 加载正确的数据
useEffect(() => { useEffect(() => {
const loadData = async () => { const loadData = async () => {
@@ -246,6 +286,11 @@ export default function Home() {
return ( return (
<div className="dashboard-container"> <div className="dashboard-container">
{/* 登出表单 - 隐藏 */}
<Form method="post" id="logout-form" className="hidden">
<input type="hidden" name="intent" value="logout" />
</Form>
{/* 页面头部 */} {/* 页面头部 */}
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-medium"></h2> <h2 className="text-xl font-medium"></h2>
@@ -257,13 +302,23 @@ export default function Home() {
</div> </div>
<div className="user-profile p-4 border-b border-gray-100 flex items-center"> <div className="user-profile p-4 border-b border-gray-100 flex items-center">
<div className="avatar w-10 h-10 rounded-full bg-primary text-white flex items-center justify-center"> <div className="avatar w-10 h-10 rounded-full bg-primary text-white flex items-center justify-center">
<span></span> <span>{userRole === 'developer' ? '管' : '用'}</span>
</div> </div>
<div className="ml-3"> <div className="ml-3">
<p className="text-sm font-medium"></p> <p className="text-sm font-medium">{userRole === 'developer' ? '系统管理员' : '普通用户'}</p>
<p className="text-xs text-gray-500"></p> <p className="text-xs text-gray-500">{userRole === 'developer' ? '超级管理员' : '标准权限'}</p>
</div> </div>
</div> </div>
{/* 登出操作 */}
<Button
type="default"
size="small"
className="ml-4 hover:bg-gray-100"
onClick={handleLogout}
>
<i className="ri-logout-box-line mr-1"></i>
</Button>
</div> </div>
</div> </div>
+19 -4
View File
@@ -28,6 +28,13 @@ export async function action({ request }: ActionFunctionArgs) {
if (!username || !password) { if (!username || !password) {
return Response.json({ error: "用户名和密码不能为空" }); return Response.json({ error: "用户名和密码不能为空" });
} }
if (userRole === 'developer') {
if (username !== 'admin' || password !== 'admin') {
// toastService.error("管理员用户名或密码错误");
return Response.json({ error: "管理员用户名或密码错误" });
}
}
// 在实际应用中,这里应该是对用户名和密码的验证逻辑 // 在实际应用中,这里应该是对用户名和密码的验证逻辑
// 简化起见,我们直接视为登录成功 // 简化起见,我们直接视为登录成功
@@ -61,6 +68,13 @@ export default function Login() {
const actionData = useActionData<typeof action>(); const actionData = useActionData<typeof action>();
const navigation = useNavigation(); const navigation = useNavigation();
// 使用 useEffect 确保错误提示只显示一次
// useEffect(() => {
// if(actionData?.error) {
// toastService.error(actionData.error);
// }
// }, [actionData?.error]);
// 判断是否正在提交表单 // 判断是否正在提交表单
const isSubmitting = navigation.state === "submitting"; const isSubmitting = navigation.state === "submitting";
@@ -76,7 +90,10 @@ export default function Login() {
<h2 className="login-subtitle"></h2> <h2 className="login-subtitle"></h2>
<Form method="post" className="login-form"> <Form method="post" className="login-form">
{actionData?.error && ( {actionData?.error && (
<div className="error-message">{actionData.error}</div> <div className="error-message-container">
<div className="error-icon"><i className="ri-error-warning-line"></i></div>
<div className="error-text">{actionData.error}</div>
</div>
)} )}
<div className="form-group"> <div className="form-group">
@@ -89,7 +106,6 @@ export default function Login() {
onChange={(e) => setUsername(e.target.value)} onChange={(e) => setUsername(e.target.value)}
className="form-input" className="form-input"
placeholder="请输入用户名" placeholder="请输入用户名"
required
/> />
</div> </div>
@@ -103,7 +119,6 @@ export default function Login() {
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
className="form-input" className="form-input"
placeholder="请输入密码" placeholder="请输入密码"
required
/> />
</div> </div>
@@ -118,7 +133,7 @@ export default function Login() {
required required
> >
<option value="common"></option> <option value="common"></option>
<option value="developer"></option> <option value="developer"></option>
</select> </select>
</div> </div>
+10 -14
View File
@@ -33,7 +33,7 @@ import { ReviewSettings } from "~/components/rules/new/ReviewSettings";
import { ActionButtons } from "~/components/rules/new/ActionButtons"; import { ActionButtons } from "~/components/rules/new/ActionButtons";
import { PageHeader } from "~/components/rules/new/PageHeader"; import { PageHeader } from "~/components/rules/new/PageHeader";
import rulesStyles from "~/styles/rules.css?url"; import rulesStyles from "~/styles/rules.css?url";
import { useNavigate, useLocation } from "@remix-run/react"; import { useNavigate, useLocation, useRouteLoaderData } from "@remix-run/react";
// 导入评查点模型定义和常量 // 导入评查点模型定义和常量
import type { import type {
EvaluationPoint, EvaluationPoint,
@@ -153,12 +153,15 @@ export default function RuleNew() {
const [isEditMode, setIsEditMode] = useState(false); const [isEditMode, setIsEditMode] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [instanceKey, setInstanceKey] = useState<string>('new'); const [instanceKey, setInstanceKey] = useState<string>('new');
const [userRole, setUserRole] = useState<UserRole>('common'); // 从root路由获取用户角色,而不是从sessionStorage
const rootData = useRouteLoaderData("root") as { userRole: UserRole };
const userRole = rootData?.userRole || 'common';
const [formData, setFormData] = useState<EvaluationPoint>({}); const [formData, setFormData] = useState<EvaluationPoint>({});
const [evaluationPointGroups, setEvaluationPointGroups] = useState<EvaluationPointGroup[]>([]); const [evaluationPointGroups, setEvaluationPointGroups] = useState<EvaluationPointGroup[]>([]);
// 检查用户是否为开发者角色 // 判断表单是否为只读模式
const isDeveloper = userRole === 'developer'; const isReadOnly = userRole === 'common';
// 添加用于共享的字段数据状态 // 添加用于共享的字段数据状态
const [extractionFields, setExtractionFields] = useState<string[]>([]); const [extractionFields, setExtractionFields] = useState<string[]>([]);
@@ -710,13 +713,6 @@ export default function RuleNew() {
const id = searchParams.get('id'); const id = searchParams.get('id');
const mode = searchParams.get('mode'); const mode = searchParams.get('mode');
// 从sessionStorage获取用户角色
if (typeof window !== 'undefined') {
const userRoleFromSession = sessionStorage.getItem('userRole') as UserRole || 'common';
// console.log("userRoleFromSession-----",userRoleFromSession);
setUserRole(userRoleFromSession);
}
// 编辑或复制模式下设置加载状态 // 编辑或复制模式下设置加载状态
if (id || mode === 'copy') { if (id || mode === 'copy') {
setIsLoading(true); setIsLoading(true);
@@ -746,9 +742,9 @@ export default function RuleNew() {
<div className="container"> <div className="container">
{/* 页面标题和右上角保存按钮 */} {/* 页面标题和右上角保存按钮 */}
<PageHeader <PageHeader
title={isEditMode ? "编辑评查点" : "新增评查点"} title={isEditMode ? (isReadOnly ? "查看评查点" : "编辑评查点") : "新增评查点"}
onSave={handleSave} onSave={handleSave}
showSaveButton={isDeveloper} showSaveButton={!isReadOnly}
/> />
{/* 加载状态显示 */} {/* 加载状态显示 */}
@@ -817,7 +813,7 @@ export default function RuleNew() {
onSave={handleSave} onSave={handleSave}
onSaveDraft={handleSaveDraft} onSaveDraft={handleSaveDraft}
isEditMode={isEditMode} isEditMode={isEditMode}
showButtons={isDeveloper} showButtons={!isReadOnly}
/> />
</div> </div>
</RuleContext.Provider> </RuleContext.Provider>
+30
View File
@@ -53,6 +53,36 @@
gap: 1.5rem; gap: 1.5rem;
} }
/* 优化的错误提示样式 */
.error-message-container {
display: flex;
align-items: center;
padding: 0.75rem 1rem;
background-color: #fef2f2;
border: 1px solid #fee2e2;
border-radius: 6px;
animation: fadeIn 0.3s ease-in-out;
}
.error-icon {
color: #ef4444;
font-size: 1.25rem;
margin-right: 0.75rem;
display: flex;
align-items: center;
}
.error-text {
color: #b91c1c;
font-size: 0.875rem;
font-weight: 500;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.form-group { .form-group {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
+12
View File
@@ -31,5 +31,17 @@ export default defineConfig({
open: true, open: true,
allowedHosts: ['nas.7bm.co', 'localhost', '127.0.0.1'], // 允许的主机名列表1 allowedHosts: ['nas.7bm.co', 'localhost', '127.0.0.1'], // 允许的主机名列表1
cors: true, cors: true,
// HMR配置
hmr: {
// 控制HMR更新时行为
overlay: false,
},
},
// 优化依赖预构建配置
optimizeDeps: {
// 防止依赖预构建时触发页面刷新导致路由中断
force: false,
// 预构建这些依赖,避免首次加载时出现重新构建
include: ['react-pdf', 'pdfjs-dist','dayjs','@remix-run/node','react-dom','axios','dayjs/plugin/utc','react-router-dom','jszip'],
}, },
}); });