diff --git a/app/components/layout/Layout.tsx b/app/components/layout/Layout.tsx index c190608..5acd1f3 100644 --- a/app/components/layout/Layout.tsx +++ b/app/components/layout/Layout.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Sidebar } from './Sidebar'; // import { Header } from './Header'; import { Breadcrumb } from './Breadcrumb'; -import { useMatches } from '@remix-run/react'; +import { useMatches, useLocation } from '@remix-run/react'; interface LayoutProps { children: React.ReactNode; @@ -11,7 +11,7 @@ interface LayoutProps { // 添加一个接口表示路由handle可能包含的属性 interface RouteHandle { hideBreadcrumb?: boolean; - [key: string]: any; + [key: string]: unknown; } interface Match { @@ -23,9 +23,14 @@ interface Match { export function Layout({ children }: LayoutProps) { const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const matches = useMatches() as Match[]; + const location = useLocation(); + + // 检查当前路径是否应该隐藏侧边栏 + const noLayoutPaths = ['/login', '/']; + const shouldHideSidebar = noLayoutPaths.includes(location.pathname); // 检查当前路由是否应该隐藏默认面包屑 - const shouldHideBreadcrumb = matches.some(match => + const shouldHideBreadcrumb = shouldHideSidebar || matches.some(match => match.handle && match.handle.hideBreadcrumb === true ); @@ -43,6 +48,11 @@ export function Layout({ children }: LayoutProps) { localStorage.setItem('sidebarCollapsed', String(newState)); }; + // 如果是无布局页面,只渲染内容 + if (shouldHideSidebar) { + return <>{children}; + } + return (
>({}); const menuItems: MenuItem[] = [ + { + id: 'contract-search', + title: '智能搜索', + path: '/contract-search', + hideBreadcrumb: true, + icon: 'ri-search-line' + }, { id: 'home', title: '系统概览', - path: '/', + path: '/home', icon: 'ri-home-line' }, { diff --git a/app/components/reviews/ReviewPointsList.tsx b/app/components/reviews/ReviewPointsList.tsx index e37288b..502ff41 100644 --- a/app/components/reviews/ReviewPointsList.tsx +++ b/app/components/reviews/ReviewPointsList.tsx @@ -459,15 +459,24 @@ export function ReviewPointsList({ // 获取所有consistency规则中的fields const allConsistencyFields: string[][] = []; + + // 存储 sourceField 和 targetField 的映射关系 + const pairsMapping: Record = {}; + consistencyRules.forEach(rule => { if (rule.config?.fields) { allConsistencyFields.push(rule.config.fields); - }else if (rule.config?.pairs) { + } else if (rule.config?.pairs) { // 处理pairs情况,提取sourceField和targetField const fields: string[] = []; rule.config.pairs.forEach(pair => { if (pair.sourceField) fields.push(pair.sourceField); if (pair.targetField) fields.push(pair.targetField); + + // 记录 sourceField 和 targetField 的映射关系 + if (pair.sourceField && pair.targetField) { + pairsMapping[pair.sourceField] = pair.targetField; + } }); if (fields.length > 0) { allConsistencyFields.push(fields); @@ -507,10 +516,136 @@ export function ReviewPointsList({ } }); + // 对每个分组内的条目按照 sourceField 和 targetField 的关系进行排序 + Object.keys(groupedContent).forEach(groupKey => { + if (groupKey !== 'default' && groupedContent[groupKey].length > 1) { + // 创建一个新数组用于存储排序后的结果 + const sortedEntries: Array<[string, { page?: number | string, value?: object }]> = []; + const entriesMap = new Map(groupedContent[groupKey]); + + // 找出所有的源字段和目标字段对 + const processed = new Set(); + + // 构建一个字段之间的连接关系图,用于处理嵌套关系 + const fieldChains: Array = []; + + // 遍历所有映射关系,构建字段链 + const buildFieldChains = () => { + // 创建一个图结构,记录每个字段的后继字段 + const graph: Record = {}; + + // 根据映射关系建立图 + Object.entries(pairsMapping).forEach(([source, target]) => { + if (!graph[source]) graph[source] = []; + graph[source].push(target); + + // 确保目标字段在图中有一个空数组 + if (!graph[target]) graph[target] = []; + }); + + // 查找所有在当前分组中的字段 + const fieldsInGroup = new Set(Array.from(entriesMap.keys())); + + // 找出入度为0的节点(即只作为sourceField而不是任何targetField的字段) + const startNodes: string[] = []; + for (const field of fieldsInGroup) { + // 检查该字段是否作为targetField存在 + const isTarget = Object.values(pairsMapping).includes(field); + // 如果该字段是sourceField但不是targetField,则为起始节点 + if (!isTarget && field in pairsMapping) { + startNodes.push(field); + } + } + + // 从每个起始节点开始,使用DFS构建字段链 + for (const startNode of startNodes) { + const chain: string[] = []; + const dfs = (node: string) => { + // 如果该节点不在当前分组中,则跳过 + if (!fieldsInGroup.has(node)) return; + + chain.push(node); + // 遍历所有后继节点 + for (const nextNode of graph[node] || []) { + dfs(nextNode); + } + }; + + dfs(startNode); + + // 如果链不为空,则添加到字段链列表中 + if (chain.length > 0) { + fieldChains.push(chain); + } + } + + // 处理环形依赖或没有入度为0的节点的情况 + // 找出未被处理的字段 + const processedInChains = new Set(fieldChains.flat()); + const remainingFields = Array.from(fieldsInGroup).filter(f => !processedInChains.has(f)); + + // 将剩余字段按照pairsMapping的关系组织成链 + while (remainingFields.length > 0) { + const field = remainingFields.shift()!; + + // 如果该字段已经在某个链中,则跳过 + if (processedInChains.has(field)) continue; + + const chain: string[] = [field]; + processedInChains.add(field); + + // 向后查找链 + let currentField = field; + while (currentField in pairsMapping) { + const nextField = pairsMapping[currentField]; + // 如果下一个字段不在分组中或已处理,则中断 + if (!fieldsInGroup.has(nextField) || processedInChains.has(nextField)) break; + + chain.push(nextField); + processedInChains.add(nextField); + currentField = nextField; + + // 从剩余字段中移除 + const index = remainingFields.indexOf(nextField); + if (index !== -1) { + remainingFields.splice(index, 1); + } + } + + if (chain.length > 0) { + fieldChains.push(chain); + } + } + }; + + buildFieldChains(); + + // 根据字段链构建排序后的结果 + fieldChains.forEach(chain => { + chain.forEach(field => { + if (entriesMap.has(field) && !processed.has(field)) { + sortedEntries.push([field, entriesMap.get(field)!]); + processed.add(field); + } + }); + }); + + // 添加剩余未处理的字段 + for (const [key] of groupedContent[groupKey]) { + if (!processed.has(key)) { + sortedEntries.push([key, entriesMap.get(key)!]); + } + } + + // 用排序后的结果替换原数组 + groupedContent[groupKey] = sortedEntries; + } + }); + return ( <> {/* 渲染各个分组 */} - {Object.entries(groupedContent).map(([groupKey, entries], groupIndex) => { + {Object.entries(groupedContent).map(([groupKey, entries]) => { if (entries.length === 0) return null; // 非默认组添加边框 diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index 64825cb..6fdd113 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -1,324 +1,174 @@ -// import React from 'react'; -import { type MetaFunction, type LoaderFunctionArgs, redirect } from "@remix-run/node"; -import { useLoaderData } 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"; -// import { FileTypeTag, links as fileTypeTagLinks } from "~/components/ui/FileTypeTag"; -import { Tag } from "~/components/ui/Tag"; -import homeStyles from "~/styles/pages/sys_overview.css?url"; -import { getDocuments, type DocumentUI } from "~/api/files/documents"; -import { useState, useEffect } from "react"; -import { getHomeData } from "~/api/home/home"; +import { useState, useEffect } from 'react'; +import { useNavigate, Form } 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'; -import { getUserSession } from "~/root"; - -// 文件处理状态选项 -const fileProcessingStatusOptions = [ - { value: "Waiting", label: "上传中", icon: "ri-loader-line", color: "blue" }, - { value: "Cutting", label: "切分中", icon: "ri-loader-line", color: "purple" }, - { value: "Extractioning", label: "抽取中", icon: "ri-loader-line", color: "cyan" }, - { value: "Evaluationing", label: "评查中", icon: "ri-loader-line", color: "teal" }, - { value: "Processed", label: "已完成", icon: "ri-check-line", color: "green" }, -]; +// import { getUserSession, logout } from "~/root"; export const links = () => [ - { rel: "stylesheet", href: homeStyles }, - ...fileTagLinks() + { rel: "stylesheet", href: styles } ]; export const meta: MetaFunction = () => { return [ { title: "中国烟草AI合同及卷宗审核系统 - 首页" }, - { name: "description", content: "AI审核系统首页" } + { name: "description", content: "中国烟草AI合同及卷宗审核系统首页" }, ]; }; -// API 响应的类型定义 -// interface StatsData { -// totalFiles: number; -// reviewedFiles: number; -// pendingFiles: number; -// passRate: number; -// } - -// 添加认证检查 -export async function loader({ request }: LoaderFunctionArgs) { - // 检查用户登录状态 - // const { isAuthenticated } = await getUserSession(request); +// 处理登出请求 +export async function action({ request }: ActionFunctionArgs) { + const formData = await request.formData(); + const intent = formData.get("intent"); - // if (!isAuthenticated) { - // return redirect("/login"); - // } - - try { - const documentSearchParams = { - page: 1, - pageSize: 10, - order: 'updated_at.desc' - }; - - // 获取最近文档数据 - const responseDocuments = await getDocuments(documentSearchParams); - if (responseDocuments.error) { - console.error('获取最近文档数据失败', responseDocuments.error); - return Response.json({ error: responseDocuments.error }, { status: responseDocuments.status || 500 }); - } - const recentFiles = responseDocuments.data?.documents || []; - console.log("recentFiles-------",recentFiles); - - - const homeData = await getHomeData(); - console.log("homeData-------",homeData); - - - return Response.json({ homeData, recentFiles }); - } catch (error) { - // 错误处理 - console.error('Failed to fetch dashboard data:', error); - return Response.json( - { error: '获取数据失败,请稍后重试' }, - { status: 500 } - ); + if (intent === "logout") { + // return logout(request); } + + return null; } +// 验证用户登录状态 +// export async function loader({ request }: LoaderFunctionArgs) { + // const { isAuthenticated } = await getUserSession(request); + +// if (!isAuthenticated) { +// return redirect("/login"); +// } + +// return Response.json({ isAuthenticated }); +// } + export default function Index() { - const { homeData, recentFiles: initialRecentFiles } = useLoaderData(); - // 使用useState存储最近文档数据,初始值为loader加载的数据 - const [recentFiles, setRecentFiles] = useState(initialRecentFiles || []); + const navigate = useNavigate(); const [currentDateTime, setCurrentDateTime] = useState({ date: '', time: '' }); - // 更新当前时间 + // 更新日期时间 useEffect(() => { - // 使用dayjs格式化日期和时间 const updateDateTime = () => { const now = dayjs(); + // 格式化日期: YYYY/MM/DD setCurrentDateTime({ - date: now.format('YYYY年MM月DD日'), + date: now.format('YYYY/MM/DD'), time: now.format('HH:mm:ss') }); }; - // 立即更新一次 + // 初始化时间 updateDateTime(); - // 设置计时器,每秒更新一次 + // 每秒更新一次 const timerID = setInterval(updateDateTime, 1000); - // 清理函数,组件卸载时清除计时器 return () => clearInterval(timerID); }, []); - - // 修改useEffect定时器,每10秒自动获取最近文档数据 - // 按照定时器更新最近文档 - useEffect(() => { - // 定义一个函数用于获取最新的文档数据 - const fetchLatestDocuments = async () => { - try { - const documentSearchParams = { - page: 1, - pageSize: 10, - order: 'updated_at.desc' - }; - - console.log('定时获取最新文档数据...'); - const responseDocuments = await getDocuments(documentSearchParams); - - if (responseDocuments.error) { - console.error('获取最近文档数据失败', responseDocuments.error); - return; - } - - // 获取新的文档数据 - const newRecentFiles = responseDocuments.data?.documents || []; - - // 检查数据是否有变化 - if (JSON.stringify(newRecentFiles) !== JSON.stringify(recentFiles)) { - console.log('文档数据已更新,直接更新状态'); - // 直接更新状态,不需要刷新页面 - setRecentFiles(newRecentFiles); - } - } catch (error) { - console.error('自动获取文档数据失败:', error); - } - }; - - // 设置10秒的定时器 - const timerID = setInterval(fetchLatestDocuments, 10000); - - // 组件卸载时清除定时器 - return () => { - console.log('清除文档数据自动更新定时器'); - clearInterval(timerID); - }; - }, []); // 不再依赖recentFiles,避免循环依赖 - - return ( -
- {/* 页面头部 */} -
-

系统概览

-
-
- {currentDateTime.date} - | - {currentDateTime.time} -
-
-
- -
-
-

系统管理员

-

超级管理员

-
-
-
-
- - {/* 统计卡片区域 */} - -
- - - - -
-
- - {/* 快捷访问区域 */} - -
- - - - - {/* */} - - {/* */} - -
-
- - {/* 最近文档区域 */} - 查看全部} - className="mt-6" - > -
- {recentFiles.map((file: DocumentUI) => ( -
-
- -
-
{file.name}
-
- - {file.typeName} - - · - {file.updatedAt} -
-
-
-
- {(() => { - const fileStatus = file.fileStatus || "-"; - const status = fileProcessingStatusOptions.find(s => s.value === fileStatus) || - fileProcessingStatusOptions[0]; - const isSpinning = fileStatus !== "Processed"; - return ( -
- - {status.label} -
- ); - })()} -
-
- ))} -
-
-
- ); -} - -// 统计卡片组件 -interface StatCardProps { - title: string; - value: number | string; - icon: string; - trend?: { - value: number; - isUp: boolean; + + // 处理模块点击 + const handleModuleClick = (path: string) => { + navigate(path); + }; + + // 处理键盘事件 + const handleKeyDown = (path: string, e: React.KeyboardEvent) => { + if (e.key === 'Enter' || e.key === ' ') { + handleModuleClick(path); + } + }; + + // 处理登出 + const handleLogout = () => { + // 使用Form组件提交登出请求 + const form = document.getElementById('logout-form') as HTMLFormElement; + if (form) { + form.submit(); + } }; -} -function StatCard({ title, value, icon, trend }: StatCardProps) { return ( -
-
{title}
-
{value}
- {trend && ( -
- - {trend.value}% - 较上月 +
+ {/* 登出表单 - 隐藏 */} +
+ +
+ + {/* 头部 */} +
+
+ 中国烟草 +
+ 中国烟草 + CHINA TOBACCO +
- )} - +
+ {currentDateTime.date} {currentDateTime.time} +
+ 用户头像 + 系统管理员 + +
+
+
+ + {/* 主要内容 */} +
+

- 欢迎来到智慧法务平台 -

+ +
+ {/* 合同管理模块 */} +
handleModuleClick('/documents')} + onKeyDown={(e) => handleKeyDown('/documents', e)} + role="button" + tabIndex={0} + aria-label="合同管理" + > + + 合同管理 +
+ + {/* 案卷智能评查模块 */} +
handleModuleClick('/home')} + onKeyDown={(e) => handleKeyDown('/home', e)} + role="button" + tabIndex={0} + aria-label="案卷智能评查" + > + + 案卷智能评查 +
+ + {/* 智慧法务大模型模块 */} +
handleModuleClick('/prompts')} + onKeyDown={(e) => handleKeyDown('/prompts', e)} + role="button" + tabIndex={0} + aria-label="智慧法务大模型" + > + + 智慧法务大模型 +
+
+
+ {/* 底部山水背景 */} +
+
+
+
); -} - -// 快捷方式组件 -interface ShortcutItemProps { - icon: string; - label: string; - to: string; -} - -function ShortcutItem({ icon, label, to }: ShortcutItemProps) { - return ( - - ); -} +} \ No newline at end of file diff --git a/app/routes/contract-search._index.tsx b/app/routes/contract-search._index.tsx new file mode 100644 index 0000000..28f0b85 --- /dev/null +++ b/app/routes/contract-search._index.tsx @@ -0,0 +1,152 @@ +// import { useState } from 'react'; +// import styles from '~/styles/pages/contract-search.css?url'; + +// export const links = () => [ +// { rel: 'stylesheet', href: styles } +// ]; + +// export default function ContractSearchIndex() { +// const [searchQuery, setSearchQuery] = useState(''); + +// const handleSearchInputChange = (e: React.ChangeEvent) => { +// setSearchQuery(e.target.value); +// // 自动调整高度 +// e.target.style.height = 'auto'; +// e.target.style.height = Math.max(80, e.target.scrollHeight) + 'px'; +// }; + +// const handleSearch = () => { +// if (searchQuery.trim()) { +// console.log('搜索查询:', searchQuery); +// // 这里可以添加实际的搜索逻辑 +// } +// }; + +// const handleCategoryClick = (categoryName: string) => { +// console.log('选择分类:', categoryName); +// // 这里可以添加跳转到相应分类的逻辑 +// }; + +// const handleKeyDown = (e: React.KeyboardEvent, categoryName: string) => { +// if (e.key === 'Enter' || e.key === ' ') { +// e.preventDefault(); +// handleCategoryClick(categoryName); +// } +// }; + +// return ( +//
+//
+//

