fix: 1.接入ai_suggestion.

2. 接入合同起草功能。
This commit is contained in:
2025-12-05 00:04:45 +08:00
parent eca98fc540
commit 33f10896a0
29 changed files with 3184 additions and 981 deletions
+10 -10
View File
@@ -83,7 +83,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
// console.log(`🔑 [Index Loader] 用户${hasSettingsAccess ? '有' : '没有'}系统设置权限`);
// console.log(`🔑 [Index Loader] 系统设置子路由数量: ${settingsChildren.length}`);
// console.log(`🔑 [Index Loader] 用户${hasCrossCheckingAccess ? '有' : '没有'}交叉评查权限`);
// console.log(`🔑 [Index Loader] 用户${hasChatLLMAccess ? '有' : '没有'}智慧法务大模型权限`);
// console.log(`🔑 [Index Loader] 用户${hasChatLLMAccess ? '有' : '没有'}智慧法务助手权限`);
}
}
@@ -176,8 +176,8 @@ export default function Index() {
// 提取文档类型 IDs
const typeIds = module.document_types?.map(dt => dt.id) || [];
// 🔑 验证文档类型(智慧法务大模型除外)
if (module.name !== '智慧法务大模型' && typeIds.length === 0) {
// 🔑 验证文档类型(智慧法务助手除外)
if (module.name !== '智慧法务助手' && typeIds.length === 0) {
toastService.error('该入口尚未关联文档类型,无法进入');
console.warn('⚠️ [Index] 模块未关联文档类型:', module.name);
return; // 阻止进入
@@ -207,11 +207,11 @@ export default function Index() {
// 合同相关模块 → 跳转到合同模板搜索
targetPath = '/contract-template/search';
// console.log('📌 [Index] 合同模块,跳转到:', targetPath);
} else if (module.name === '智慧法务大模型') {
// 智慧法务大模型 → 跳转到 AI 对话
} else if (module.name === '智慧法务助手') {
// 智慧法务助手 → 跳转到 AI 对话
targetPath = '/chat-with-llm/chat';
sessionStorage.setItem('selectedModulePicPath', '/images/icon_assistant.png')
// console.log('📌 [Index] 智慧法务大模型,跳转到:', targetPath);
// console.log('📌 [Index] 智慧法务助手,跳转到:', targetPath);
} else {
// console.log('📌 [Index] 其他模块,跳转到:', targetPath);
}
@@ -370,17 +370,17 @@ export default function Index() {
{loaderData.entryModules && loaderData.entryModules.length > 0 ? (
<>
{loaderData.entryModules.map((module) => {
// 判断是否为智慧法务大模型,如果是且有交叉评查权限,则在其之前插入交叉评查卡片
const isLLMModule = module.name === '智慧法务大模型';
// 判断是否为智慧法务助手,如果是且有交叉评查权限,则在其之前插入交叉评查卡片
const isLLMModule = module.name === '智慧法务助手';
// 🔑 如果是智慧法务大模型且用户没有访问权限,则不渲染该模块
// 🔑 如果是智慧法务助手且用户没有访问权限,则不渲染该模块
if (isLLMModule && !loaderData.hasChatLLMAccess) {
return null;
}
return (
<React.Fragment key={module.id}>
{/* 在智慧法务大模型之前插入交叉评查入口 */}
{/* 在智慧法务助手之前插入交叉评查入口 */}
{isLLMModule && loaderData.hasCrossCheckingAccess && (
<div
className="module-card"
+111 -37
View File
@@ -1,10 +1,13 @@
import type { MetaFunction, LoaderFunctionArgs } from '@remix-run/node';
import { useLoaderData, useNavigate } from '@remix-run/react';
import type { MetaFunction, LoaderFunctionArgs, ActionFunctionArgs } from '@remix-run/node';
import { redirect } from '@remix-run/node';
import { useLoaderData, useNavigate, useSubmit } from '@remix-run/react';
import { useState } from 'react';
import { getContractTemplate } from '~/api/contract-template/templates';
import type { ContractTemplate } from '~/api/contract-template/templates';
import styles from '~/styles/pages/contract-template.css?url';
import filePreviewStyles from '~/styles/components/file-preview-isolation.css?url';
import { getUserSession } from '~/api/login/auth.server';
import { createDraftContract } from '~/api/contracts/draft-service.server';
// 导入FilePreview组件
import { FilePreview } from '~/components/reviews';
@@ -36,26 +39,26 @@ export const handle = {
export async function loader({ params, request }: LoaderFunctionArgs) {
const templateId = params.id!;
// 获取 JWT
const { frontendJWT } = await getUserSession(request);
const jwt = frontendJWT || undefined;
try {
const response = await getContractTemplate(templateId, jwt);
if (response.error) {
throw new Response(response.error, { status: response.status || 404 });
}
if (!response.data) {
throw new Response('模板未找到', { status: 404 });
}
// 添加调试信息
// console.log('模板详情数据:', response.data);
// console.log('分类信息:', response.data.category);
return { template: response.data };
} catch (error) {
console.error('加载模板详情失败:', error);
@@ -63,9 +66,61 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
}
}
/**
* Action 函数:处理起草合同请求
*/
export async function action({ request, params }: ActionFunctionArgs) {
const templateId = parseInt(params.id || '0');
if (!templateId) {
return Response.json({ error: '模板ID无效' }, { status: 400 });
}
// 获取用户信息和JWT
const { userInfo, frontendJWT } = await getUserSession(request);
if (!userInfo?.sub) {
return Response.json({ error: '未登录' }, { status: 401 });
}
try {
// 解析表单数据
const formData = await request.formData();
const title = formData.get('title') as string;
const draftFilePath = formData.get('draftFilePath') as string | null;
if (!title) {
return Response.json({ error: '标题不能为空' }, { status: 400 });
}
// 创建草稿记录(到时候可以换成接口,使用接口来在minio中生成备份文件:备份文件可以用时间戳+uuid来保证唯一性。)
// const draft = await createDraftContract(
// {
// templateId,
// title,
// draftFilePath: draftFilePath || undefined
// },
// parseInt(userInfo.sub),
// draftFilePath || undefined,
// frontendJWT || undefined
// );
// 重定向到草稿编辑页面
// return redirect(`/contract-draft/${draft.id}`);
return redirect(`/contract-draft/1`);
} catch (error) {
console.error('[Template Detail] 创建草稿失败:', error);
return Response.json(
{ error: error instanceof Error ? error.message : '创建草稿失败' },
{ status: 500 }
);
}
}
export default function ContractTemplateDetail() {
const { template }: { template: ContractTemplate } = useLoaderData<typeof loader>();
const navigate = useNavigate();
const submit = useSubmit();
const [isCreatingDraft, setIsCreatingDraft] = useState(false);
// 注释掉收藏功能
// const [isFavorited, setIsFavorited] = useState(false);
@@ -75,6 +130,7 @@ export default function ContractTemplateDetail() {
// 使用统一的下载方法(与 rules-files.tsx 相同)
const handleDownload = async () => {
if (!template.file_path) {
toastService.error('文件路径不存在,无法下载');
return;
@@ -84,6 +140,7 @@ export default function ContractTemplateDetail() {
// 使用axios封装的下载方法
const blob = await downloadFile(template.file_path);
// 创建Blob URL
const blobUrl = URL.createObjectURL(blob);
@@ -120,6 +177,29 @@ export default function ContractTemplateDetail() {
}
};
// 起草合同
const handleStartDraft = () => {
if (isCreatingDraft) return;
// 生成默认标题
// const defaultTitle = `${template.title}-${new Date().toLocaleDateString('zh-CN').replace(/\//g, '')}`;
// // 提示用户输入标题
// const title = prompt('请输入合同标题:', defaultTitle);
// if (!title) return;
setIsCreatingDraft(true);
// 使用 Remix 的 submit 提交表单
const formData = new FormData();
// formData.append('title', title.trim());
formData.append('title', '买卖合同-拟起草合同');
// 可选:如果需要复制文件,可以先调用文件复制服务,然后传递 draftFilePath
// formData.append('draftFilePath', draftFilePath);
submit(formData, { method: 'post' });
};
/* const handleFavorite = () => {
setIsFavorited(!isFavorited);
console.log('收藏状态:', !isFavorited);
@@ -247,15 +327,32 @@ export default function ContractTemplateDetail() {
</div>
<div className="detail-actions flex gap-3">
<button
<button
className="detail-btn primary bg-primary text-white px-6 py-3 rounded-lg flex items-center gap-2 hover:bg-primary-hover"
onClick={handleStartDraft}
disabled={isCreatingDraft}
>
{isCreatingDraft ? (
<>
<i className="ri-loader-4-line animate-spin"></i>
...
</>
) : (
<>
<i className="ri-edit-line"></i>
</>
)}
</button>
<button
className="detail-btn secondary bg-white border border-gray-200 px-6 py-3 rounded-lg flex items-center gap-2 hover:border-primary"
onClick={handleDownload}
>
<i className="ri-download-line"></i>
使
</button>
{template.pdf_file_path && (
<button
<button
className="detail-btn secondary bg-white border border-gray-200 px-6 py-3 rounded-lg flex items-center gap-2 hover:border-primary"
onClick={handlePreview}
>
@@ -336,38 +433,15 @@ export default function ContractTemplateDetail() {
<div className="content-section mb-8" id="template-preview">
<h3 className="section-title text-xl font-semibold mb-4"></h3>
<div className="border border-gray-200 rounded-lg overflow-hidden">
{/* 使用更强的样式隔离 */}
<div
className="file-preview-isolation"
style={{
// 使用CSS变量避免继承
'--font-family': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
'--font-size': '14px',
'--line-height': '1.5',
'--text-color': '#333333',
'--bg-color': '#ffffff',
// 强制重置所有可能的样式
all: 'unset',
display: 'block',
fontFamily: 'var(--font-family)',
fontSize: 'var(--font-size)',
lineHeight: 'var(--line-height)',
color: 'var(--text-color)',
backgroundColor: 'var(--bg-color)',
width: '100%',
minHeight: '600px',
position: 'relative',
isolation: 'isolate', // 创建新的层叠上下文
contain: 'layout style', // CSS容器化
zIndex: 0
} as React.CSSProperties}
<div
className="file-preview-isolation w-full"
>
<FilePreview
fileContent={fileContent}
activeReviewPointResultId={null}
targetPage={undefined}
isStructuredView={false}
isTemplate={true}
/>
</div>
</div>
+151 -24
View File
@@ -223,9 +223,11 @@ export default function DocumentsIndex() {
const [showAttachmentUpload, setShowAttachmentUpload] = useState<boolean>(false);
const [showTemplateUpload, setShowTemplateUpload] = useState<boolean>(false);
const [selectedDocumentId, setSelectedDocumentId] = useState<number | null>(null);
const [selectedDocumentName, setSelectedDocumentName] = useState<string | null>(null);
const [selectedDocumentVersion, setSelectedDocumentVersion] = useState<number | null>(null);
const [attachmentFiles, setAttachmentFiles] = useState<File[]>([]);
const [templateFile, setTemplateFile] = useState<File | null>(null);
const [attachmentMergeMode, setAttachmentMergeMode] = useState<'overwrite' | 'new'>('overwrite');
const [attachmentMergeMode, setAttachmentMergeMode] = useState<'overwrite' | 'new'>('new');
const [attachmentRemark, setAttachmentRemark] = useState<string>("");
const [attachmentUploading, setAttachmentUploading] = useState<boolean>(false);
const [templateUploading, setTemplateUploading] = useState<boolean>(false);
@@ -356,6 +358,38 @@ export default function DocumentsIndex() {
}
}, [searchParams, fetchData, documentTypeIds]);
// 监听 documents 数据变化,自动修正不一致的展开状态
useEffect(() => {
if (documents.length === 0) return;
const newExpandedRows = new Set(expandedRows);
let hasChanges = false;
// 检查每个展开的行
expandedRows.forEach(docId => {
const doc = documents.find(d => d.id === docId);
// 如果文档不存在或没有历史版本数据,自动折叠
if (!doc || !doc.historyVersions || doc.historyVersions.length === 0) {
console.warn(`自动折叠文档 ${docId}:数据不完整`);
newExpandedRows.delete(docId);
hasChanges = true;
}
});
// 如果有变化,更新状态
if (hasChanges) {
setExpandedRows(newExpandedRows);
// 同时更新 documents 中的 isExpanded 状态
setDocuments(prevDocs =>
prevDocs.map(d =>
newExpandedRows.has(d.id) ? { ...d, isExpanded: true } : { ...d, isExpanded: false }
)
);
}
}, [documents, expandedRows]);
// 使用并更新缓存数据
useEffect(() => {
// 如果有缓存数据,先显示缓存,再在后台加载新数据
@@ -881,6 +915,8 @@ export default function DocumentsIndex() {
setAttachmentRemark("");
setShowAttachmentUpload(false);
setSelectedDocumentId(null);
setSelectedDocumentName(null);
setSelectedDocumentVersion(null);
// 刷新文档列表
if (documentTypeIds && documentTypeIds.length > 0) {
@@ -951,6 +987,8 @@ export default function DocumentsIndex() {
setTemplateFile(null);
setShowTemplateUpload(false);
setSelectedDocumentId(null);
setSelectedDocumentName(null);
setSelectedDocumentVersion(null);
// 刷新文档列表
if (documentTypeIds && documentTypeIds.length > 0) {
@@ -981,11 +1019,28 @@ export default function DocumentsIndex() {
)
);
} else {
// 展开前检查是否有历史版本数据
const hasHistoryData = doc.historyVersions && doc.historyVersions.length > 0;
const hasHistoryCount = doc.historyCount && doc.historyCount > 0;
// 如果有历史版本计数但没有数据,可能是数据加载失败
if (hasHistoryCount && !hasHistoryData) {
console.warn(`文档 ${doc.id}${doc.historyCount} 个历史版本,但数据为空`);
toastService.warning('历史版本数据加载失败,请刷新页面重试');
return;
}
// 如果没有历史版本,不允许展开
if (!hasHistoryCount) {
console.log(`文档 ${doc.id} 没有历史版本`);
return;
}
// 展开:显示历史版本
newExpanded.add(doc.id);
setExpandedRows(newExpanded);
// 更新展开状态(历史版本数据已经在主数据中了)
// 更新展开状态
setDocuments(prevDocs =>
prevDocs.map(d =>
d.id === doc.id ? { ...d, isExpanded: true } : d
@@ -1077,6 +1132,36 @@ export default function DocumentsIndex() {
<i className="ri-download-line"></i>
</button>
{parentDoc.type === '1' && historyDoc.fileStatus === 'Processed' && (
<>
<button
type="button"
className="text-xs px-2 py-1 h-7 mr-1 hover:underline hover:text-primary"
onClick={() => {
setSelectedDocumentId(historyDoc.id);
setSelectedDocumentName(historyDoc.name);
setSelectedDocumentVersion(historyDoc.versionNumber || null);
setShowAttachmentUpload(true);
}}
>
<i className="ri-attachment-line"></i>
</button>
<button
type="button"
className="text-xs px-2 py-1 h-7 mr-1 text-gray-500 hover:underline hover:text-gray-700"
onClick={() => {
setSelectedDocumentId(historyDoc.id);
setSelectedDocumentName(historyDoc.name);
setSelectedDocumentVersion(historyDoc.versionNumber || null);
setShowTemplateUpload(true);
}}
>
<i className="ri-file-copy-line"></i>
</button>
</>
)}
<button
type="button"
className="text-xs px-2 py-1 h-7 text-error hover:underline hover:text-red-700"
@@ -1294,22 +1379,26 @@ export default function DocumentsIndex() {
</button>
{record.type === '1' && record.fileStatus === 'Processed' && (
<>
<button
<button
type="button"
className="text-xs px-2 py-1 h-7 mr-1 hover:underline hover:text-primary"
onClick={() => {
setSelectedDocumentId(record.id);
setSelectedDocumentName(record.name);
setSelectedDocumentVersion(record.historyCount !== undefined && record.historyCount > 0 ? record.historyCount + 1 : null);
setShowAttachmentUpload(true);
}}
>
<i className="ri-attachment-line"></i>
</button>
<button
<button
type="button"
className="text-xs px-2 py-1 h-7 mr-1 text-gray-500 hover:underline hover:text-gray-700"
onClick={() => {
setSelectedDocumentId(record.id);
setSelectedDocumentName(record.name);
setSelectedDocumentVersion(record.historyCount !== undefined && record.historyCount > 0 ? record.historyCount + 1 : null);
setShowTemplateUpload(true);
}}
>
@@ -1531,22 +1620,34 @@ export default function DocumentsIndex() {
))}
</tr>
{/* 历史版本行 */}
{doc.isExpanded && doc.historyVersions && doc.historyVersions.length > 0 && (
{doc.isExpanded && (
<>
{doc.historyVersions.map((historyDoc) => renderHistoryRow(historyDoc, doc))}
{/* 正在加载历史版本 */}
{loadingHistory.has(doc.id) ? (
<tr key={`loading-${doc.id}`} className="history-row">
<td colSpan={columns.length} className="px-4 py-3">
<div className="version-loading">
<i className="ri-loader-4-line"></i>
...
</div>
</td>
</tr>
) : doc.historyVersions && doc.historyVersions.length > 0 ? (
/* 显示历史版本数据 */
doc.historyVersions.map((historyDoc) => renderHistoryRow(historyDoc, doc))
) : (
/* 数据为空时的提示 */
<tr key={`empty-${doc.id}`} className="history-row">
<td colSpan={columns.length} className="px-4 py-3">
<div className="text-center text-gray-500 text-sm py-2">
<i className="ri-information-line mr-1"></i>
</div>
</td>
</tr>
)}
</>
)}
{/* 正在加载历史版本 */}
{doc.isExpanded && loadingHistory.has(doc.id) && (
<tr key={`loading-${doc.id}`} className="history-row">
<td colSpan={columns.length} className="px-4 py-3">
<div className="version-loading">
<i className="ri-loader-4-line"></i>
...
</div>
</td>
</tr>
)}
</>
))}
</tbody>
@@ -1573,6 +1674,8 @@ export default function DocumentsIndex() {
onClick={() => {
setShowAttachmentUpload(false);
setSelectedDocumentId(null);
setSelectedDocumentName(null);
setSelectedDocumentVersion(null);
setAttachmentFiles([]);
setAttachmentRemark("");
}}
@@ -1587,6 +1690,8 @@ export default function DocumentsIndex() {
onClick={() => {
setShowAttachmentUpload(false);
setSelectedDocumentId(null);
setSelectedDocumentName(null);
setSelectedDocumentVersion(null);
setAttachmentFiles([]);
setAttachmentRemark("");
}}
@@ -1600,10 +1705,18 @@ export default function DocumentsIndex() {
{/* 文档信息 */}
<div className="bg-gray-50 p-3 rounded">
<p className="text-sm text-gray-600">
ID: <span className="font-medium">{selectedDocumentId}</span>
{/* 目标文档ID: <span className="font-medium">{selectedDocumentId}</span> */}
: <span className="font-medium">{selectedDocumentName}</span>
{selectedDocumentVersion && (
<span className="ml-2 text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded">
v{selectedDocumentVersion}
</span>
)}
</p>
<p className="text-xs text-gray-500 mt-1">
.pdf.docxZIPRAR格式ZIP/RAR内需要保证文件格式一致
.pdf.docxZIPRAR格式
<i className="ri-information-line mr-1"></i>
ZIP/RAR内需要保证文件格式一致
</p>
</div>
@@ -1624,7 +1737,7 @@ export default function DocumentsIndex() {
<label htmlFor="attachment-file-input" className="cursor-pointer">
<i className="ri-attachment-line text-3xl text-gray-400 mb-2 block"></i>
<p className="text-sm text-gray-600"></p>
<p className="text-xs text-gray-500 mt-1">PDFWordZIPRAR格式</p>
<p className="text-xs text-gray-500 mt-1">.pdf.docx.zip.rar格式</p>
</label>
</div>
{attachmentFiles.length > 0 && (
@@ -1650,7 +1763,7 @@ export default function DocumentsIndex() {
</label>
<div className="space-y-2">
<label className="flex items-center">
{/* <label className="flex items-center">
<input
type="radio"
name="mergeMode"
@@ -1660,7 +1773,7 @@ export default function DocumentsIndex() {
className="mr-2"
/>
<span className="text-sm">覆盖原文档(推荐)</span>
</label>
</label> */}
<label className="flex items-center">
<input
type="radio"
@@ -1696,6 +1809,8 @@ export default function DocumentsIndex() {
onClick={() => {
setShowAttachmentUpload(false);
setSelectedDocumentId(null);
setSelectedDocumentName(null);
setSelectedDocumentVersion(null);
setAttachmentFiles([]);
setAttachmentRemark("");
}}
@@ -1723,11 +1838,13 @@ export default function DocumentsIndex() {
onClick={() => {
setShowTemplateUpload(false);
setSelectedDocumentId(null);
setSelectedDocumentName(null);
setSelectedDocumentVersion(null);
setTemplateFile(null);
}}
>
<div
className="bg-white rounded-lg p-6 w-full max-w-lg mx-4"
className="bg-white rounded-lg p-6 w-full max-w-xl mx-4"
onClick={(e) => e.stopPropagation()}
>
<div className="flex justify-between items-center mb-4">
@@ -1736,6 +1853,8 @@ export default function DocumentsIndex() {
onClick={() => {
setShowTemplateUpload(false);
setSelectedDocumentId(null);
setSelectedDocumentName(null);
setSelectedDocumentVersion(null);
setTemplateFile(null);
}}
className="text-gray-400 hover:text-gray-600"
@@ -1748,7 +1867,13 @@ export default function DocumentsIndex() {
{/* 文档信息 */}
<div className="bg-gray-50 p-3 rounded">
<p className="text-sm text-gray-600">
ID: <span className="font-medium">{selectedDocumentId}</span>
{/* 目标文档ID: <span className="font-medium">{selectedDocumentId}</span> */}
: <span className="font-medium">{selectedDocumentName}</span>
{selectedDocumentVersion && (
<span className="ml-2 text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded">
v{selectedDocumentVersion}
</span>
)}
</p>
<p className="text-xs text-gray-500 mt-1">
.pdf.docx格式
@@ -1794,6 +1919,8 @@ export default function DocumentsIndex() {
onClick={() => {
setShowTemplateUpload(false);
setSelectedDocumentId(null);
setSelectedDocumentName(null);
setSelectedDocumentVersion(null);
setTemplateFile(null);
}}
disabled={templateUploading}
+28 -14
View File
@@ -336,8 +336,9 @@ export default function FilesUpload() {
// 附件追加状态
const [showAttachmentUpload, setShowAttachmentUpload] = useState<boolean>(false);
const [selectedDocumentId, setSelectedDocumentId] = useState<number | null>(null);
const [selectedDocumentName, setSelectedDocumentName] = useState<string | null>(null);
const [attachmentFiles, setAttachmentFiles] = useState<File[]>([]);
const [attachmentMergeMode, setAttachmentMergeMode] = useState<'overwrite' | 'new'>('overwrite');
const [attachmentMergeMode, setAttachmentMergeMode] = useState<'overwrite' | 'new'>('new');
const [attachmentRemark, setAttachmentRemark] = useState<string>("");
const [attachmentUploading, setAttachmentUploading] = useState<boolean>(false);
@@ -887,7 +888,7 @@ export default function FilesUpload() {
if (files.length > 0) {
// 检查主文件类型
const selectedDocument = queueFiles.find(doc => doc.id === selectedDocumentId);
const isMainFileDocx = selectedDocument?.path.toLowerCase().endsWith('.docx');
const isMainFileDocx = selectedDocument?.path?.toLowerCase().endsWith('.docx');
// 验证文件类型,支持PDF、Word、ZIP、RAR
const validFiles: File[] = [];
@@ -899,7 +900,7 @@ export default function FilesUpload() {
const isPdf = file.type === 'application/pdf' || fileName.endsWith('.pdf');
const isValidType =
isPdf ||
file.type === 'application/msword' || fileName.endsWith('.doc') ||
// file.type === 'application/msword' || fileName.endsWith('.doc') ||
file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || fileName.endsWith('.docx') ||
file.type === 'application/zip' || fileName.endsWith('.zip') ||
file.type === 'application/x-rar-compressed' || fileName.endsWith('.rar');
@@ -973,6 +974,7 @@ export default function FilesUpload() {
setAttachmentRemark("");
setShowAttachmentUpload(false);
setSelectedDocumentId(null);
setSelectedDocumentName(null)
// 刷新文档列表
await filterDocuments(documentTypeIds);
@@ -1042,6 +1044,7 @@ export default function FilesUpload() {
setTemplateFile(null);
setShowTemplateUpload(false);
setSelectedDocumentId(null);
setSelectedDocumentName(null)
// 刷新文档列表
await filterDocuments(documentTypeIds);
@@ -2114,6 +2117,7 @@ export default function FilesUpload() {
icon="ri-attachment-line"
onClick={() => {
setSelectedDocumentId(record.id);
setSelectedDocumentName(record.name)
setShowAttachmentUpload(true);
}}
className="text-xs px-2 py-1 h-7"
@@ -2126,6 +2130,7 @@ export default function FilesUpload() {
icon="ri-file-copy-line"
onClick={() => {
setSelectedDocumentId(record.id);
setSelectedDocumentName(record.name)
setShowTemplateUpload(true);
}}
className="text-xs px-2 py-1 h-7"
@@ -2290,7 +2295,7 @@ export default function FilesUpload() {
ref={contractMainFileRef}
multiple={false}
accept=".pdf,.docx"
tipText="请上传合同主文件,格式:PDF/Word"
tipText="请上传合同主文件,格式:.pdf/.docx"
mainText="上传合同主文件"
buttonText="选择主文件"
icon="ri-file-text-line"
@@ -2327,7 +2332,7 @@ export default function FilesUpload() {
ref={contractAttachmentFileRef}
multiple={false}
accept=".pdf,.docx"
tipText="请上传合同附件,格式:PDF/Word"
tipText="请上传合同附件,格式:.pdf/.docx"
mainText="上传合同附件"
buttonText="选择附件"
icon="ri-file-copy-line"
@@ -2362,7 +2367,7 @@ export default function FilesUpload() {
onFilesSelected={handleContractTemplateFilesSelected}
multiple={false}
accept=".pdf,.docx"
tipText="请上传合同模板,格式:PDF/Word"
tipText="请上传合同模板,格式:.pdf/.docx"
mainText="上传合同模板"
buttonText="选择模板"
icon="ri-file-copy-line"
@@ -2600,11 +2605,13 @@ export default function FilesUpload() {
if (e.target === e.currentTarget) {
setShowAttachmentUpload(false);
setSelectedDocumentId(null);
setSelectedDocumentName(null)
setAttachmentFiles([]);
setAttachmentRemark("");
setAttachmentMergeMode('overwrite');
}
}}
}
}
>
<div className="bg-white rounded-lg p-6 w-full max-w-2xl mx-4 max-h-[90vh] overflow-y-auto">
<div className="flex justify-between items-center mb-4">
@@ -2613,6 +2620,7 @@ export default function FilesUpload() {
onClick={() => {
setShowAttachmentUpload(false);
setSelectedDocumentId(null);
setSelectedDocumentName(null)
setAttachmentFiles([]);
setAttachmentRemark("");
}}
@@ -2626,10 +2634,11 @@ export default function FilesUpload() {
{/* 文档信息 */}
<div className="bg-gray-50 p-3 rounded">
<p className="text-sm text-gray-600">
ID: <span className="font-medium">{selectedDocumentId}</span>
{/* 目标文档ID: <span className="font-medium">{selectedDocumentId}</span> */}
: <span className="font-medium">{selectedDocumentName}</span>
</p>
<p className="text-xs text-gray-500 mt-1">
PDFWordZIPRAR格式ZIP/RAR内仅合并其中的PDF文件
.pdf.docx.zip.rar格式<i className="ri-information-2-line mr-1"></i>ZIP/RAR内需要保证文件格式一致
</p>
</div>
@@ -2642,7 +2651,7 @@ export default function FilesUpload() {
onFilesSelected={handleAttachmentFilesSelected}
multiple={true}
accept=".pdf,.docx,.zip,.rar"
tipText="支持PDF、Word、ZIP、RAR格式,可多选"
tipText="支持.pdf、.docx、.zip、.rar格式,可多选"
mainText="选择附件文件"
buttonText="选择文件"
icon="ri-attachment-line"
@@ -2652,7 +2661,7 @@ export default function FilesUpload() {
<p className="text-sm text-green-600 mb-2">
<i className="ri-checkbox-circle-line"></i> {attachmentFiles.length}
</p>
<div className="space-y-1 max-h-32 overflow-y-auto">
<div className="space-y-1 max-h-32 overflow-y-auto">
{attachmentFiles.map((file, index) => (
<div key={index} className="text-xs text-gray-600 bg-gray-50 p-2 rounded">
<i className="ri-file-line mr-1"></i>
@@ -2670,7 +2679,7 @@ export default function FilesUpload() {
</label>
<div className="space-y-2">
<label className="flex items-center">
{/* <label className="flex items-center">
<input
type="radio"
name="mergeMode"
@@ -2680,7 +2689,7 @@ export default function FilesUpload() {
className="mr-2"
/>
<span className="text-sm">覆盖原文档(推荐)</span>
</label>
</label> */}
<label className="flex items-center">
<input
type="radio"
@@ -2716,6 +2725,7 @@ export default function FilesUpload() {
onClick={() => {
setShowAttachmentUpload(false);
setSelectedDocumentId(null);
setSelectedDocumentName(null)
setAttachmentFiles([]);
setAttachmentRemark("");
}}
@@ -2746,6 +2756,7 @@ export default function FilesUpload() {
if (e.target === e.currentTarget) {
setShowTemplateUpload(false);
setSelectedDocumentId(null);
setSelectedDocumentName(null)
setTemplateFile(null);
}
}}
@@ -2757,6 +2768,7 @@ export default function FilesUpload() {
onClick={() => {
setShowTemplateUpload(false);
setSelectedDocumentId(null);
setSelectedDocumentName(null)
setTemplateFile(null);
}}
className="text-gray-400 hover:text-gray-600"
@@ -2769,7 +2781,8 @@ export default function FilesUpload() {
{/* 文档信息 */}
<div className="bg-gray-50 p-3 rounded">
<p className="text-sm text-gray-600">
ID: <span className="font-medium">{selectedDocumentId}</span>
{/* 目标文档ID: <span className="font-medium">{selectedDocumentId}</span> */}
: <span className="font-medium">{selectedDocumentName}</span>
</p>
<p className="text-xs text-gray-500 mt-1">
.pdf.docx格式
@@ -2810,6 +2823,7 @@ export default function FilesUpload() {
onClick={() => {
setShowTemplateUpload(false);
setSelectedDocumentId(null);
setSelectedDocumentName(null)
setTemplateFile(null);
}}
disabled={templateUploading}
+11 -11
View File
@@ -157,17 +157,17 @@ export default function PromptsIndex() {
const canViewTemplate = canView('prompt_template');
// 调试信息
// useEffect(() => {
// console.log('📋 [Prompts] 模板数据:', templates);
// console.log('📋 [Prompts] 用户角色:', userRole);
// console.log('📋 [Prompts] 权限列表:', permissions);
// console.log('📋 [Prompts] 权限检查结果:', {
// canCreate: canCreateTemplate,
// canEdit: canEditTemplate,
// canDelete: canDeleteTemplate,
// canView: canViewTemplate
// });
// }, [userRole, permissions, templates, canCreateTemplate, canEditTemplate, canDeleteTemplate, canViewTemplate]);
useEffect(() => {
console.log('📋 [Prompts] 模板数据:', templates);
// console.log('📋 [Prompts] 用户角色:', userRole);
// console.log('📋 [Prompts] 权限列表:', permissions);
// console.log('📋 [Prompts] 权限检查结果:', {
// canCreate: canCreateTemplate,
// canEdit: canEditTemplate,
// canDelete: canDeleteTemplate,
// canView: canViewTemplate
// });
}, [userRole, permissions, templates, canCreateTemplate, canEditTemplate, canDeleteTemplate, canViewTemplate]);
// 处理搜索名称
const handleNameSearch = (value: string) => {
+12 -3
View File
@@ -1361,9 +1361,18 @@ export default function RolePermissions() {
marginLeft: '8px',
padding: '2px 8px',
fontSize: '12px',
backgroundColor: selectedPermCount > 0 ? '#e6f7ed' : '#f5f5f5',
color: selectedPermCount > 0 ? '#52c41a' : '#666',
border: selectedPermCount > 0 ? '1px solid #b7eb8f' : '1px solid #d9d9d9',
backgroundColor:
selectedPermCount === permissions.length ? '#e6f7ed' : // 全部选中:绿色
selectedPermCount > 0 ? '#fff7e6' : // 部分选中:浅橙色
'#f5f5f5', // 未选中:灰色
color:
selectedPermCount === permissions.length ? '#52c41a' : // 全部选中:绿色
selectedPermCount > 0 ? '#fa8c16' : // 部分选中:橙色
'#666', // 未选中:灰色
border:
selectedPermCount === permissions.length ? '1px solid #b7eb8f' : // 全部选中:绿色
selectedPermCount > 0 ? '1px solid #ffd591' : // 部分选中:浅橙色
'1px solid #d9d9d9', // 未选中:灰色
borderRadius: '4px',
cursor: 'pointer',
display: 'inline-flex',