From 057563ba5e729b0a7c04dcf8732e0e70566e63cd Mon Sep 17 00:00:00 2001 From: yorn <1057707203@qq.com> Date: Tue, 3 Jun 2025 15:17:09 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=95=B0=E6=8D=AE=E9=9A=94?= =?UTF-8?q?=E7=A6=BB=EF=BC=8C=E8=BF=9B=E8=A1=8C=E6=9D=83=E9=99=90=E6=8E=A7?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/layout/Layout.tsx | 15 +++++ app/components/layout/Sidebar.tsx | 106 ++++++++++++++++-------------- app/root.tsx | 1 + app/routes/_index.tsx | 25 ++++--- app/routes/files.upload.tsx | 19 +++++- app/routes/home.tsx | 83 +++++++++++++++++++---- app/routes/login.tsx | 23 +++++-- app/routes/rules-new.tsx | 24 +++---- app/styles/pages/login.css | 30 +++++++++ vite.config.ts | 12 ++++ 10 files changed, 244 insertions(+), 94 deletions(-) diff --git a/app/components/layout/Layout.tsx b/app/components/layout/Layout.tsx index 2af5764..f48fc32 100644 --- a/app/components/layout/Layout.tsx +++ b/app/components/layout/Layout.tsx @@ -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); diff --git a/app/components/layout/Sidebar.tsx b/app/components/layout/Sidebar.tsx index 73a40ee..f87fd90 100644 --- a/app/components/layout/Sidebar.tsx +++ b/app/components/layout/Sidebar.tsx @@ -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>({}); const [currentApp, setCurrentApp] = useState(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 (
-
+
{ + navigate('/'); + }} + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + navigate('/'); + } + }} + > 智慧法务 {!collapsed &&

智慧法务

}
@@ -309,9 +316,6 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = 'contract {APP_NAME_MAP[currentApp] || '合同管理'}
-
- 当前模块: {APP_NAME_MAP[currentApp] || '合同管理'} -
)} diff --git a/app/root.tsx b/app/root.tsx index 44c05d6..f3b7f80 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -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" } diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index d444af1..74a1bed 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -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(); 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() { {currentDateTime.date} {currentDateTime.time}
用户头像 - 系统管理员 + {userRole === 'developer' ? '系统管理员' : '普通用户'}
diff --git a/app/routes/login.tsx b/app/routes/login.tsx index c8f4b6b..79da34e 100644 --- a/app/routes/login.tsx +++ b/app/routes/login.tsx @@ -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(); 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() {

用户登录

{actionData?.error && ( -
{actionData.error}
+
+
+
{actionData.error}
+
)}
@@ -89,7 +106,6 @@ export default function Login() { onChange={(e) => setUsername(e.target.value)} className="form-input" placeholder="请输入用户名" - required />
@@ -103,7 +119,6 @@ export default function Login() { onChange={(e) => setPassword(e.target.value)} className="form-input" placeholder="请输入密码" - required /> @@ -118,7 +133,7 @@ export default function Login() { required > - + diff --git a/app/routes/rules-new.tsx b/app/routes/rules-new.tsx index 0ecb9db..47b420e 100644 --- a/app/routes/rules-new.tsx +++ b/app/routes/rules-new.tsx @@ -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('new'); - const [userRole, setUserRole] = useState('common'); + // 从root路由获取用户角色,而不是从sessionStorage + const rootData = useRouteLoaderData("root") as { userRole: UserRole }; + const userRole = rootData?.userRole || 'common'; + const [formData, setFormData] = useState({}); const [evaluationPointGroups, setEvaluationPointGroups] = useState([]); - // 检查用户是否为开发者角色 - const isDeveloper = userRole === 'developer'; + // 判断表单是否为只读模式 + const isReadOnly = userRole === 'common'; // 添加用于共享的字段数据状态 const [extractionFields, setExtractionFields] = useState([]); @@ -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() {
{/* 页面标题和右上角保存按钮 */} {/* 加载状态显示 */} @@ -817,7 +813,7 @@ export default function RuleNew() { onSave={handleSave} onSaveDraft={handleSaveDraft} isEditMode={isEditMode} - showButtons={isDeveloper} + showButtons={!isReadOnly} />
diff --git a/app/styles/pages/login.css b/app/styles/pages/login.css index c92b107..7f63ea0 100644 --- a/app/styles/pages/login.css +++ b/app/styles/pages/login.css @@ -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; diff --git a/vite.config.ts b/vite.config.ts index 6e074cb..b7cd2cd 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -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'], }, });