AI智能合同模板搜索

+//

输入合同名称、用途或关键内容,快速找到最适合的模板

+ +//
+//
+// +//
+//
+// +// 支持自然语言描述,AI将为您匹配最相关的模板 +//
+// +//
+//
+//
+//
+ +//
+// +// +// +// +// +// +//
+//
+// ); +// } diff --git a/app/routes/contract-search.tsx b/app/routes/contract-search.tsx new file mode 100644 index 0000000..5f6ec27 --- /dev/null +++ b/app/routes/contract-search.tsx @@ -0,0 +1,23 @@ +import { Outlet } from "@remix-run/react"; +import { type MetaFunction } from "@remix-run/node"; + +export const meta: MetaFunction = () => { + return [ + { title: "智能搜索 - 中国烟草AI合同及卷宗审核系统" }, + { + name: "contract-search", + content: "智能搜索模块,包括智能搜索功能" + } + ]; +}; + +export const handle = { + breadcrumb: "智能搜索" +}; + +/** + * 配置列表路由布局 + */ +export default function ContractSearchLayout() { + return ; +} \ No newline at end of file diff --git a/app/routes/home.tsx b/app/routes/home.tsx index 1007d7c..649760b 100644 --- a/app/routes/home.tsx +++ b/app/routes/home.tsx @@ -1,180 +1,324 @@ -import { useState, useEffect } from 'react'; -import { useNavigate, Form } from '@remix-run/react'; -import { type MetaFunction, type ActionFunctionArgs, LoaderFunctionArgs, redirect, json } from "@remix-run/node"; -import styles from "~/styles/pages/home.css?url"; -import { getUserSession, logout } from "~/root"; +// import React from 'react'; +import { type MetaFunction, type LoaderFunctionArgs, redirect } from "@remix-run/node"; +import { useLoaderData } 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"; +// import { FileTypeTag, links as fileTypeTagLinks } from "~/components/ui/FileTypeTag"; +import { Tag } from "~/components/ui/Tag"; +import homeStyles from "~/styles/pages/sys_overview.css?url"; +import { getDocuments, type DocumentUI } from "~/api/files/documents"; +import { useState, useEffect } from "react"; +import { getHomeData } from "~/api/home/home"; +import dayjs from 'dayjs'; +// import { getUserSession } from "~/root"; + +// 文件处理状态选项 +const fileProcessingStatusOptions = [ + { value: "Waiting", label: "上传中", icon: "ri-loader-line", color: "blue" }, + { value: "Cutting", label: "切分中", icon: "ri-loader-line", color: "purple" }, + { value: "Extractioning", label: "抽取中", icon: "ri-loader-line", color: "cyan" }, + { value: "Evaluationing", label: "评查中", icon: "ri-loader-line", color: "teal" }, + { value: "Processed", label: "已完成", icon: "ri-check-line", color: "green" }, +]; export const links = () => [ - { rel: "stylesheet", href: styles } + { rel: "stylesheet", href: homeStyles }, + ...fileTagLinks() ]; export const meta: MetaFunction = () => { return [ { title: "中国烟草AI合同及卷宗审核系统 - 首页" }, - { name: "description", content: "中国烟草AI合同及卷宗审核系统首页" }, + { name: "description", content: "AI审核系统首页" } ]; }; -// 处理登出请求 -export async function action({ request }: ActionFunctionArgs) { - const formData = await request.formData(); - const intent = formData.get("intent"); - - if (intent === "logout") { - return logout(request); - } - - return null; -} +// API 响应的类型定义 +// interface StatsData { +// totalFiles: number; +// reviewedFiles: number; +// pendingFiles: number; +// passRate: number; +// } -// 验证用户登录状态 +// 添加认证检查 export async function loader({ request }: LoaderFunctionArgs) { - const { isAuthenticated } = await getUserSession(request); + // 检查用户登录状态 + // const { isAuthenticated } = await getUserSession(request); - if (!isAuthenticated) { - return redirect("/login"); + // if (!isAuthenticated) { + // return redirect("/login"); + // } + + try { + const documentSearchParams = { + page: 1, + pageSize: 10, + order: 'updated_at.desc' + }; + + // 获取最近文档数据 + const responseDocuments = await getDocuments(documentSearchParams); + if (responseDocuments.error) { + console.error('获取最近文档数据失败', responseDocuments.error); + return Response.json({ error: responseDocuments.error }, { status: responseDocuments.status || 500 }); + } + const recentFiles = responseDocuments.data?.documents || []; + console.log("recentFiles-------",recentFiles); + + + const homeData = await getHomeData(); + console.log("homeData-------",homeData); + + + return Response.json({ homeData, recentFiles }); + } catch (error) { + // 错误处理 + console.error('Failed to fetch dashboard data:', error); + return Response.json( + { error: '获取数据失败,请稍后重试' }, + { status: 500 } + ); } - - return json({ isAuthenticated }); } export default function Home() { - const navigate = useNavigate(); - const [currentTime, setCurrentTime] = useState(''); - const [currentDate, setCurrentDate] = useState(''); + const { homeData, recentFiles: initialRecentFiles } = useLoaderData(); + // 使用useState存储最近文档数据,初始值为loader加载的数据 + const [recentFiles, setRecentFiles] = useState(initialRecentFiles || []); + const [currentDateTime, setCurrentDateTime] = useState({ + date: '', + time: '' + }); - // 更新日期时间 + // 更新当前时间 useEffect(() => { + // 使用dayjs格式化日期和时间 const updateDateTime = () => { - const now = new Date(); - // 格式化日期: YYYY/MM/DD - const date = now.toLocaleDateString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit' - }).replace(/\//g, '/'); - - // 格式化时间: HH:MM - const time = now.toLocaleTimeString('zh-CN', { - hour: '2-digit', - minute: '2-digit', - hour12: false + const now = dayjs(); + setCurrentDateTime({ + date: now.format('YYYY年MM月DD日'), + time: now.format('HH:mm:ss') }); - - setCurrentDate(date); - setCurrentTime(time); }; - // 初始化时间 + // 立即更新一次 updateDateTime(); - // 每分钟更新一次 - const interval = setInterval(updateDateTime, 60000); + // 设置计时器,每秒更新一次 + const timerID = setInterval(updateDateTime, 1000); - return () => clearInterval(interval); + // 清理函数,组件卸载时清除计时器 + return () => clearInterval(timerID); }, []); - - // 处理模块点击 - const handleModuleClick = (path: string) => { - navigate(path); - }; - // 处理键盘事件 - const handleKeyDown = (path: string, e: React.KeyboardEvent) => { - if (e.key === 'Enter' || e.key === ' ') { - handleModuleClick(path); - } - }; - - // 处理登出 - const handleLogout = () => { - // 使用Form组件提交登出请求 - const form = document.getElementById('logout-form') as HTMLFormElement; - if (form) { - form.submit(); - } - }; + // 修改useEffect定时器,每10秒自动获取最近文档数据 + // 按照定时器更新最近文档 + useEffect(() => { + // 定义一个函数用于获取最新的文档数据 + const fetchLatestDocuments = async () => { + try { + const documentSearchParams = { + page: 1, + pageSize: 10, + order: 'updated_at.desc' + }; + + console.log('定时获取最新文档数据...'); + const responseDocuments = await getDocuments(documentSearchParams); + + if (responseDocuments.error) { + console.error('获取最近文档数据失败', responseDocuments.error); + return; + } + + // 获取新的文档数据 + const newRecentFiles = responseDocuments.data?.documents || []; + + // 检查数据是否有变化 + if (JSON.stringify(newRecentFiles) !== JSON.stringify(recentFiles)) { + console.log('文档数据已更新,直接更新状态'); + // 直接更新状态,不需要刷新页面 + setRecentFiles(newRecentFiles); + } + } catch (error) { + console.error('自动获取文档数据失败:', error); + } + }; + + // 设置10秒的定时器 + const timerID = setInterval(fetchLatestDocuments, 10000); + + // 组件卸载时清除定时器 + return () => { + console.log('清除文档数据自动更新定时器'); + clearInterval(timerID); + }; + }, []); // 不再依赖recentFiles,避免循环依赖 return ( -
- {/* 登出表单 - 隐藏 */} -
- -
- - {/* 头部 */} -
-
- 中国烟草 - 中国烟草 - CHINA TOBACCO +
+ {/* 页面头部 */} +
+

