From 1b0108e5188600f7b6500b14edabcddd3187b511 Mon Sep 17 00:00:00 2001 From: yorn <1057707203@qq.com> Date: Wed, 26 Nov 2025 18:05:15 +0800 Subject: [PATCH] =?UTF-8?q?fix:=201.=20=E7=B3=BB=E7=BB=9F=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E5=85=A5=E5=8F=A3=E8=BF=9B=E6=9D=A5=E5=8F=AA=E4=BC=9A?= =?UTF-8?q?=E8=B7=B3=E8=BD=AC=E5=88=B0=E6=8B=A5=E6=9C=89=E6=9D=83=E9=99=90?= =?UTF-8?q?=E8=AE=BF=E9=97=AE=E7=9A=84=E9=A1=B5=E9=9D=A2=E3=80=82=202.=20?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=99=BB=E5=BD=95=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/routes/_index.tsx | 33 ++++++++++++++++++---- app/routes/api.pdf-proxy.tsx | 52 ++++++++++++++++++++++++++++------ app/routes/documents.edit.tsx | 10 ++++--- app/routes/login.tsx | 34 ++++++++++++++-------- app/routes/settings.tsx | 25 +++++++++++++++++ app/styles/pages/login.css | 53 +++++++++++++++++++++++++++++++++++ 6 files changed, 179 insertions(+), 28 deletions(-) create mode 100644 app/routes/settings.tsx diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index c216e8a..4e79559 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -52,23 +52,36 @@ export async function loader({ request }: LoaderFunctionArgs) { // 🔑 检查用户是否有系统设置权限 let hasSettingsAccess = false; let hasCrossCheckingAccess = false; + let settingsChildren: { path: string; title: string }[] = []; if (userRole && frontendJWT) { const { getUserRoutesByRole } = await import('~/api/auth/user-routes'); const routesResult = await getUserRoutesByRole(userRole, frontendJWT, true); // includeHidden=true if (routesResult.success && routesResult.data) { - // 检查是否存在顶级路由 '/settings' - hasSettingsAccess = routesResult.data.some(route => route.path === '/settings'); + // 查找 '/settings' 路由及其子路由 + const settingsRoute = routesResult.data.find(route => route.path === '/settings'); + if (settingsRoute) { + hasSettingsAccess = true; + // 提取子路由信息(仅 path 和 title) + if (settingsRoute.children && settingsRoute.children.length > 0) { + settingsChildren = settingsRoute.children.map(child => ({ + path: child.path, + title: child.title + })); + } + } + // 检查是否存在顶级路由 '/cross-checking' hasCrossCheckingAccess = routesResult.data.some(route => route.path === '/cross-checking'); // console.log(`🔑 [Index Loader] 用户${hasSettingsAccess ? '有' : '没有'}系统设置权限`); + // console.log(`🔑 [Index Loader] 系统设置子路由数量: ${settingsChildren.length}`); // console.log(`🔑 [Index Loader] 用户${hasCrossCheckingAccess ? '有' : '没有'}交叉评查权限`); } } // 返回用户信息、入口模块和权限给客户端 - return Response.json({ userRole, userInfo, entryModules, hasSettingsAccess, hasCrossCheckingAccess }); + return Response.json({ userRole, userInfo, entryModules, hasSettingsAccess, hasCrossCheckingAccess, settingsChildren }); } export default function Index() { @@ -238,6 +251,14 @@ export default function Index() { // 处理进入系统设置 const handleEnterSettings = () => { + // 🔑 检查是否有系统设置的子路由 + if (!loaderData.settingsChildren || loaderData.settingsChildren.length === 0) { + // 没有子路由,显示错误提示 + toastService.error('您无权限访问或页面丢失'); + console.warn('⚠️ [Index] 系统设置没有可访问的子路由'); + return; + } + if (typeof window !== 'undefined') { // 🔑 设置标志:表示用户通过系统设置入口进入 sessionStorage.setItem('settingsMode', 'true'); @@ -249,8 +270,10 @@ export default function Index() { sessionStorage.removeItem('crossCheckingMode'); } - // 跳转到系统设置的默认页面 - navigate('/rule-groups'); + // 跳转到第一个子路由 + const firstChildPath = loaderData.settingsChildren[0].path; + console.log(`📌 [Index] 系统设置:跳转到第一个子路由 ${firstChildPath}`); + navigate(firstChildPath); }; // 处理进入交叉评查 diff --git a/app/routes/api.pdf-proxy.tsx b/app/routes/api.pdf-proxy.tsx index 3755cc7..d37458d 100644 --- a/app/routes/api.pdf-proxy.tsx +++ b/app/routes/api.pdf-proxy.tsx @@ -11,9 +11,16 @@ export async function loader({ request }: LoaderFunctionArgs) { // 获取 JWT token const { frontendJWT } = await getUserSession(request); - // 从查询参数获取文件路径 + // 从查询参数获取文件路径和预览标志 const url = new URL(request.url); const filePath = url.searchParams.get("path"); + const isPreview = url.searchParams.get("preview") === "true"; + + console.log("📄 [PDF Proxy] 请求参数:", { + path: filePath, + preview: url.searchParams.get("preview"), + isPreview: isPreview + }); if (!filePath) { return new Response("缺少文件路径参数", { status: 400 }); @@ -38,13 +45,42 @@ export async function loader({ request }: LoaderFunctionArgs) { // 获取文件内容 const blob = await response.blob(); - // 返回文件,保持原始的 Content-Type - return new Response(blob, { - headers: { - 'Content-Type': response.headers.get('Content-Type') || 'application/pdf', - 'Cache-Control': 'public, max-age=3600', // 缓存1小时 - }, - }); + // 从路径中提取文件名 + const fileName = filePath.split('/').pop() || 'document.pdf'; + + // 判断文件类型 + const fileExtension = fileName.split('.').pop()?.toLowerCase(); + let contentType = response.headers.get('Content-Type') || 'application/octet-stream'; + + // 🔑 根据文件扩展名强制设置正确的 Content-Type + if (fileExtension === 'pdf') { + contentType = 'application/pdf'; + } else if (fileExtension === 'doc' || fileExtension === 'docx') { + contentType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; + } + + // 设置响应头 + const headers: HeadersInit = { + 'Content-Type': contentType, + 'Cache-Control': 'public, max-age=3600', // 缓存1小时 + }; + + // 根据 preview 参数决定是预览还是下载 + // 默认行为:强制下载(保持向后兼容性) + if (isPreview) { + // 在浏览器中预览 + headers['Content-Disposition'] = `inline; filename="${encodeURIComponent(fileName)}"`; + console.log("📄 [PDF Proxy] 设置为预览模式 (inline)"); + } else { + // 强制下载(默认行为) + headers['Content-Disposition'] = `attachment; filename="${encodeURIComponent(fileName)}"`; + console.log("📄 [PDF Proxy] 设置为下载模式 (attachment)"); + } + + console.log("📄 [PDF Proxy] 响应头:", headers); + + // 返回文件 + return new Response(blob, { headers }); } catch (error) { console.error('PDF 代理错误:', error); diff --git a/app/routes/documents.edit.tsx b/app/routes/documents.edit.tsx index 4b9f62e..ce84f20 100644 --- a/app/routes/documents.edit.tsx +++ b/app/routes/documents.edit.tsx @@ -417,7 +417,7 @@ export default function DocumentEdit() { // 下载文档 const downloadDocument = async () => { try { - // 使用 PDF 代理路由获取文件,自动添加 JWT 认证 + // 使用 PDF 代理路由获取文件,自动添加 JWT 认证(默认行为是下载) const downloadUrl = `/api/pdf-proxy?path=${encodeURIComponent(documentData.path)}`; // 使用fetch获取文件内容 @@ -457,8 +457,8 @@ export default function DocumentEdit() { // 在新窗口打开文档预览 const openPreview = () => { - // 使用 PDF 代理路由,自动添加 JWT 认证 - const previewUrl = `/api/pdf-proxy?path=${encodeURIComponent(documentData.path)}`; + // 使用 PDF 代理路由,自动添加 JWT 认证,添加 preview 参数实现预览 + const previewUrl = `/api/pdf-proxy?path=${encodeURIComponent(documentData.path)}&preview=true`; window.open(previewUrl, '_blank'); }; @@ -622,7 +622,7 @@ export default function DocumentEdit() { {/* 文档预览 */} - @@ -662,6 +662,8 @@ export default function DocumentEdit() { )} + } + {/* 修改历史 */} diff --git a/app/routes/login.tsx b/app/routes/login.tsx index 993a056..541a65d 100644 --- a/app/routes/login.tsx +++ b/app/routes/login.tsx @@ -213,6 +213,7 @@ export default function Login() { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [passwordLoginError, setPasswordLoginError] = useState(null); + const [showPassword, setShowPassword] = useState(false); // 从 loaderData 中获取错误信息 const oauthError = loaderData?.flashError; @@ -466,17 +467,28 @@ export default function Login() { 密码 - setPassword(e.target.value)} - className="form-input" - placeholder="请输入密码" - disabled={isLoading} - required - /> + + setPassword(e.target.value)} + className="form-input" + placeholder="请输入密码" + disabled={isLoading} + required + /> + setShowPassword(!showPassword)} + tabIndex={-1} + aria-label={showPassword ? "隐藏密码" : "显示密码"} + > + + + { + return [ + { title: "系统设置 - 中国烟草AI合同及卷宗审核系统" }, + { + name: "settings", + content: "评查点分组,入口模块管理,角色权限管理,文档类型管理,提示词管理" + } + ]; +}; + +export const handle = { + breadcrumb: "系统设置", + to: "/settings" // 指定面包屑点击后跳转的路径 +}; + +/** + * 规则管理路由布局 + */ +export default function SettingsLayout() { + return ; +} \ No newline at end of file diff --git a/app/styles/pages/login.css b/app/styles/pages/login.css index 22dc4ad..67d250f 100644 --- a/app/styles/pages/login.css +++ b/app/styles/pages/login.css @@ -235,6 +235,50 @@ color: #9ca3af; } +/* 密码输入框包装器 */ +.password-input-wrapper { + position: relative; + width: 100%; +} + +.password-input-wrapper .form-input { + padding-right: 3rem; +} + +/* 密码切换按钮 */ +.password-toggle-button { + position: absolute; + right: 0; + top: 0; + height: 100%; + width: 3rem; + display: flex; + align-items: center; + justify-content: center; + background: transparent; + border: none; + color: #6b7280; + cursor: pointer; + transition: color 0.2s ease; + font-family: 'remixicon' !important; + font-style: normal; + -webkit-font-smoothing: antialiased; +} + +.password-toggle-button:hover { + color: #015c42; +} + +.password-toggle-button:focus { + outline: none; + color: #015c42; +} + +.password-toggle-button i { + font-size: 1.25rem; + pointer-events: none; +} + .admin-login-button { display: flex; align-items: center; @@ -562,6 +606,15 @@ .admin-login-text:hover { color: #93c5fd; } + + .password-toggle-button { + color: #9ca3af; + } + + .password-toggle-button:hover, + .password-toggle-button:focus { + color: #60a5fa; + } } \ No newline at end of file