优化数据隔离,进行权限控制
This commit is contained in:
+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>
|
||||
|
||||
Reference in New Issue
Block a user