系统概览

+
+
+ {currentDateTime.date} + | + {currentDateTime.time} +
+
+
+ +
+
+

系统管理员

+

超级管理员

+
+
+
+
+ + {/* 统计卡片区域 */} + +
+ + + +
-
- {currentDate} {currentTime} -
- 用户头像 - 系统管理员 - -
-
-
+ - {/* 主要内容 */} -
-

- 欢迎来到智慧法务平台 -

- -
- {/* 合同管理模块 */} -
handleModuleClick('/documents')} - onKeyDown={(e) => handleKeyDown('/documents', e)} - role="button" - tabIndex={0} - aria-label="合同管理" - > -
- 合同管理 -
- - {/* 案卷智能评查模块 */} -
handleModuleClick('/')} - onKeyDown={(e) => handleKeyDown('/', e)} - role="button" - tabIndex={0} - aria-label="案卷智能评查" - > -
- 案卷智能评查 -
- - {/* 智慧法务大模型模块 */} -
handleModuleClick('/prompts')} - onKeyDown={(e) => handleKeyDown('/prompts', e)} - role="button" - tabIndex={0} - aria-label="智慧法务大模型" - > -
- 智慧法务大模型 -
+ {/* 快捷访问区域 */} + +
+ + + + + {/* */} + + {/* */} +
-
+ - {/* 底部山水背景 */} -
-
-
+ {/* 最近文档区域 */} + 查看全部} + className="mt-6" + > +
+ {recentFiles.map((file: DocumentUI) => ( +
+
+ +
+
{file.name}
+
+ + {file.typeName} + + · + {file.updatedAt} +
+
+
+
+ {(() => { + const fileStatus = file.fileStatus || "-"; + const status = fileProcessingStatusOptions.find(s => s.value === fileStatus) || + fileProcessingStatusOptions[0]; + const isSpinning = fileStatus !== "Processed"; + return ( +
+ + {status.label} +
+ ); + })()} +
+
+ ))} +
+
); -} \ No newline at end of file +} + +// 统计卡片组件 +interface StatCardProps { + title: string; + value: number | string; + icon: string; + trend?: { + value: number; + isUp: boolean; + }; +} + +function StatCard({ title, value, icon, trend }: StatCardProps) { + return ( +
+
{title}
+
{value}
+ {trend && ( +
+ + {trend.value}% + 较上月 +
+ )} + +
+ ); +} + +// 快捷方式组件 +interface ShortcutItemProps { + icon: string; + label: string; + to: string; +} + +function ShortcutItem({ icon, label, to }: ShortcutItemProps) { + return ( + + ); +} diff --git a/app/styles/pages/contract-search.css b/app/styles/pages/contract-search.css new file mode 100644 index 0000000..f4523f8 --- /dev/null +++ b/app/styles/pages/contract-search.css @@ -0,0 +1,186 @@ +/* 合同搜索页面样式 */ +:root { + --primary-color: #00684a; + --primary-hover: #005a40; + --primary-light: rgba(0, 104, 74, 0.1); + --success-color: #52c41a; + --warning-color: #faad14; + --error-color: #ff4d4f; + --text-color: rgba(0, 0, 0, 0.85); + --text-secondary: rgba(0, 0, 0, 0.45); + --border-color: #f0f0f0; + --bg-gray: #f5f5f5; + --gradient-bg: linear-gradient(135deg, #f8fffe 0%, #f0f9ff 100%); +} + +.content-area { + @apply flex-1 p-6 overflow-y-auto; +} + +.search-hero { + @apply text-center py-16 bg-gradient-to-r from-green-50 to-blue-50 rounded-2xl mb-8; +} + +.search-title { + @apply text-3xl font-semibold text-gray-800 mb-3; +} + +.search-subtitle { + @apply text-base text-gray-600 mb-10; +} + +.search-container { + @apply max-w-xl mx-auto relative; +} + +.search-box { + @apply bg-white border-2 border-gray-200 rounded-xl p-4 shadow-md transition-all; +} + +.search-box:focus-within { + @apply border-green-600 shadow-lg; +} + +.search-textarea { + @apply w-full min-h-[80px] border-none outline-none resize-vertical text-base leading-relaxed; +} + +.search-actions { + @apply flex justify-between items-center mt-3 pt-3 border-t border-gray-100; +} + +.search-tips { + @apply text-xs text-gray-500; +} + +.search-btn { + @apply bg-green-700 text-white border-none rounded-lg py-2.5 px-6 text-sm font-medium cursor-pointer transition-all flex items-center gap-1.5; +} + +.search-btn:hover { + @apply bg-green-800 transform -translate-y-0.5; +} + +.quick-categories { + @apply grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-4 mt-10; +} + +.category-card { + @apply bg-white rounded-xl p-5 text-center cursor-pointer transition-all border border-gray-100 shadow-sm; +} + +.category-card:hover { + @apply transform -translate-y-0.5 shadow-md border-green-700; +} + +.category-icon { + @apply w-12 h-12 bg-green-100 rounded-xl flex items-center justify-center mx-auto mb-3 text-green-700; +} + +.category-title { + @apply text-base font-medium mb-1; +} + +.category-count { + @apply text-xs text-gray-500; +} + +.contract-search-container { + @apply w-full p-6 bg-white rounded-lg shadow-sm; +} + +.search-header { + @apply mb-6; +} + +.search-description { + @apply text-gray-600 text-sm; +} + +.search-form { + @apply mb-6; +} + +.search-form-row { + @apply flex flex-wrap items-center gap-4 mb-4; +} + +.search-form-item { + @apply flex-grow min-w-[200px]; +} + +.search-form-label { + @apply block text-sm font-medium text-gray-700 mb-1; +} + +.search-form-input { + @apply w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent; +} + +.search-form-select { + @apply w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent; +} + +.search-form-date { + @apply w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent; +} + +.search-buttons { + @apply flex justify-end gap-4 mt-6; +} + +.search-button { + @apply px-4 py-2 rounded-md font-medium; +} + +.search-button-primary { + @apply bg-blue-600 text-white hover:bg-blue-700; +} + +.search-button-secondary { + @apply bg-gray-200 text-gray-700 hover:bg-gray-300; +} + +.search-results { + @apply mt-8; +} + +.search-results-header { + @apply flex justify-between items-center mb-4; +} + +.search-results-title { + @apply text-lg font-medium text-gray-800; +} + +.search-results-count { + @apply text-gray-600; +} + +.search-results-table { + @apply w-full border-collapse; +} + +.search-results-table th { + @apply px-4 py-3 bg-gray-100 text-left text-xs font-medium text-gray-600 uppercase tracking-wider; +} + +.search-results-table td { + @apply px-4 py-3 border-t border-gray-200 text-sm; +} + +.search-results-actions { + @apply flex gap-2; +} + +.search-results-action { + @apply text-blue-600 hover:text-blue-800 cursor-pointer; +} + +.no-results { + @apply py-12 text-center text-gray-500; +} + +.pagination-container { + @apply mt-6 flex justify-center; +} \ No newline at end of file diff --git a/app/styles/pages/home.css b/app/styles/pages/home.css index a421c6d..95e3f4e 100644 --- a/app/styles/pages/home.css +++ b/app/styles/pages/home.css @@ -2,8 +2,9 @@ .home-page { display: flex; flex-direction: column; - min-height: 100vh; - background-color: #f0f7f4; + height: 100vh; + /* height: 100%; */ + background-color: #ffffff; } /* 头部样式 */ @@ -11,7 +12,7 @@ display: flex; justify-content: space-between; align-items: center; - padding: 0.75rem 2rem; + padding: 0.75rem 1rem; background-color: white; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); } @@ -22,19 +23,19 @@ } .logo { - height: 36px; - margin-right: 0.75rem; + height: 60px; + margin-right: 0.15rem; } .logo-text { - font-size: 1.125rem; - font-weight: 600; + font-size: 1.8rem; + font-weight: 800; color: #333; - margin-right: 0.5rem; } .logo-text-en { - font-size: 0.75rem; + margin-top: -0.2rem; + font-size: 0.85rem; color: #666; font-weight: 500; text-transform: uppercase; @@ -48,7 +49,7 @@ } .datetime { - font-size: 0.875rem; + font-size:1rem; color: #666; } @@ -72,13 +73,18 @@ } /* 主要内容区域 */ - .main-content { + .index-main-content { + border-radius: 0.5rem 0.5rem 0 0; + height: 100%; flex: 1; display: flex; flex-direction: column; align-items: center; - padding: 3rem 1.5rem; + justify-content: center; + margin: 1rem; padding-bottom: 0; + margin-bottom: 0; + background-color: #f0f7f4; } .welcome-text { @@ -98,11 +104,10 @@ .module-card { display: flex; - flex-direction: column; align-items: center; width: 250px; padding: 2rem 1.5rem; - background-color: white; + background: linear-gradient(to bottom, #ebebeb, #ffffff); border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); transition: transform 0.2s, box-shadow 0.2s; @@ -111,19 +116,11 @@ .module-card:hover { transform: translateY(-5px); + border: 1px solid #269b6c; box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); } - .module-icon { - width: 64px; - height: 64px; - margin-bottom: 1.5rem; - background-position: center; - background-repeat: no-repeat; - background-size: contain; - } - - .contract-icon { + /* .contract-icon { background-image: url('/images/contract-icon.svg'); } @@ -133,9 +130,10 @@ .ai-icon { background-image: url('/images/ai-icon.svg'); - } + } */ .module-name { + margin-left: 1rem; font-size: 1.125rem; font-weight: 500; color: #333; @@ -144,8 +142,12 @@ /* 底部山水背景 */ .footer { height: 200px; + margin: 1rem; + margin-top: 0; overflow: hidden; position: relative; + background-color: #f0f7f4; + border-radius: 0 0 0.5rem 0.5rem; } .mountains-bg { diff --git a/html/合同模板页面.html b/html/合同模板页面.html new file mode 100644 index 0000000..873e9b1 --- /dev/null +++ b/html/合同模板页面.html @@ -0,0 +1,1629 @@ + + + + + + 中国烟草合同模板库 - AI智能搜索系统 + + + + + + + +
+
+
+
+
+
+ +
+ + +
+
+ +
+ 欢迎,张经理 + +
+
+ +
+
+

