优化数据隔离,进行权限控制
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 toggleSidebar = () => {
|
||||||
const newState = !sidebarCollapsed;
|
const newState = !sidebarCollapsed;
|
||||||
setSidebarCollapsed(newState);
|
setSidebarCollapsed(newState);
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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="智慧法务大模型"
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -29,6 +29,13 @@ export async function action({ request }: ActionFunctionArgs) {
|
|||||||
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
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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'],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user