fix: 1. 系统设置入口进来只会跳转到拥有权限访问的页面。

2. 优化登录样式
This commit is contained in:
2025-11-26 18:05:15 +08:00
parent efbf78246f
commit 1b0108e518
6 changed files with 179 additions and 28 deletions
+28 -5
View File
@@ -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);
};
// 处理进入交叉评查
+44 -8
View File
@@ -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);
+6 -4
View File
@@ -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() {
</Card>
{/* 文档预览 */}
<Card
{ false && <Card
title="文档预览"
className="mb-4"
>
@@ -662,6 +662,8 @@ export default function DocumentEdit() {
)}
</div>
</Card>
}
{/* 修改历史 */}
<Card title="修改历史" className="hidden">
+23 -11
View File
@@ -213,6 +213,7 @@ export default function Login() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [passwordLoginError, setPasswordLoginError] = useState<string | null>(null);
const [showPassword, setShowPassword] = useState(false);
// 从 loaderData 中获取错误信息
const oauthError = loaderData?.flashError;
@@ -466,17 +467,28 @@ export default function Login() {
<div className="form-group">
<label htmlFor="password" className="form-label"></label>
<input
type="password"
id="password"
name="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="form-input"
placeholder="请输入密码"
disabled={isLoading}
required
/>
<div className="password-input-wrapper">
<input
type={showPassword ? "text" : "password"}
id="password"
name="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="form-input"
placeholder="请输入密码"
disabled={isLoading}
required
/>
<button
type="button"
className="password-toggle-button"
onClick={() => setShowPassword(!showPassword)}
tabIndex={-1}
aria-label={showPassword ? "隐藏密码" : "显示密码"}
>
<i className={showPassword ? "ri-eye-off-line" : "ri-eye-line"}></i>
</button>
</div>
</div>
<button
+25
View File
@@ -0,0 +1,25 @@
import { Outlet } from "@remix-run/react";
import { type MetaFunction } from "@remix-run/node";
export const meta: MetaFunction = () => {
return [
{ title: "系统设置 - 中国烟草AI合同及卷宗审核系统" },
{
name: "settings",
content: "评查点分组,入口模块管理,角色权限管理,文档类型管理,提示词管理"
}
];
};
export const handle = {
breadcrumb: "系统设置",
to: "/settings" // 指定面包屑点击后跳转的路径
};
/**
* 规则管理路由布局
*/
export default function SettingsLayout() {
return <Outlet />;
}
+53
View File
@@ -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;
}
}