AI智能合同模板搜索

+

输入合同名称、用途或关键内容,快速找到最适合的模板

+ +
+ +
+
+ +
+
+
+ +
+
销售合同
+
128个模板
+
+
+
+ +
+
采购合同
+
96个模板
+
+
+
+ +
+
物流运输
+
64个模板
+
+
+
+ +
+
人事劳务
+
52个模板
+
+
+
+ +
+
租赁合同
+
38个模板
+
+
+
+ +
+
保密协议
+
24个模板
+
+
+
+
+
+
+ + +
+
+
+
+
+
+ +
+ + +
+
+ +
+
+ + +
+ 欢迎,张经理 + +
+
+ +
+ +
+ +
+ +
+
+ 为您找到 24 个相关模板 +
+
+ +
+
+ +
+
全部 (24)
+
销售合同 (18)
+
采购合同 (4)
+
服务合同 (2)
+
+ +
+
+
+
销售合同
+
+ + 4.8 +
+
+

烟草产品销售合同标准模板

+

适用于烟草产品销售业务,包含完整的违约责任条款、付款方式、交付条件等核心要素,符合行业规范要求。

+
+ 更新时间:2023-10-25 + 使用次数:1,248 +
+
+ + + +
+
+ +
+
+
销售合同
+
+ + 4.6 +
+
+

