优化首页的ui交互,解决打包生产环境下写入sessionStorage错误,优化权限路由的跳转问题
This commit is contained in:
@@ -49,8 +49,8 @@ export function ContractSearchHero({
|
|||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
padding: '60px 0',
|
padding: '60px 0',
|
||||||
background: 'linear-gradient(135deg, rgba(0, 104, 74, 0.05) 0%, rgba(0, 104, 74, 0.02) 100%)',
|
background: 'linear-gradient(135deg, rgba(0, 104, 74, 0.05) 0%, rgba(0, 104, 74, 0.02) 100%)',
|
||||||
borderRadius: '16px',
|
borderRadius: '5px',
|
||||||
marginBottom: '32px'
|
// marginBottom: '32px'
|
||||||
}}>
|
}}>
|
||||||
<h1 style={{
|
<h1 style={{
|
||||||
fontSize: '32px',
|
fontSize: '32px',
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ const APP_NAME_MAP: Record<string, string> = {
|
|||||||
|
|
||||||
// 应用模块图标映射
|
// 应用模块图标映射
|
||||||
const APP_ICON_MAP: Record<string, string> = {
|
const APP_ICON_MAP: Record<string, string> = {
|
||||||
'contract': 'ri-file-list-2-fill',
|
'contract': '/images/icon_hetong.png',
|
||||||
'record': 'ri-folder-shared-fill',
|
'record': '/images/icon_anjuan.png',
|
||||||
'model': 'ri-robot-2-fill'
|
'model': '/images/icon_assistant.png'
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Sidebar({ onToggle, collapsed, userRole, selectedApp = '' }: SidebarProps) {
|
export function Sidebar({ onToggle, collapsed, userRole, selectedApp = '' }: SidebarProps) {
|
||||||
@@ -367,7 +367,7 @@ export function Sidebar({ onToggle, collapsed, userRole, selectedApp = '' }: Sid
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<i className={`${APP_ICON_MAP[currentApp] || ''} mr-2 text-xl`}></i>
|
<img src={APP_ICON_MAP[currentApp] || ''} alt={APP_NAME_MAP[currentApp] || ''} className="w-6 h-6 mr-2" />
|
||||||
<span className="font-medium">{APP_NAME_MAP[currentApp] || ''}</span>
|
<span className="font-medium">{APP_NAME_MAP[currentApp] || ''}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+30
-11
@@ -38,19 +38,21 @@ export const developerOnlyPaths = [
|
|||||||
'/settings',
|
'/settings',
|
||||||
'/config-lists',
|
'/config-lists',
|
||||||
'/document-types',
|
'/document-types',
|
||||||
'/prompts'
|
'/prompts',
|
||||||
];
|
];
|
||||||
|
|
||||||
// 创建基于Cookie的会话存储
|
// 创建基于Cookie的会话存储
|
||||||
// 在实际应用中,应该使用环境变量来设置密钥
|
// 在实际应用中,应该使用环境变量来设置密钥
|
||||||
const sessionStorage = createCookieSessionStorage({
|
export const sessionStorage = createCookieSessionStorage({
|
||||||
cookie: {
|
cookie: {
|
||||||
name: "__session",
|
name: "__lgsession",
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
path: "/",
|
path: "/",
|
||||||
sameSite: "lax",
|
sameSite: "lax",
|
||||||
secrets: ["s3cr3t"], // 应该从环境变量读取
|
secrets: ["s3cr3t"], // 应该从环境变量读取
|
||||||
secure: process.env.NODE_ENV === "production",
|
// secure: process.env.NODE_ENV === "production",
|
||||||
|
maxAge: 60 * 60 * 24 * 1, // 1天
|
||||||
|
secure: false, // 开发环境中禁用secure
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -63,9 +65,18 @@ export async function getSession(request: Request) {
|
|||||||
// 获取用户登录状态
|
// 获取用户登录状态
|
||||||
export async function getUserSession(request: Request) {
|
export async function getUserSession(request: Request) {
|
||||||
const session = await getSession(request);
|
const session = await getSession(request);
|
||||||
|
const isAuthenticated = session.get("isAuthenticated") === true;
|
||||||
|
const userRole = session.get("userRole") || 'common' as UserRole;
|
||||||
|
|
||||||
|
console.log("获取会话状态:",
|
||||||
|
// "Cookie:", request.headers.get("Cookie"),
|
||||||
|
"是否认证:", isAuthenticated,
|
||||||
|
"用户角色:", userRole
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isAuthenticated: session.get("isAuthenticated") === true,
|
isAuthenticated,
|
||||||
userRole: session.get("userRole") || 'common' as UserRole
|
userRole
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,10 +85,15 @@ export async function createUserSession(isAuthenticated: boolean, userRole: User
|
|||||||
const session = await sessionStorage.getSession();
|
const session = await sessionStorage.getSession();
|
||||||
session.set("isAuthenticated", isAuthenticated);
|
session.set("isAuthenticated", isAuthenticated);
|
||||||
session.set("userRole", userRole);
|
session.set("userRole", userRole);
|
||||||
console.log("session-----", session.get("userRole"));
|
|
||||||
|
const cookie = await sessionStorage.commitSession(session);
|
||||||
|
console.log("创建会话 - 设置Cookie:", !!cookie);
|
||||||
|
console.log("创建会话 - 用户角色:", userRole);
|
||||||
|
console.log("创建会话 - 重定向到:", redirectTo);
|
||||||
|
|
||||||
return redirect(redirectTo, {
|
return redirect(redirectTo, {
|
||||||
headers: {
|
headers: {
|
||||||
"Set-Cookie": await sessionStorage.commitSession(session),
|
"Set-Cookie": cookie,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -117,15 +133,17 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
|
|
||||||
// 获取用户会话
|
// 获取用户会话
|
||||||
const { isAuthenticated, userRole } = await getUserSession(request);
|
const { isAuthenticated, userRole } = await getUserSession(request);
|
||||||
// console.log("Auth status:", { isAuthenticated, userRole, pathname });
|
console.log("是否公开路径:", isPublicPath, "是否已认证:", isAuthenticated);
|
||||||
|
|
||||||
// 如果访问需要认证的路径但未登录,重定向到登录页
|
// 如果访问需要认证的路径但未登录,重定向到登录页
|
||||||
if (!isPublicPath && !isAuthenticated) {
|
if (!isPublicPath && !isAuthenticated) {
|
||||||
|
console.log("未认证,需要重定向到登录页");
|
||||||
// 保存请求的URL,以便登录后重定向回来
|
// 保存请求的URL,以便登录后重定向回来
|
||||||
const session = await getSession(request);
|
const session = await getSession(request);
|
||||||
|
|
||||||
// 如果路径是/home,则将重定向目标设置为/
|
// 如果路径是/home,则将重定向目标设置为/
|
||||||
const redirectTarget = pathname === "/home" ? "/" : pathname;
|
const redirectTarget = pathname !== "/" ? "/" : pathname;
|
||||||
|
// const redirectTarget = pathname === "home" ? "/" : pathname;
|
||||||
// 保存重定向目标
|
// 保存重定向目标
|
||||||
session.set("redirectTo", redirectTarget);
|
session.set("redirectTo", redirectTarget);
|
||||||
|
|
||||||
@@ -138,12 +156,13 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
|
|
||||||
// 如果已登录且访问登录页,重定向到首页
|
// 如果已登录且访问登录页,重定向到首页
|
||||||
if (pathname === "/login" && isAuthenticated) {
|
if (pathname === "/login" && isAuthenticated) {
|
||||||
// console.log("Already authenticated, redirecting from login to /");
|
console.log("已认证,重定向到首页");
|
||||||
return redirect("/");
|
return redirect("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查访问权限 - 如果是common用户访问了开发者专属页面,重定向到首页
|
// 检查访问权限 - 如果是common用户访问了开发者专属页面,重定向到首页
|
||||||
if (userRole === 'common' && developerOnlyPaths.some(path => pathname.startsWith(path))) {
|
if (userRole === 'common' && developerOnlyPaths.some(path => pathname.startsWith(path))) {
|
||||||
|
console.log("用户没有访问权限,重定向到首页");
|
||||||
return redirect("/");
|
return redirect("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ export default function Index() {
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
aria-label="合同管理"
|
aria-label="合同管理"
|
||||||
>
|
>
|
||||||
<i className="ri-file-list-2-fill text-[3rem] text-[#269b6c]"></i>
|
<img src="/images/icon_hetong.png" alt="合同管理" className="w-12 h-12 mx-1" />
|
||||||
<span className="module-name">合同管理</span>
|
<span className="module-name">合同管理</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ export default function Index() {
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
aria-label="案卷智能评查"
|
aria-label="案卷智能评查"
|
||||||
>
|
>
|
||||||
<i className="ri-folder-shared-fill text-[3rem] text-[#269b6c]"></i>
|
<img src="/images/icon_anjuan.png" alt="案卷智能评查" className="w-12 h-12" />
|
||||||
<span className="module-name">案卷智能评查</span>
|
<span className="module-name">案卷智能评查</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ export default function Index() {
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
aria-label="智慧法务大模型"
|
aria-label="智慧法务大模型"
|
||||||
>
|
>
|
||||||
<i className="ri-robot-2-fill text-[3rem] text-[#269b6c]"></i>
|
<img src="/images/icon_assistant.png" alt="智慧法务大模型" className="w-12 h-12" />
|
||||||
<span className="module-name">智慧法务大模型</span>
|
<span className="module-name">智慧法务大模型</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+24
-24
@@ -1,8 +1,8 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Form, useActionData, useNavigation } from "@remix-run/react";
|
import { useActionData } from "@remix-run/react";
|
||||||
import { type MetaFunction, type ActionFunctionArgs, redirect, type LoaderFunctionArgs } from "@remix-run/node";
|
import { type MetaFunction, type ActionFunctionArgs, redirect, type LoaderFunctionArgs } from "@remix-run/node";
|
||||||
import styles from "~/styles/pages/login.css?url";
|
import styles from "~/styles/pages/login.css?url";
|
||||||
import { createUserSession, getUserSession, getSession, type UserRole } from "~/root";
|
import { getUserSession, getSession, type UserRole, sessionStorage } from "~/root";
|
||||||
|
|
||||||
export const links = () => [
|
export const links = () => [
|
||||||
{ rel: "stylesheet", href: styles }
|
{ rel: "stylesheet", href: styles }
|
||||||
@@ -31,22 +31,30 @@ export async function action({ request }: ActionFunctionArgs) {
|
|||||||
|
|
||||||
if (userRole === 'developer') {
|
if (userRole === 'developer') {
|
||||||
if (username !== 'admin' || password !== 'admin') {
|
if (username !== 'admin' || password !== 'admin') {
|
||||||
// toastService.error("管理员用户名或密码错误");
|
|
||||||
return Response.json({ error: "管理员用户名或密码错误" });
|
return Response.json({ error: "管理员用户名或密码错误" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在实际应用中,这里应该是对用户名和密码的验证逻辑
|
|
||||||
// 简化起见,我们直接视为登录成功
|
|
||||||
|
|
||||||
// 获取session中存储的重定向URL,如果没有则默认到/
|
// 获取session中存储的重定向URL,如果没有则默认到/
|
||||||
const session = await getSession(request);
|
const session = await getSession(request);
|
||||||
// 查看session中存储的redirectTo值
|
// 查看session中存储的redirectTo值
|
||||||
const redirectTo = session.get("redirectTo") || "/";
|
const redirectTo = session.get("redirectTo") || "/";
|
||||||
// console.log("登录后重定向到:", redirectTo);
|
console.log("登录后重定向到:", redirectTo);
|
||||||
|
|
||||||
// 创建登录会话并重定向
|
// 创建会话cookie
|
||||||
return createUserSession(true, userRole, redirectTo);
|
const newSession = await sessionStorage.getSession();
|
||||||
|
newSession.set("isAuthenticated", true);
|
||||||
|
newSession.set("userRole", userRole);
|
||||||
|
const cookie = await sessionStorage.commitSession(newSession);
|
||||||
|
|
||||||
|
console.log("设置cookie:", !!cookie);
|
||||||
|
|
||||||
|
// 使用新方法进行重定向
|
||||||
|
return redirect(redirectTo, {
|
||||||
|
headers: {
|
||||||
|
"Set-Cookie": cookie
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载器,获取当前会话状态
|
// 加载器,获取当前会话状态
|
||||||
@@ -66,18 +74,7 @@ export default function Login() {
|
|||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [userRole, setUserRole] = useState<UserRole>("common");
|
const [userRole, setUserRole] = useState<UserRole>("common");
|
||||||
const actionData = useActionData<typeof action>();
|
const actionData = useActionData<typeof action>();
|
||||||
const navigation = useNavigation();
|
|
||||||
|
|
||||||
// 使用 useEffect 确保错误提示只显示一次
|
|
||||||
// useEffect(() => {
|
|
||||||
// if(actionData?.error) {
|
|
||||||
// toastService.error(actionData.error);
|
|
||||||
// }
|
|
||||||
// }, [actionData?.error]);
|
|
||||||
|
|
||||||
// 判断是否正在提交表单
|
|
||||||
const isSubmitting = navigation.state === "submitting";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="login-page">
|
<div className="login-page">
|
||||||
<div className="login-container">
|
<div className="login-container">
|
||||||
@@ -88,7 +85,11 @@ export default function Login() {
|
|||||||
|
|
||||||
<div className="login-form-container">
|
<div className="login-form-container">
|
||||||
<h2 className="login-subtitle">用户登录</h2>
|
<h2 className="login-subtitle">用户登录</h2>
|
||||||
<Form method="post" className="login-form">
|
<form
|
||||||
|
action="/login"
|
||||||
|
method="post"
|
||||||
|
className="login-form"
|
||||||
|
>
|
||||||
{actionData?.error && (
|
{actionData?.error && (
|
||||||
<div className="error-message-container">
|
<div className="error-message-container">
|
||||||
<div className="error-icon"><i className="ri-error-warning-line"></i></div>
|
<div className="error-icon"><i className="ri-error-warning-line"></i></div>
|
||||||
@@ -140,11 +141,10 @@ export default function Login() {
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="login-button"
|
className="login-button"
|
||||||
disabled={isSubmitting}
|
|
||||||
>
|
>
|
||||||
{isSubmitting ? "登录中..." : "登录"}
|
登录
|
||||||
</button>
|
</button>
|
||||||
</Form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="login-footer">
|
<div className="login-footer">
|
||||||
|
|||||||
@@ -14,15 +14,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.contract-template-search {
|
.contract-template-search {
|
||||||
padding: 24px;
|
/* padding: 24px; */
|
||||||
background: var(--gradient-bg);
|
background: var(--gradient-bg);
|
||||||
min-height: 100vh;
|
min-height: calc(100vh - 90px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.contract-search-results {
|
.contract-search-results {
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
background: var(--gradient-bg);
|
background: var(--gradient-bg);
|
||||||
min-height: 100vh;
|
min-height: calc(100vh - 90px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 搜索英雄区域 */
|
/* 搜索英雄区域 */
|
||||||
@@ -158,6 +158,7 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
padding:8px;
|
||||||
margin-top: 40px;
|
margin-top: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.15);
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-container {
|
.logo-container {
|
||||||
@@ -74,7 +75,6 @@
|
|||||||
|
|
||||||
/* 主要内容区域 */
|
/* 主要内容区域 */
|
||||||
.index-main-content {
|
.index-main-content {
|
||||||
border-radius: 0.5rem 0.5rem 0 0;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
transform: translateY(-7rem);
|
transform: translateY(-7rem);
|
||||||
}
|
}
|
||||||
.welcome-text {
|
.welcome-text {
|
||||||
font-size: 1.75rem;
|
font-size: 1.95rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #333;
|
color: #333;
|
||||||
margin-bottom: 5rem;
|
margin-bottom: 5rem;
|
||||||
@@ -115,9 +115,12 @@
|
|||||||
.module-card {
|
.module-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 250px;
|
justify-content: flex-start;
|
||||||
padding: 2rem 1.5rem;
|
gap: 1.5rem;
|
||||||
background: linear-gradient(to bottom, #ebebeb, #ffffff);
|
padding: 0 2rem;
|
||||||
|
height: 136px;
|
||||||
|
width: 290px;
|
||||||
|
background: linear-gradient(180deg, #ebf1f7 0%, #ffffff 100%);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||||
transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s;
|
transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s;
|
||||||
@@ -144,8 +147,7 @@
|
|||||||
} */
|
} */
|
||||||
|
|
||||||
.module-name {
|
.module-name {
|
||||||
margin-left: 1rem;
|
font-size: 1.25rem;
|
||||||
font-size: 1.125rem;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
Reference in New Issue
Block a user