优化数据隔离,进行权限控制
This commit is contained in:
@@ -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 newState = !sidebarCollapsed;
|
||||
setSidebarCollapsed(newState);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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';
|
||||
|
||||
interface MenuItem {
|
||||
@@ -44,31 +44,24 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = 'contract
|
||||
const location = useLocation();
|
||||
const [expandedMenus, setExpandedMenus] = useState<Record<string, boolean>>({});
|
||||
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 并设置当前应用模块
|
||||
useEffect(() => {
|
||||
// 初始加载时获取 reviewType
|
||||
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 变化
|
||||
// 监听 sessionStorage 变化(主要用于多标签页情况)
|
||||
const handleStorageChange = (e: StorageEvent) => {
|
||||
if (e.key === 'reviewType' && 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);
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
window.removeEventListener('reviewTypeChange', handleReviewTypeChange);
|
||||
window.removeEventListener('storage', handleStorageChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 监听路由变化,重新检查 reviewType
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
try {
|
||||
const reviewType = sessionStorage.getItem('reviewType');
|
||||
// console.log('路由变化, 检查 reviewType:', reviewType, '路径:', location.pathname);
|
||||
if (reviewType) {
|
||||
setCurrentApp(reviewType);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('路由变化时读取 reviewType 失败:', error);
|
||||
}
|
||||
}, [location.pathname]);
|
||||
|
||||
@@ -110,26 +103,6 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = 'contract
|
||||
path: '/home',
|
||||
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',
|
||||
title: '文件管理',
|
||||
@@ -178,6 +151,7 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = 'contract
|
||||
id: 'rule-new',
|
||||
title: '新增评查点',
|
||||
path: '/rules-new',
|
||||
requiredRole: 'developer',
|
||||
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',
|
||||
title: '系统设置',
|
||||
@@ -270,6 +264,7 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = 'contract
|
||||
|
||||
// 获取当前应用模式下应显示的菜单ID列表
|
||||
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 => {
|
||||
@@ -289,7 +284,19 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = 'contract
|
||||
return (
|
||||
<div className={`sidebar ${collapsed ? 'collapsed' : ''}`}>
|
||||
<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" />
|
||||
{!collapsed && <h2 className="text-lg font-medium">智慧法务</h2>}
|
||||
</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>
|
||||
<span className="font-medium">{APP_NAME_MAP[currentApp] || '合同管理'}</span>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
当前模块: {APP_NAME_MAP[currentApp] || '合同管理'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -168,6 +168,7 @@ export function links() {
|
||||
{ rel: "stylesheet", href: styles },
|
||||
{ rel: "stylesheet", href: messageModalStyles },
|
||||
{ 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.gstatic.com", crossOrigin: "anonymous" },
|
||||
// { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap" }
|
||||
|
||||
+16
-9
@@ -1,5 +1,5 @@
|
||||
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 styles from "~/styles/pages/home.css?url";
|
||||
import dayjs from 'dayjs';
|
||||
@@ -30,21 +30,27 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
|
||||
// 验证用户登录状态
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const { isAuthenticated } = await getUserSession(request);
|
||||
const { isAuthenticated, userRole } = await getUserSession(request);
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return redirect("/login");
|
||||
}
|
||||
return null;
|
||||
return Response.json({ userRole });
|
||||
}
|
||||
|
||||
export default function Index() {
|
||||
const navigate = useNavigate();
|
||||
const { userRole } = useLoaderData<typeof loader>();
|
||||
const [currentDateTime, setCurrentDateTime] = useState({
|
||||
date: '',
|
||||
time: ''
|
||||
});
|
||||
|
||||
// 打印服务器端传递的用户角色
|
||||
useEffect(() => {
|
||||
console.log('_index 服务器返回的用户角色:', userRole);
|
||||
}, [userRole]);
|
||||
|
||||
// 更新日期时间
|
||||
useEffect(() => {
|
||||
const updateDateTime = () => {
|
||||
@@ -83,10 +89,8 @@ export default function Index() {
|
||||
|
||||
// 处理登出
|
||||
const handleLogout = () => {
|
||||
// 清除sessionStorage中的用户角色信息
|
||||
// 清除sessionStorage中的所有数据
|
||||
if (typeof window !== 'undefined') {
|
||||
sessionStorage.removeItem('userRole');
|
||||
// 可以根据需要清除其他会话数据
|
||||
sessionStorage.clear();
|
||||
}
|
||||
|
||||
@@ -94,6 +98,9 @@ export default function Index() {
|
||||
const form = document.getElementById('logout-form') as HTMLFormElement;
|
||||
if (form) {
|
||||
form.submit();
|
||||
} else {
|
||||
// 如果找不到表单,直接导航到登录页
|
||||
navigate('/login');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -117,7 +124,7 @@ export default function Index() {
|
||||
<span className="datetime">{currentDateTime.date} {currentDateTime.time}</span>
|
||||
<div className="user">
|
||||
<img src="/avatar.png" alt="用户头像" className="avatar" />
|
||||
<span className="username">系统管理员</span>
|
||||
<span className="username">{userRole === 'developer' ? '系统管理员' : '普通用户'}</span>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="logout-button"
|
||||
@@ -163,8 +170,8 @@ export default function Index() {
|
||||
{/* 智慧法务大模型模块 */}
|
||||
<div
|
||||
className="module-card"
|
||||
onClick={() => handleModuleClick('/prompts', 'model')}
|
||||
onKeyDown={(e) => handleKeyDown('/prompts', 'model', e)}
|
||||
onClick={() => handleModuleClick('/', 'model')}
|
||||
onKeyDown={(e) => handleKeyDown('/', 'model', e)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label="智慧法务大模型"
|
||||
|
||||
@@ -22,10 +22,12 @@ import {
|
||||
DocumentStatus
|
||||
} from "~/api/files/files-upload";
|
||||
import { updateDocumentAuditStatus } from "~/api/evaluation_points/rules-files";
|
||||
import { links as fileTypeTagLinks } from "~/components/ui/FileTypeTag";
|
||||
|
||||
export function links() {
|
||||
return [
|
||||
{ rel: "stylesheet", href: uploadStyles }
|
||||
{ rel: "stylesheet", href: uploadStyles },
|
||||
...fileTypeTagLinks()
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1446,8 +1448,21 @@ export default function FilesUpload() {
|
||||
width: "15%",
|
||||
render: (_: unknown, record: Document) => {
|
||||
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 (
|
||||
<span className="file-type-badge">
|
||||
<span className={typeClass}>
|
||||
{typeName}
|
||||
</span>
|
||||
);
|
||||
|
||||
+69
-14
@@ -1,6 +1,6 @@
|
||||
// import React from 'react';
|
||||
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 { Button } from "~/components/ui/Button";
|
||||
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 { getHomeData } from "~/api/home/home";
|
||||
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";
|
||||
|
||||
// 文件处理状态选项
|
||||
@@ -43,15 +46,11 @@ export const meta: MetaFunction = () => {
|
||||
// }
|
||||
|
||||
// 添加认证检查
|
||||
export async function loader() {
|
||||
// 检查用户登录状态
|
||||
// const { isAuthenticated } = await getUserSession(request);
|
||||
|
||||
// if (!isAuthenticated) {
|
||||
// return redirect("/login");
|
||||
// }
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
try {
|
||||
// 从根loader获取用户角色
|
||||
const { userRole } = await getUserSession(request);
|
||||
|
||||
// 返回默认值,实际数据将在客户端根据 sessionStorage 加载
|
||||
return Response.json({
|
||||
homeData: {
|
||||
@@ -64,7 +63,8 @@ export async function loader() {
|
||||
issuesGrowth: { value: 0, isUp: true }
|
||||
},
|
||||
recentFiles: [],
|
||||
reviewType: null
|
||||
reviewType: null,
|
||||
userRole: userRole
|
||||
});
|
||||
} 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() {
|
||||
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 [homeData, setHomeData] = useState(initialHomeData);
|
||||
const [currentDateTime, setCurrentDateTime] = useState({
|
||||
@@ -85,6 +98,12 @@ export default function Home() {
|
||||
time: ''
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const userRole = serverUserRole as UserRole;
|
||||
|
||||
// 打印服务器端传递的用户角色
|
||||
useEffect(() => {
|
||||
console.log('服务器返回的用户角色:', serverUserRole);
|
||||
}, [serverUserRole]);
|
||||
|
||||
// 更新当前时间
|
||||
useEffect(() => {
|
||||
@@ -107,6 +126,27 @@ export default function Home() {
|
||||
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 加载正确的数据
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
@@ -246,6 +286,11 @@ export default function Home() {
|
||||
|
||||
return (
|
||||
<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">
|
||||
<h2 className="text-xl font-medium">系统概览</h2>
|
||||
@@ -257,13 +302,23 @@ export default function Home() {
|
||||
</div>
|
||||
<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">
|
||||
<span>管</span>
|
||||
<span>{userRole === 'developer' ? '管' : '用'}</span>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<p className="text-sm font-medium">系统管理员</p>
|
||||
<p className="text-xs text-gray-500">超级管理员</p>
|
||||
<p className="text-sm font-medium">{userRole === 'developer' ? '系统管理员' : '普通用户'}</p>
|
||||
<p className="text-xs text-gray-500">{userRole === 'developer' ? '超级管理员' : '标准权限'}</p>
|
||||
</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>
|
||||
|
||||
|
||||
+19
-4
@@ -28,6 +28,13 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
if (!username || !password) {
|
||||
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 navigation = useNavigation();
|
||||
|
||||
// 使用 useEffect 确保错误提示只显示一次
|
||||
// useEffect(() => {
|
||||
// if(actionData?.error) {
|
||||
// toastService.error(actionData.error);
|
||||
// }
|
||||
// }, [actionData?.error]);
|
||||
|
||||
// 判断是否正在提交表单
|
||||
const isSubmitting = navigation.state === "submitting";
|
||||
|
||||
@@ -76,7 +90,10 @@ export default function Login() {
|
||||
<h2 className="login-subtitle">用户登录</h2>
|
||||
<Form method="post" className="login-form">
|
||||
{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">
|
||||
@@ -89,7 +106,6 @@ export default function Login() {
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
className="form-input"
|
||||
placeholder="请输入用户名"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -103,7 +119,6 @@ export default function Login() {
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="form-input"
|
||||
placeholder="请输入密码"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -118,7 +133,7 @@ export default function Login() {
|
||||
required
|
||||
>
|
||||
<option value="common">普通用户</option>
|
||||
<option value="developer">开发者</option>
|
||||
<option value="developer">管理员</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
+10
-14
@@ -33,7 +33,7 @@ import { ReviewSettings } from "~/components/rules/new/ReviewSettings";
|
||||
import { ActionButtons } from "~/components/rules/new/ActionButtons";
|
||||
import { PageHeader } from "~/components/rules/new/PageHeader";
|
||||
import rulesStyles from "~/styles/rules.css?url";
|
||||
import { useNavigate, useLocation } from "@remix-run/react";
|
||||
import { useNavigate, useLocation, useRouteLoaderData } from "@remix-run/react";
|
||||
// 导入评查点模型定义和常量
|
||||
import type {
|
||||
EvaluationPoint,
|
||||
@@ -153,12 +153,15 @@ export default function RuleNew() {
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
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 [evaluationPointGroups, setEvaluationPointGroups] = useState<EvaluationPointGroup[]>([]);
|
||||
|
||||
// 检查用户是否为开发者角色
|
||||
const isDeveloper = userRole === 'developer';
|
||||
// 判断表单是否为只读模式
|
||||
const isReadOnly = userRole === 'common';
|
||||
|
||||
// 添加用于共享的字段数据状态
|
||||
const [extractionFields, setExtractionFields] = useState<string[]>([]);
|
||||
@@ -710,13 +713,6 @@ export default function RuleNew() {
|
||||
const id = searchParams.get('id');
|
||||
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') {
|
||||
setIsLoading(true);
|
||||
@@ -746,9 +742,9 @@ export default function RuleNew() {
|
||||
<div className="container">
|
||||
{/* 页面标题和右上角保存按钮 */}
|
||||
<PageHeader
|
||||
title={isEditMode ? "编辑评查点" : "新增评查点"}
|
||||
title={isEditMode ? (isReadOnly ? "查看评查点" : "编辑评查点") : "新增评查点"}
|
||||
onSave={handleSave}
|
||||
showSaveButton={isDeveloper}
|
||||
showSaveButton={!isReadOnly}
|
||||
/>
|
||||
|
||||
{/* 加载状态显示 */}
|
||||
@@ -817,7 +813,7 @@ export default function RuleNew() {
|
||||
onSave={handleSave}
|
||||
onSaveDraft={handleSaveDraft}
|
||||
isEditMode={isEditMode}
|
||||
showButtons={isDeveloper}
|
||||
showButtons={!isReadOnly}
|
||||
/>
|
||||
</div>
|
||||
</RuleContext.Provider>
|
||||
|
||||
@@ -53,6 +53,36 @@
|
||||
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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -31,5 +31,17 @@ export default defineConfig({
|
||||
open: true,
|
||||
allowedHosts: ['nas.7bm.co', 'localhost', '127.0.0.1'], // 允许的主机名列表1
|
||||
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'],
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user