零售商销售协议模板

+

专为零售商设计的销售协议,详细规定了违约责任、退换货政策、结算方式等条款。

+
+ 更新时间:2023-10-20 + 使用次数:856 +
+
+ + + +
+
+ +
+
+
采购合同
+
+ + 4.7 +
+
+

设备采购合同(含违约条款)

+

设备采购专用合同模板,包含详细的违约责任条款、质量保证、验收标准等内容。

+
+ 更新时间:2023-10-18 + 使用次数:642 +
+
+ + + +
+
+ +
+
+
销售合同
+
+ + 4.5 +
+
+

批发销售合同模板

+

适用于大宗批发业务的销售合同,强化了违约责任条款和风险控制措施。

+
+ 更新时间:2023-10-15 + 使用次数:534 +
+
+ + + +
+
+ +
+
+
服务合同
+
+ + 4.4 +
+
+

技术服务合同(标准版)

+

技术服务类合同模板,包含服务标准、违约责任、知识产权保护等关键条款。

+
+ 更新时间:2023-10-12 + 使用次数:423 +
+
+ + + +
+
+ +
+
+
销售合同
+
+ + 4.3 +
+
+

区域代理销售合同

+

区域代理商专用销售合同,明确代理权限、销售目标、违约责任等核心条款。

+
+ 更新时间:2023-10-10 + 使用次数:312 +
+
+ + + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ +
+ + +
+
+ +
+
+ + +
+ 欢迎,张经理 + +
+
+ +
+
+
+

