添加交叉评查菜单页面,添加单点登录相关逻辑(待完善)
This commit is contained in:
@@ -7,6 +7,8 @@ import chatInputStyles from "~/styles/components/chat-with-llm/chat-input.css?ur
|
||||
import chatSidebarStyles from "~/styles/components/chat-with-llm/sidebar.css?url";
|
||||
import chatThoughtProcessStyles from "~/styles/components/chat-with-llm/thought-process.css?url";
|
||||
import chatMarkdownStyles from "~/styles/components/chat-with-llm/markdown.css?url";
|
||||
// 导入测试文件用于调试
|
||||
import "~/utils/dify-test.client";
|
||||
|
||||
export function links() {
|
||||
return [
|
||||
@@ -32,6 +34,11 @@ export const meta: MetaFunction = () => {
|
||||
/**
|
||||
* 聊天主页面
|
||||
* 实现单页面应用模式,所有会话切换都在同一页面内完成
|
||||
*
|
||||
* 调试说明:
|
||||
* - 打开浏览器开发者工具的控制台
|
||||
* - 输入 window.testDify() 可以测试Dify API连接
|
||||
* - 查看网络选项卡可以监控API请求
|
||||
*/
|
||||
export default function ChatWithLLMIndex() {
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,523 @@
|
||||
import { useState, useRef } from "react";
|
||||
import { type MetaFunction, type ActionFunctionArgs } from "@remix-run/node";
|
||||
import { Form, useNavigation } from "@remix-run/react";
|
||||
import { UploadArea, type UploadAreaRef } from "~/components/ui/UploadArea";
|
||||
import { Button } from "~/components/ui/Button";
|
||||
import { messageService } from "~/components/ui/MessageModal";
|
||||
import { toastService } from "~/components/ui/Toast";
|
||||
import crossCheckingUploadStyles from "~/styles/pages/cross-checking-upload.css?url";
|
||||
import {
|
||||
CaseType,
|
||||
CASE_TYPE_TO_TYPE_ID,
|
||||
type CrossCheckingUploadedFile,
|
||||
generateFileId,
|
||||
formatFileSize,
|
||||
batchUploadCrossCheckingFiles
|
||||
} from "~/api/cross-checking/cross-files-upload";
|
||||
|
||||
export const meta: MetaFunction = () => {
|
||||
return [
|
||||
{ title: "交叉评查上传 - 中国烟草AI合同及卷宗审核系统" },
|
||||
{ name: "description", content: "交叉评查案卷上传和任务创建" }
|
||||
];
|
||||
};
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: "交叉评查上传"
|
||||
};
|
||||
|
||||
export function links() {
|
||||
return [
|
||||
{ rel: "stylesheet", href: crossCheckingUploadStyles }
|
||||
];
|
||||
}
|
||||
|
||||
// 步骤枚举
|
||||
const STEPS = [
|
||||
{ id: 1, label: "创建任务" },
|
||||
{ id: 2, label: "创建评查小组" },
|
||||
{ id: 3, label: "选择卷宗" }
|
||||
];
|
||||
|
||||
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
const formData = await request.formData();
|
||||
const caseType = formData.get("caseType") as string;
|
||||
const uploadType = formData.get("uploadType") as string;
|
||||
|
||||
console.log("交叉评查上传:", { caseType, uploadType });
|
||||
|
||||
// 这里可以处理上传后的业务逻辑
|
||||
// 例如创建任务记录等
|
||||
return Response.json({ success: true, message: "文件上传成功" });
|
||||
};
|
||||
|
||||
export default function CrossCheckingUpload() {
|
||||
// 基础状态
|
||||
const [caseType, setCaseType] = useState<CaseType>(CaseType.ADMINISTRATIVE_PENALTY);
|
||||
const [currentStep] = useState(1);
|
||||
const navigation = useNavigation();
|
||||
|
||||
// 上传配置状态 - 设置默认值
|
||||
const [priority] = useState<string>("normal");
|
||||
const [documentNumber] = useState<string>("");
|
||||
const [remark] = useState<string>("");
|
||||
const [isTestDocument] = useState<boolean>(false);
|
||||
|
||||
// 文件管理状态
|
||||
const [singleFiles, setSingleFiles] = useState<CrossCheckingUploadedFile[]>([]);
|
||||
const [multipleFiles, setMultipleFiles] = useState<CrossCheckingUploadedFile[]>([]);
|
||||
const [uploadType, setUploadType] = useState<'none' | 'single' | 'multiple'>('none');
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
|
||||
// 引用
|
||||
const singleUploadRef = useRef<UploadAreaRef>(null);
|
||||
const multipleUploadRef = useRef<UploadAreaRef>(null);
|
||||
|
||||
// 获取当前typeId
|
||||
const currentTypeId = CASE_TYPE_TO_TYPE_ID[caseType];
|
||||
|
||||
// 处理案卷类型切换
|
||||
const handleCaseTypeChange = (type: CaseType) => {
|
||||
if (isUploading) {
|
||||
toastService.warning("上传进行中,无法切换案卷类型");
|
||||
return;
|
||||
}
|
||||
|
||||
setCaseType(type);
|
||||
// 清空已选择的文件和重置上传方式
|
||||
clearAllFiles();
|
||||
console.log("案卷类型切换为:", type, "typeId:", CASE_TYPE_TO_TYPE_ID[type]);
|
||||
};
|
||||
|
||||
// 清空所有文件
|
||||
const clearAllFiles = () => {
|
||||
setSingleFiles([]);
|
||||
setMultipleFiles([]);
|
||||
setUploadType('none');
|
||||
// 重置文件输入框
|
||||
singleUploadRef.current?.resetFileInput();
|
||||
multipleUploadRef.current?.resetFileInput();
|
||||
};
|
||||
|
||||
// 处理单案件文件选择
|
||||
const handleSingleFilesSelected = (files: FileList) => {
|
||||
if (uploadType === 'multiple') {
|
||||
toastService.warning("已选择多案件导入方式,无法选择单案件文件");
|
||||
return;
|
||||
}
|
||||
|
||||
const validFiles: CrossCheckingUploadedFile[] = [];
|
||||
let hasInvalidFiles = false;
|
||||
|
||||
Array.from(files).forEach(file => {
|
||||
if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) {
|
||||
validFiles.push({
|
||||
id: generateFileId(),
|
||||
file,
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
uploadType: 'single'
|
||||
});
|
||||
} else {
|
||||
hasInvalidFiles = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasInvalidFiles) {
|
||||
messageService.error('只能上传PDF格式的文件', {
|
||||
title: '文件类型错误',
|
||||
confirmText: '确定',
|
||||
});
|
||||
}
|
||||
|
||||
if (validFiles.length > 0) {
|
||||
setSingleFiles(prev => [...prev, ...validFiles]);
|
||||
setUploadType('single');
|
||||
console.log("选择单案件文件:", validFiles.length, "个");
|
||||
}
|
||||
};
|
||||
|
||||
// 处理多案件文件选择
|
||||
const handleMultipleFilesSelected = (files: FileList) => {
|
||||
if (uploadType === 'single') {
|
||||
toastService.warning("已选择单案件导入方式,无法选择多案件文件");
|
||||
return;
|
||||
}
|
||||
|
||||
const validFiles: CrossCheckingUploadedFile[] = [];
|
||||
let hasInvalidFiles = false;
|
||||
|
||||
Array.from(files).forEach(file => {
|
||||
const isZip = file.type === 'application/zip' ||
|
||||
file.type === 'application/x-zip-compressed' ||
|
||||
file.name.toLowerCase().endsWith('.zip');
|
||||
const isRar = file.type === 'application/x-rar-compressed' ||
|
||||
file.name.toLowerCase().endsWith('.rar');
|
||||
const is7z = file.type === 'application/x-7z-compressed' ||
|
||||
file.name.toLowerCase().endsWith('.7z');
|
||||
const isTar = file.type === 'application/x-tar' ||
|
||||
file.name.toLowerCase().endsWith('.tar');
|
||||
|
||||
if (isZip || isRar || is7z || isTar) {
|
||||
validFiles.push({
|
||||
id: generateFileId(),
|
||||
file,
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
uploadType: 'multiple'
|
||||
});
|
||||
} else {
|
||||
hasInvalidFiles = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasInvalidFiles) {
|
||||
messageService.error('只能上传ZIP或RAR格式的压缩文件', {
|
||||
title: '文件类型错误',
|
||||
confirmText: '确定',
|
||||
});
|
||||
}
|
||||
|
||||
if (validFiles.length > 0) {
|
||||
setMultipleFiles(prev => [...prev, ...validFiles]);
|
||||
setUploadType('multiple');
|
||||
console.log("选择多案件文件:", validFiles.length, "个");
|
||||
}
|
||||
};
|
||||
|
||||
// 删除单个文件
|
||||
const handleRemoveFile = (fileId: string, type: 'single' | 'multiple') => {
|
||||
if (isUploading) {
|
||||
toastService.warning("上传进行中,无法删除文件");
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'single') {
|
||||
setSingleFiles(prev => {
|
||||
const newFiles = prev.filter(f => f.id !== fileId);
|
||||
if (newFiles.length === 0) {
|
||||
setUploadType('none');
|
||||
singleUploadRef.current?.resetFileInput();
|
||||
}
|
||||
return newFiles;
|
||||
});
|
||||
} else {
|
||||
setMultipleFiles(prev => {
|
||||
const newFiles = prev.filter(f => f.id !== fileId);
|
||||
if (newFiles.length === 0) {
|
||||
setUploadType('none');
|
||||
multipleUploadRef.current?.resetFileInput();
|
||||
}
|
||||
return newFiles;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 清空文件列表
|
||||
const handleClearFiles = (type: 'single' | 'multiple') => {
|
||||
if (isUploading) {
|
||||
toastService.warning("上传进行中,无法清空文件");
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'single') {
|
||||
setSingleFiles([]);
|
||||
singleUploadRef.current?.resetFileInput();
|
||||
} else {
|
||||
setMultipleFiles([]);
|
||||
multipleUploadRef.current?.resetFileInput();
|
||||
}
|
||||
setUploadType('none');
|
||||
};
|
||||
|
||||
// 处理完成上传
|
||||
const handleCompleteUpload = async () => {
|
||||
const filesToUpload = uploadType === 'single' ? singleFiles : multipleFiles;
|
||||
|
||||
if (filesToUpload.length === 0) {
|
||||
toastService.error("请先选择要上传的文件");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsUploading(true);
|
||||
|
||||
try {
|
||||
console.log("开始批量上传文件:", filesToUpload.length, "个,案卷类型:", caseType, "typeId:", currentTypeId);
|
||||
|
||||
const result = await batchUploadCrossCheckingFiles(
|
||||
filesToUpload,
|
||||
currentTypeId,
|
||||
priority,
|
||||
documentNumber,
|
||||
remark,
|
||||
isTestDocument
|
||||
);
|
||||
|
||||
const { successes, failures } = result;
|
||||
|
||||
if (failures.length === 0) {
|
||||
// 全部成功
|
||||
toastService.success(`成功上传 ${successes.length} 个文件`);
|
||||
messageService.success(`文件上传完成!成功上传 ${successes.length} 个文件,现在可以进行下一步操作。`, {
|
||||
title: '上传成功',
|
||||
confirmText: '确定',
|
||||
onConfirm: () => {
|
||||
// 清空文件列表
|
||||
clearAllFiles();
|
||||
}
|
||||
});
|
||||
} else if (successes.length === 0) {
|
||||
// 全部失败
|
||||
toastService.error(`文件上传失败,共 ${failures.length} 个文件上传失败`);
|
||||
messageService.error(`所有文件上传失败。失败原因:${failures[0].error}`, {
|
||||
title: '上传失败',
|
||||
confirmText: '确定',
|
||||
});
|
||||
} else {
|
||||
// 部分成功
|
||||
toastService.warning(`部分文件上传成功:成功 ${successes.length} 个,失败 ${failures.length} 个`);
|
||||
messageService.warning(
|
||||
`部分文件上传完成:\n成功:${successes.length} 个文件\n失败:${failures.length} 个文件\n\n失败文件:\n${failures.map(f => `${f.file.name}: ${f.error}`).join('\n')}`,
|
||||
{
|
||||
title: '部分上传成功',
|
||||
confirmText: '确定',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("批量上传失败:", error);
|
||||
toastService.error("文件上传过程中发生错误");
|
||||
messageService.error(`文件上传失败:${error instanceof Error ? error.message : '未知错误'}`, {
|
||||
title: '上传失败',
|
||||
confirmText: '确定',
|
||||
});
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 检查是否可以完成
|
||||
const canComplete = (singleFiles.length > 0 || multipleFiles.length > 0) && !isUploading;
|
||||
const isSubmitting = navigation.state === "submitting";
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-8">
|
||||
<div className="max-w-6xl mx-auto px-4">
|
||||
{/* 步骤指示器 */}
|
||||
<div className="steps-indicator">
|
||||
{STEPS.map((step) => (
|
||||
<div
|
||||
key={step.id}
|
||||
className={`step-item ${step.id < currentStep ? 'completed' : ''}`}
|
||||
>
|
||||
<div className={`step-circle ${step.id <= currentStep ? 'active' : 'inactive'}`}>
|
||||
{step.id}
|
||||
</div>
|
||||
<div className="step-label">{step.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 案卷类型选择器 */}
|
||||
<div className="case-type-selector">
|
||||
<div className="case-type-options">
|
||||
<button
|
||||
type="button"
|
||||
className={`case-type-option ${caseType === CaseType.ADMINISTRATIVE_PENALTY ? 'active' : 'inactive'}`}
|
||||
onClick={() => handleCaseTypeChange(CaseType.ADMINISTRATIVE_PENALTY)}
|
||||
disabled={isUploading}
|
||||
>
|
||||
行政处罚
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`case-type-option ${caseType === CaseType.ADMINISTRATIVE_PERMIT ? 'active' : 'inactive'}`}
|
||||
onClick={() => handleCaseTypeChange(CaseType.ADMINISTRATIVE_PERMIT)}
|
||||
disabled={isUploading}
|
||||
>
|
||||
行政许可
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 文件上传区域 */}
|
||||
<Form method="post" encType="multipart/form-data">
|
||||
<input type="hidden" name="caseType" value={caseType} />
|
||||
<input type="hidden" name="uploadType" value={uploadType} />
|
||||
|
||||
<div className="upload-section">
|
||||
{/* 单案件导入 */}
|
||||
<div className="upload-item">
|
||||
<div className="upload-item-header">
|
||||
<i className="upload-item-icon ri-file-text-line"></i>
|
||||
<span>单案件导入</span>
|
||||
{uploadType === 'single' && singleFiles.length > 0 && (
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
icon="ri-delete-bin-line"
|
||||
onClick={() => handleClearFiles('single')}
|
||||
disabled={isUploading}
|
||||
>
|
||||
清空
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<UploadArea
|
||||
ref={singleUploadRef}
|
||||
onFilesSelected={handleSingleFilesSelected}
|
||||
className="custom-upload-area"
|
||||
accept=".pdf"
|
||||
multiple={true}
|
||||
icon="ri-file-upload-line"
|
||||
buttonText="选择文件"
|
||||
mainText="点击或拖拽文件到此区域上传"
|
||||
tipText={
|
||||
<div className="upload-tip-error">
|
||||
请上传案件相关PDF文件
|
||||
</div>
|
||||
}
|
||||
disabled={uploadType === 'multiple' || isUploading}
|
||||
/>
|
||||
|
||||
{/* 单案件文件列表 */}
|
||||
{singleFiles.length > 0 && (
|
||||
<div className="mt-4 space-y-2">
|
||||
<div className="text-sm font-medium text-gray-700">
|
||||
已选择 {singleFiles.length} 个文件:
|
||||
</div>
|
||||
<div className="max-h-32 overflow-y-auto space-y-1">
|
||||
{singleFiles.map((file) => (
|
||||
<div key={file.id} className="flex items-center justify-between bg-gray-50 p-2 rounded">
|
||||
<div className="flex items-center space-x-2 flex-1 min-w-0">
|
||||
<i className="ri-file-pdf-line text-red-500"></i>
|
||||
<span className="text-sm truncate">{file.name}</span>
|
||||
<span className="text-xs text-gray-500">{formatFileSize(file.size)}</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveFile(file.id, 'single')}
|
||||
className="text-red-500 hover:text-red-700 p-1"
|
||||
disabled={isUploading}
|
||||
>
|
||||
<i className="ri-close-line"></i>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 多案件导入 */}
|
||||
<div className="upload-item">
|
||||
<div className="upload-item-header">
|
||||
<i className="upload-item-icon ri-file-list-line"></i>
|
||||
<span>多案件导入</span>
|
||||
{uploadType === 'multiple' && multipleFiles.length > 0 && (
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
icon="ri-delete-bin-line"
|
||||
onClick={() => handleClearFiles('multiple')}
|
||||
disabled={isUploading}
|
||||
>
|
||||
清空
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<UploadArea
|
||||
ref={multipleUploadRef}
|
||||
onFilesSelected={handleMultipleFilesSelected}
|
||||
className="custom-upload-area"
|
||||
accept=".zip,.rar,.7z,.tar"
|
||||
multiple={false}
|
||||
icon="ri-folder-zip-line"
|
||||
buttonText="选择文件"
|
||||
mainText="点击或拖拽文件到此区域上传"
|
||||
tipText={
|
||||
<div className="upload-tip-error">
|
||||
请上传多个案件作为压缩包zip、rar、7z、tar文件
|
||||
</div>
|
||||
}
|
||||
disabled={uploadType === 'single' || isUploading}
|
||||
/>
|
||||
|
||||
{/* 多案件文件列表 */}
|
||||
{multipleFiles.length > 0 && (
|
||||
<div className="mt-4 space-y-2">
|
||||
<div className="text-sm font-medium text-gray-700">
|
||||
已选择 {multipleFiles.length} 个压缩包:
|
||||
</div>
|
||||
<div className="max-h-32 overflow-y-auto space-y-1">
|
||||
{multipleFiles.map((file) => (
|
||||
<div key={file.id} className="flex items-center justify-between bg-gray-50 p-2 rounded">
|
||||
<div className="flex items-center space-x-2 flex-1 min-w-0">
|
||||
<i className="ri-folder-zip-line text-orange-500"></i>
|
||||
<span className="text-sm truncate">{file.name}</span>
|
||||
<span className="text-xs text-gray-500">{formatFileSize(file.size)}</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveFile(file.id, 'multiple')}
|
||||
className="text-red-500 hover:text-red-700 p-1"
|
||||
disabled={isUploading}
|
||||
>
|
||||
<i className="ri-close-line"></i>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 完成按钮 */}
|
||||
<div className="complete-button-container">
|
||||
<button
|
||||
type="button"
|
||||
className="complete-button"
|
||||
disabled={!canComplete}
|
||||
onClick={handleCompleteUpload}
|
||||
>
|
||||
{isUploading || isSubmitting ? (
|
||||
<>
|
||||
<i className="ri-loader-4-line animate-spin mr-2"></i>
|
||||
上传中...
|
||||
</>
|
||||
) : (
|
||||
"完成"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
{/* 文件选择状态提示 */}
|
||||
{!canComplete && !isUploading && (
|
||||
<div className="text-center mt-4 text-gray-500 text-sm">
|
||||
请至少选择一种导入方式的文件
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 上传进度提示 */}
|
||||
{isUploading && (
|
||||
<div className="text-center mt-4">
|
||||
<div className="bg-blue-50 p-4 rounded-md border border-blue-100">
|
||||
<div className="flex items-center justify-center text-blue-800 mb-2">
|
||||
<i className="ri-loader-4-line animate-spin text-xl mr-2"></i>
|
||||
<span className="font-medium">正在上传文件...</span>
|
||||
</div>
|
||||
<p className="text-sm text-blue-700">
|
||||
正在上传 {uploadType === 'single' ? singleFiles.length : multipleFiles.length} 个文件,请稍候
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
import { Outlet } from "react-router-dom";
|
||||
import {type MetaFunction} from "@remix-run/node";
|
||||
import { Outlet } from "@remix-run/react";
|
||||
import { type MetaFunction } from "@remix-run/node";
|
||||
|
||||
export const meta: MetaFunction = () => {
|
||||
return [
|
||||
{title: "文档列表 - 中国烟草AI合同及卷宗审核系统"},
|
||||
{name: "documents", content: "文档列表,新增,修改"}
|
||||
{ title: "文档列表 - 中国烟草AI合同及卷宗审核系统" },
|
||||
{ name: "documents", content: "文档列表,新增,修改" }
|
||||
]
|
||||
}
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: "文档列表"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文档列表路由布局
|
||||
|
||||
+1
-1
@@ -306,7 +306,7 @@ export default function Home() {
|
||||
</div>
|
||||
<div className="ml-1">
|
||||
<p className="text-sm font-medium mb-0">{userRole === 'developer' ? '系统管理员' : '普通用户'}</p>
|
||||
<p className="text-xs text-gray-500 mb-0">{userRole === 'developer' ? '超级管理员' : '标准权限'}</p>
|
||||
{/* <p className="text-xs text-gray-500 mb-0">{userRole === 'developer' ? '超级管理员' : '标准权限'}</p> */}
|
||||
</div>
|
||||
</div>
|
||||
{/* 登出操作 */}
|
||||
|
||||
+45
-4
@@ -1,9 +1,9 @@
|
||||
import { useEffect } from "react";
|
||||
import { useSearchParams } from "@remix-run/react";
|
||||
import { type MetaFunction, type LoaderFunctionArgs, redirect } from "@remix-run/node";
|
||||
import { useSearchParams, Form } from "@remix-run/react";
|
||||
import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs, redirect } from "@remix-run/node";
|
||||
import { OAuthClient } from "~/utils/oauth-client";
|
||||
import { OAUTH_CONFIG } from "~/config/api-config";
|
||||
import { getUserSession, getSession } from "~/root";
|
||||
import { getUserSession, getSession, createUserSession } from "~/root";
|
||||
import styles from "~/styles/pages/login.css?url";
|
||||
|
||||
export const links = () => [
|
||||
@@ -39,6 +39,23 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
});
|
||||
}
|
||||
|
||||
// 处理表单提交的action函数
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
const formData = await request.formData();
|
||||
const intent = formData.get("intent");
|
||||
|
||||
if (intent === "temp_admin_login") {
|
||||
// 获取重定向目标
|
||||
const session = await getSession(request);
|
||||
const redirectTo = session.get("redirectTo") || "/";
|
||||
|
||||
// 创建管理员会话
|
||||
return createUserSession(true, 'developer', redirectTo);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function Login() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const error = searchParams.get("error");
|
||||
@@ -131,10 +148,34 @@ export default function Login() {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 临时管理员登录区域 */}
|
||||
<div className="temp-login-section">
|
||||
<div className="section-divider">
|
||||
<span>或</span>
|
||||
</div>
|
||||
|
||||
<Form method="post" className="temp-login-form">
|
||||
<input type="hidden" name="intent" value="temp_admin_login" />
|
||||
<button
|
||||
type="submit"
|
||||
className="temp-admin-login-button"
|
||||
>
|
||||
<i className="ri-admin-line"></i>
|
||||
临时管理员登录
|
||||
</button>
|
||||
<div className="temp-login-tips">
|
||||
<p>
|
||||
<i className="ri-alert-line"></i>
|
||||
仅供开发测试使用,将以管理员身份登录
|
||||
</p>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="login-footer">
|
||||
<p>© 2024 中国烟草 版权所有</p>
|
||||
<p>© 2025 中国烟草 版权所有</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user