销售合同模板

+
+ 共 128 个模板 +
+
+
+ +
+
+ +
+
全部 (128)
+
标准版 (86)
+
简化版 (24)
+
专业版 (18)
+
+ +
+
+
+
标准版
+
+ + 4.9 +
+
+

烟草产品销售合同(2023版)

+

最新版本的烟草产品销售合同模板,包含完整的法律条款、风险控制措施和行业标准要求。

+
+ 更新时间:2023-10-25 + 使用次数:2,156 +
+
+ + + +
+
+ +
+
+
标准版
+
+ + 4.8 +
+
+

零售商销售协议模板

+

专为零售商设计的销售协议,涵盖商品配送、结算方式、退换货政策等关键条款。

+
+ 更新时间:2023-10-20 + 使用次数:1,834 +
+
+ + + +
+
+ +
+
+
专业版
+
+ + 4.7 +
+
+

大客户销售合同模板

+

适用于大客户的专业销售合同,包含定制化条款、特殊优惠政策和长期合作框架。

+
+ 更新时间:2023-10-18 + 使用次数:1,245 +
+
+ + + +
+
+ +
+
+
简化版
+
+ + 4.6 +
+
+

小额销售合同(简化版)

+

适用于小额交易的简化版销售合同,条款精简但保证法律效力。

+
+ 更新时间:2023-10-15 + 使用次数:956 +
+
+ + + +
+
+ +
+
+
标准版
+
+ + 4.5 +
+
+

区域代理销售合同

+

区域代理商专用销售合同,明确代理权限、销售目标和考核标准。

+
+ 更新时间:2023-10-12 + 使用次数:743 +
+
+ + + +
+
+ +
+
+
标准版
+
+ + 4.4 +
+
+

批发销售合同模板

+

适用于大宗批发业务的销售合同,包含数量折扣、物流配送等专业条款。

+
+ 更新时间:2023-10-10 + 使用次数:612 +
+
+ + + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ +
+ + +
+
+ +
+ + 欢迎,张经理 + +
+
+ +
+
+
+
+
销售合同 · 标准版
+
+
+ + + + + + 4.9 (156评价) +
+
+
+ +

烟草产品销售合同(2023版)

+ +
+
+ 模板编号: + XS-2023-001 +
+
+ 更新时间: + 2023年10月25日 +
+
+ 使用次数: + 2,156次 +
+
+ 文件大小: + 245KB +
+
+ 适用范围: + 烟草产品销售 +
+
+ 法律依据: + 《合同法》《烟草专卖法》 +
+
+ +
+ + + + +
+
+ +
+
+

模板简介

+

+ 本模板是专为烟草行业设计的标准销售合同,严格遵循《烟草专卖法》等相关法律法规, + 涵盖了烟草产品销售过程中的各个关键环节。模板包含完整的合同条款结构, + 包括合同主体、标的物、价格条款、交付方式、付款条件、违约责任、争议解决等核心内容。 + 适用于各类烟草产品的销售业务,能够有效保护交易双方的合法权益。 +

+
+ +
+

主要特点

+
+
+
+ + 法律合规 +
+

严格遵循烟草行业法律法规,确保合同条款合法有效

+
+
+
+ + 条款完整 +
+

涵盖销售全流程,条款结构完整,逻辑清晰

+
+
+
+ + 易于定制 +
+

模板化设计,可根据具体业务需求灵活调整

+
+
+
+ + 行业标准 +
+

符合烟草行业标准,被广泛使用和认可

+
+
+
+ +
+

合同条款结构

+
+
+
1
+
+
合同主体
+
甲乙双方基本信息、资质证明
+
+
+
+
2
+
+
标的物条款
+
产品名称、规格、数量、质量标准
+
+
+
+
3
+
+
价格与付款
+
价格条款、付款方式、结算周期
+
+
+
+
4
+
+
交付与验收
+
交付时间、地点、方式、验收标准
+
+
+
+
5
+
+
违约责任
+
违约情形、责任承担、损失赔偿
+
+
+
+
6
+
+
争议解决
+
争议处理方式、管辖法院
+
+
+
+
+ +
+

合同预览

+
+
+

烟草产品销售合同

+

合同编号:_______________

+
+ +
+

甲方(销售方):_________________________

+

地址:_____________________________________

+

法定代表人:_______________ 联系电话:_______________

+

烟草专卖许可证号:_________________________

+
+ +
+

乙方(采购方):_________________________

+

地址:_____________________________________

+

法定代表人:_______________ 联系电话:_______________

+

烟草专卖零售许可证号:_____________________

+
+ +
+

根据《中华人民共和国合同法》、《中华人民共和国烟草专卖法》等相关法律法规, + 甲乙双方在平等、自愿、公平、诚信的基础上,就烟草产品销售事宜达成如下协议:

+
+ +
+

第一条 标的物

+

1.1 产品名称:_________________________

+

1.2 产品规格:_________________________

+

1.3 产品数量:_________________________

+

1.4 产品单价:_________________________

+

1.5 合同总金额:_______________________

+
+ +
+ ... 更多条款内容请下载完整模板查看 ... +
+
+
+ +
+

用户评价

+
+
+
+
+
+ 李经理 +
+ + + + + +
+
+ 2023-10-20 +
+

模板非常专业,条款完整,符合行业规范。我们公司一直在使用这个模板,效果很好。

+
+ +
+
+
+
+ 王总 +
+ + + + + +
+
+ 2023-10-18 +
+

模板结构清晰,易于理解和使用。特别是违约责任条款写得很详细,对我们很有帮助。

+
+ +
+
+
+
+ 张主任 +
+ + + + + +
+
+ 2023-10-15 +
+

非常实用的模板,法律条款严谨,符合最新的法规要求。推荐给同行使用。

+
+
+
+
+
+
+
+
+
+ + + + \ No newline at end of file