diff --git a/app/components/ui/Alert.tsx b/app/components/ui/Alert.tsx new file mode 100644 index 0000000..ee8249f --- /dev/null +++ b/app/components/ui/Alert.tsx @@ -0,0 +1,59 @@ +import React from 'react'; + +interface AlertProps { + type: 'info' | 'success' | 'warning' | 'error'; + title: React.ReactNode; + className?: string; + children?: React.ReactNode; +} + +export function Alert({ type, title, className = '', children }: AlertProps) { + const getTypeStyles = () => { + switch (type) { + case 'info': + return { + container: 'bg-blue-50 border-blue-200 text-blue-800', + icon: 'ri-information-line text-blue-600' + }; + case 'success': + return { + container: 'bg-green-50 border-green-200 text-green-800', + icon: 'ri-checkbox-circle-line text-green-600' + }; + case 'warning': + return { + container: 'bg-yellow-50 border-yellow-200 text-yellow-800', + icon: 'ri-alert-line text-yellow-600' + }; + case 'error': + return { + container: 'bg-red-50 border-red-200 text-red-800', + icon: 'ri-error-warning-line text-red-600' + }; + default: + return { + container: 'bg-gray-50 border-gray-200 text-gray-800', + icon: 'ri-information-line text-gray-600' + }; + } + }; + + const styles = getTypeStyles(); + + return ( +
+
+
+ +
+
+
{title}
+ {children &&
{children}
} +
+
+
+ ); +} \ No newline at end of file diff --git a/app/routes/config-lists._index.tsx b/app/routes/config-lists._index.tsx index c541a52..95d7b9a 100644 --- a/app/routes/config-lists._index.tsx +++ b/app/routes/config-lists._index.tsx @@ -13,9 +13,6 @@ export const links = () => [ { rel: "stylesheet", href: configListsStyles } ]; -// export const handle = { -// breadcrumb: "系统配置管理" -// }; export const meta: MetaFunction = () => { return [ diff --git a/app/routes/config-lists.new.tsx b/app/routes/config-lists.new.tsx index ce5061e..6e4301d 100644 --- a/app/routes/config-lists.new.tsx +++ b/app/routes/config-lists.new.tsx @@ -11,9 +11,8 @@ export const links = () => [ ]; export const handle = { - breadcrumb: ({ location }: { location: Location }) => { - const hasId = new URLSearchParams(location.search).has("id"); - return hasId ? "编辑配置" : "新增配置"; + breadcrumb: (data:LoaderData) => { + return data.isEdit ? "编辑配置" : "新增配置"; } }; @@ -159,7 +158,7 @@ export async function loader({ request }: LoaderFunctionArgs) { } } - return json({ + return Response.json({ config, isEdit: !!config }); @@ -439,19 +438,10 @@ export default function ConfigNew() { 返回 -
- {config?.id && } - - - - - - - -
+ diff --git a/app/routes/prompts._index.tsx b/app/routes/prompts._index.tsx new file mode 100644 index 0000000..55ac2d1 --- /dev/null +++ b/app/routes/prompts._index.tsx @@ -0,0 +1,479 @@ +import { MetaFunction } from "@remix-run/node"; +import { useSearchParams, useNavigate } from "@remix-run/react"; +import indexStyles from "~/styles/pages/prompts_index.css?url"; +import { Card } from "~/components/ui/Card"; +import { Button } from "~/components/ui/Button"; +import { FilterPanel, FilterSelect, SearchFilter } from "~/components/ui/FilterPanel"; +import { Table } from "~/components/ui/Table"; +import { Pagination } from "~/components/ui/Pagination"; + +// 定义提示词模板类型 +export interface PromptTemplate { + id: string; + template_name: string; + template_type: 'Common' | 'Extraction' | 'Evaluation' | 'Summary'; + description: string; + version: string; + status: 'active' | 'inactive' | 'system'; + created_by: string; + template_content: string; + variables: string; // JSON字符串 +} + +// 样式链接 +export function links() { + return [{ rel: "stylesheet", href: indexStyles }]; +} + +// 页面元数据 +export const meta: MetaFunction = () => { + return [ + { title: "提示词模板管理 - 中国烟草AI合同及卷宗审核系统" }, + { name: "description", content: "管理提示词模板,包括创建、编辑和删除提示词模板" }, + ]; +}; + +// 面包屑导航 +export const handle = { + breadcrumb: "提示词模板管理" +}; + +// 模拟数据 +const MOCK_TEMPLATES: PromptTemplate[] = [ + { + id: "1", + template_name: "行政处罚-抽取通用模板", + template_type: "Extraction", + description: "本模板用于抽取行政处罚决定书编号等信息", + version: "v1.0", + status: "system", + created_by: "system", + template_content: `你是一个专业的文档信息抽取助手。请从以下{docType}文档中抽取关键信息: +1. 处罚决定书编号 +2. 处罚对象名称 +3. 处罚事由 +4. 处罚依据 +5. 处罚内容 +6. 处罚金额 +7. 发文日期 +请将结果以JSON格式输出,包含以上字段。如果某个字段在文档中未找到,则该字段的值设为null。`, + variables: JSON.stringify({ "docType": "文档类型" }) + }, + { + id: "2", + template_name: "销售合同-甲方信息评估", + template_type: "Evaluation", + description: "评估销售合同中甲方信息是否完整", + version: "v1.2", + status: "active", + created_by: "admin", + template_content: `你是一个专业的合同审核助手。请评估以下{docType}中甲方信息的完整性: +请检查以下要素是否存在且完整: +1. 甲方全称 +2. 注册地址 +3. 统一社会信用代码 +4. 法定代表人 +5. 联系方式 +请给出评估结果,并标明缺失或不完整的信息。`, + variables: JSON.stringify({ "docType": "文档类型" }) + }, + { + id: "3", + template_name: "专卖许可证-摘要模板", + template_type: "Summary", + description: "生成专卖许可证申请文件的内容摘要", + version: "v1.0", + status: "active", + created_by: "admin", + template_content: `你是一个专业的文档摘要助手。请为以下{docType}生成一份简洁的摘要: +摘要应包含以下要点: +1. 申请人基本信息 +2. 许可证类型 +3. 申请事项 +4. 经营范围 +5. 申请日期 +请控制摘要在200字以内,保留关键信息。`, + variables: JSON.stringify({ "docType": "文档类型" }) + }, + { + id: "4", + template_name: "采购合同-乙方资质抽取", + template_type: "Extraction", + description: "抽取采购合同中乙方的资质信息", + version: "v1.1", + status: "inactive", + created_by: "zhangsan", + template_content: `你是一个专业的合同信息抽取助手。请从以下{docType}中抽取乙方的资质信息: +需要抽取的信息包括: +1. 乙方全称 +2. 资质证书类型 +3. 资质证书编号 +4. 资质等级 +5. 证书有效期 +请将结果以JSON格式输出,包含以上字段。`, + variables: JSON.stringify({ "docType": "文档类型" }) + }, + { + id: "5", + template_name: "合同通用-关键条款评估", + template_type: "Evaluation", + description: "评估合同中关键条款是否明确、合规", + version: "v2.0", + status: "active", + created_by: "lisi", + template_content: `你是一个专业的{industry}行业合同审核助手。请评估以下合同中的关键条款是否明确、合规: +请重点关注以下条款: +1. 合同标的 +2. 价格条款 +3. 付款条件 +4. 交付方式 +5. 违约责任 +6. 争议解决 +请对每一项给出评估结果,并指出不明确或存在风险的条款。`, + variables: JSON.stringify({ "industry": "行业类型", "docType": "文档类型" }) + } +]; + +// 数据加载器 +export async function loader() { + try { + // 实际应用中,这里应该调用API获取数据 + // const response = await fetch(`${process.env.API_BASE_URL}/api/prompt-templates`); + // if (!response.ok) throw new Error(`获取提示词模板失败: ${response.status}`); + // const templates = await response.json(); + + // 使用模拟数据 + const templates = MOCK_TEMPLATES; + + return Response.json({ + templates, + total: templates.length, + pageSize: 10, + currentPage: 1 + }); + } catch (error) { + console.error("加载提示词模板失败:", error); + return Response.json( + { + templates: [], + total: 0, + pageSize: 10, + currentPage: 1, + error: "加载提示词模板失败" + }, + { status: 500 } + ); + } +} + +// 页面组件 +export default function PromptsIndex() { + const navigate = useNavigate(); + const [searchParams, setSearchParams] = useSearchParams(); + + // 处理搜索名称 + const handleNameSearch = (value: string) => { + const newParams = new URLSearchParams(searchParams); + if (value) { + newParams.set('name', value); + } else { + newParams.delete('name'); + } + newParams.set('page', '1'); + setSearchParams(newParams); + }; + + // 处理类型筛选变更 + const handleTypeChange = (e: React.ChangeEvent) => { + const { value } = e.target; + const newParams = new URLSearchParams(searchParams); + if (value) { + newParams.set('type', value); + } else { + newParams.delete('type'); + } + newParams.set('page', '1'); + setSearchParams(newParams); + }; + + // 处理状态筛选变更 + const handleStatusChange = (e: React.ChangeEvent) => { + const { value } = e.target; + const newParams = new URLSearchParams(searchParams); + if (value) { + newParams.set('status', value); + } else { + newParams.delete('status'); + } + newParams.set('page', '1'); + setSearchParams(newParams); + }; + + // 处理重置筛选 + const handleReset = () => { + setSearchParams(new URLSearchParams()); + }; + + // 查看模板详情 + const handleViewTemplate = (id: string) => { + navigate(`/prompts/new?id=${id}&mode=view`); + }; + + // 复制模板 + const handleCloneTemplate = (id: string) => { + if (confirm('确定要复制该模板创建新模板吗?')) { + navigate(`/prompts/new?id=${id}&mode=clone`); + } + }; + + // 编辑模板 + const handleEditTemplate = (id: string) => { + navigate(`/prompts/new?id=${id}&mode=edit`); + }; + + // 删除模板 + const handleDeleteTemplate = (id: string) => { + if (confirm('确定要删除该模板吗?删除后无法恢复。')) { + // 实际应该调用API删除数据 + console.log('删除模板ID:', id); + alert('删除成功!'); + // 刷新页面 + window.location.reload(); + } + }; + + // 定义表格列配置 + const columns = [ + { + title: "模板名称", + key: "template_name", + width: "300px", + render: (_: unknown, record: PromptTemplate) => ( +
+ + {record.template_name} +
+ ) + }, + { + title: "类型", + key: "template_type", + width: "100px", + render: (_: unknown, record: PromptTemplate) => { + let typeText = ''; + let typeClass = ''; + + switch (record.template_type) { + case 'Extraction': + typeText = '抽取'; + typeClass = 'type-extraction'; + break; + case 'Evaluation': + typeText = '评估'; + typeClass = 'type-evaluation'; + break; + case 'Summary': + typeText = '摘要'; + typeClass = 'type-summary'; + break; + case 'Common': + typeText = '通用'; + typeClass = 'type-common'; + break; + } + + return {typeText}; + } + }, + { + title: "描述", + key: "description", + render: (_: unknown, record: PromptTemplate) => ( +
+ {record.description} +
+ ) + }, + { + title: "版本", + key: "version", + width: "80px", + render: (_: unknown, record: PromptTemplate) => record.version + }, + { + title: "状态", + key: "status", + width: "80px", + render: (_: unknown, record: PromptTemplate) => { + let statusText = ''; + let statusClass = ''; + + switch (record.status) { + case 'active': + statusText = '启用'; + statusClass = 'status-active'; + break; + case 'inactive': + statusText = '停用'; + statusClass = 'status-inactive'; + break; + case 'system': + statusText = '系统预设'; + statusClass = 'status-system'; + break; + } + + return {statusText}; + } + }, + { + title: "创建者", + key: "created_by", + width: "100px", + render: (_: unknown, record: PromptTemplate) => record.created_by + }, + { + title: "操作", + key: "operation", + width: "150px", + render: (_: unknown, record: PromptTemplate) => ( +
+ {record.status === 'system' ? ( + <> + + + + ) : ( + <> + + + + )} +
+ ) + } + ]; + + return ( +
+ {/* 页面头部 */} +
+

提示词模板管理

+
+ +
+
+ + {/* 搜索栏 - 使用FilterPanel */} + + + + + } + noActionDivider={true} + > + + + + + + + + {/* 数据表格 */} + + + + {/* 分页 */} + { + const newParams = new URLSearchParams(searchParams); + newParams.set('page', page.toString()); + setSearchParams(newParams); + }} + showTotal={true} + /> + + + ); +} \ No newline at end of file diff --git a/app/routes/prompts.new.tsx b/app/routes/prompts.new.tsx new file mode 100644 index 0000000..4475cdc --- /dev/null +++ b/app/routes/prompts.new.tsx @@ -0,0 +1,711 @@ +import { useEffect, useState } from "react"; +import { MetaFunction, LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node"; +import { Link, useLoaderData, useSubmit, useNavigation } from "@remix-run/react"; +import { Button } from "~/components/ui/Button"; +import { PromptTemplate } from "./prompts._index"; +import newStyles from "~/styles/pages/prompts_new.css?url"; + +// 样式链接 +export function links() { + return [{ rel: "stylesheet", href: newStyles }]; +} + +// 页面元数据 +export const meta: MetaFunction = () => { + return [ + { title: "提示词模板编辑 - 中国烟草AI合同及卷宗审核系统" }, + { name: "description", content: "创建或编辑提示词模板" }, + ]; +}; + +// 面包屑导航 +export const handle = { + breadcrumb: "编辑提示词模板" +}; + +// 从模拟数据中获取模板 +const getTemplateById = (id: string): PromptTemplate | undefined => { + // 与prompts._index.tsx中的模拟数据保持一致 + const MOCK_TEMPLATES: PromptTemplate[] = [ + { + id: "1", + template_name: "行政处罚-抽取通用模板", + template_type: "Extraction", + description: "本模板用于抽取行政处罚决定书编号等信息", + version: "v1.0", + status: "system", + created_by: "system", + template_content: `你是一个专业的文档信息抽取助手。请从以下{docType}文档中抽取关键信息: +1. 处罚决定书编号 +2. 处罚对象名称 +3. 处罚事由 +4. 处罚依据 +5. 处罚内容 +6. 处罚金额 +7. 发文日期 +请将结果以JSON格式输出,包含以上字段。如果某个字段在文档中未找到,则该字段的值设为null。`, + variables: JSON.stringify({ "docType": "文档类型" }) + }, + { + id: "2", + template_name: "销售合同-甲方信息评估", + template_type: "Evaluation", + description: "评估销售合同中甲方信息是否完整", + version: "v1.2", + status: "active", + created_by: "admin", + template_content: `你是一个专业的合同审核助手。请评估以下{docType}中甲方信息的完整性: +请检查以下要素是否存在且完整: +1. 甲方全称 +2. 注册地址 +3. 统一社会信用代码 +4. 法定代表人 +5. 联系方式 +请给出评估结果,并标明缺失或不完整的信息。`, + variables: JSON.stringify({ "docType": "文档类型" }) + }, + { + id: "3", + template_name: "专卖许可证-摘要模板", + template_type: "Summary", + description: "生成专卖许可证申请文件的内容摘要", + version: "v1.0", + status: "active", + created_by: "admin", + template_content: `你是一个专业的文档摘要助手。请为以下{docType}生成一份简洁的摘要: +摘要应包含以下要点: +1. 申请人基本信息 +2. 许可证类型 +3. 申请事项 +4. 经营范围 +5. 申请日期 +请控制摘要在200字以内,保留关键信息。`, + variables: JSON.stringify({ "docType": "文档类型" }) + }, + { + id: "4", + template_name: "采购合同-乙方资质抽取", + template_type: "Extraction", + description: "抽取采购合同中乙方的资质信息", + version: "v1.1", + status: "inactive", + created_by: "zhangsan", + template_content: `你是一个专业的合同信息抽取助手。请从以下{docType}中抽取乙方的资质信息: +需要抽取的信息包括: +1. 乙方全称 +2. 资质证书类型 +3. 资质证书编号 +4. 资质等级 +5. 证书有效期 +请将结果以JSON格式输出,包含以上字段。`, + variables: JSON.stringify({ "docType": "文档类型" }) + }, + { + id: "5", + template_name: "合同通用-关键条款评估", + template_type: "Evaluation", + description: "评估合同中关键条款是否明确、合规", + version: "v2.0", + status: "active", + created_by: "lisi", + template_content: `你是一个专业的{industry}行业合同审核助手。请评估以下合同中的关键条款是否明确、合规: +请重点关注以下条款: +1. 合同标的 +2. 价格条款 +3. 付款条件 +4. 交付方式 +5. 违约责任 +6. 争议解决 +请对每一项给出评估结果,并指出不明确或存在风险的条款。`, + variables: JSON.stringify({ "industry": "行业类型", "docType": "文档类型" }) + } + ]; + + return MOCK_TEMPLATES.find(t => t.id === id); +}; + +// 加载函数 +export async function loader({ request }: LoaderFunctionArgs) { + try { + const url = new URL(request.url); + const id = url.searchParams.get("id"); + const mode = url.searchParams.get("mode") || "create"; + + // 模板数据,如果是新建则为空 + let template = null; + + if (id) { + // 实际应用中,这里应该调用API获取数据 + // const response = await fetch(`${process.env.API_BASE_URL}/api/prompt-templates/${id}`); + // if (!response.ok) throw new Error(`获取提示词模板失败: ${response.status}`); + // template = await response.json(); + + // 使用模拟数据 + template = getTemplateById(id); + if (!template) { + throw new Error(`未找到ID为${id}的模板`); + } + } + + return Response.json({ + template, + mode + }); + } catch (error) { + console.error("加载提示词模板失败:", error); + return Response.json( + { + template: null, + mode: "create", + error: error instanceof Error ? error.message : "加载提示词模板失败" + }, + { status: 500 } + ); + } +} + +// Action函数 - 处理表单提交 +export async function action({ request }: ActionFunctionArgs) { + try { + const formData = await request.formData(); + const templateData = Object.fromEntries(formData); + + // 表单验证 + const errors: Record = {}; + + if (!templateData.template_name) { + errors.template_name = "模板名称不能为空"; + } + + if (!templateData.template_type) { + errors.template_type = "请选择模板类型"; + } + + if (!templateData.template_content) { + errors.template_content = "模板内容不能为空"; + } + + if (Object.keys(errors).length > 0) { + return Response.json({ errors, success: false }, { status: 400 }); + } + + // 实际应用中,这里应该调用API保存数据 + // const apiUrl = templateData.id + // ? `${process.env.API_BASE_URL}/api/prompt-templates/${templateData.id}` + // : `${process.env.API_BASE_URL}/api/prompt-templates`; + // + // const response = await fetch(apiUrl, { + // method: templateData.id ? "PUT" : "POST", + // headers: { "Content-Type": "application/json" }, + // body: JSON.stringify(templateData) + // }); + // + // if (!response.ok) throw new Error(`保存提示词模板失败: ${response.status}`); + // const result = await response.json(); + + // 模拟API响应 + console.log("提交的模板数据:", templateData); + + return Response.json({ + success: true, + message: "提示词模板保存成功", + template: { + ...templateData, + id: templateData.id || Math.random().toString(36).substring(2, 10), + created_by: "当前用户", + created_at: new Date().toISOString() + } + }); + } catch (error) { + console.error("保存提示词模板失败:", error); + return Response.json( + { + success: false, + message: error instanceof Error ? error.message : "保存提示词模板失败" + }, + { status: 500 } + ); + } +} + +// 提取变量函数 +const extractVariables = (content: string) => { + const regex = /{([^{}]+)}/g; + const variables: Record = {}; + let match; + + while ((match = regex.exec(content)) !== null) { + const varName = match[1].trim(); + if (varName && !variables[varName]) { + variables[varName] = varName; + } + } + + return variables; +}; + +// 页面组件 +export default function PromptsNew() { + const { template, mode } = useLoaderData(); + const submit = useSubmit(); + const navigation = useNavigation(); + const isSubmitting = navigation.state === "submitting"; + + // 表单状态 + const [formData, setFormData] = useState>({ + id: "", + template_name: "", + template_type: "Common", + description: "", + version: "v1.0", + status: "active", + template_content: "", + variables: "{}" + }); + + // 模式状态 + const [isViewMode, setIsViewMode] = useState(false); + const [pageTitle, setPageTitle] = useState("新增提示词模板"); + + // 变量相关状态 + const [detectedVariables, setDetectedVariables] = useState>({}); + const [exampleValues, setExampleValues] = useState>({}); + const [previewContent, setPreviewContent] = useState(""); + + // 初始化表单数据 + useEffect(() => { + if (template) { + const newFormData = { + ...template, + // 如果是克隆模式,则清除ID并修改名称 + id: mode === "clone" ? "" : template.id, + template_name: mode === "clone" ? `${template.template_name} (副本)` : template.template_name, + // 如果是克隆模式,重置版本 + version: mode === "clone" ? "v1.0" : template.version + }; + + setFormData(newFormData); + + try { + // 解析模板变量 + const vars = JSON.parse(template.variables); + setExampleValues(vars); + } catch (e) { + console.error("解析变量失败:", e); + } + + // 检测模板内容中的变量 + if (template.template_content) { + const vars = extractVariables(template.template_content); + setDetectedVariables(vars); + } + } + + // 设置页面模式 + setIsViewMode(mode === "view"); + + // 设置页面标题 + if (mode === "view") { + setPageTitle("查看提示词模板"); + } else if (mode === "edit") { + setPageTitle("编辑提示词模板"); + } else if (mode === "clone") { + setPageTitle("复制创建提示词模板"); + } else { + setPageTitle("新增提示词模板"); + } + }, [template, mode]); + + // 处理输入变化 + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + + // 如果是模板内容,检测变量 + if (name === "template_content") { + const vars = extractVariables(value); + setDetectedVariables(vars); + + // 更新变量JSON + const varsJson = JSON.stringify( + Object.keys(vars).reduce((acc, key) => { + acc[key] = exampleValues[key] || key; + return acc; + }, {} as Record) + ); + + setFormData(prev => ({ + ...prev, + [name]: value, + variables: varsJson + })); + } else { + setFormData(prev => ({ + ...prev, + [name]: value + })); + } + }; + + // 处理状态切换 + const handleStatusToggle = (e: React.ChangeEvent) => { + const status = e.target.checked ? "active" : "inactive"; + setFormData(prev => ({ + ...prev, + status + })); + }; + + // 处理示例值变更 + const handleExampleValueChange = (varName: string, value: string) => { + setExampleValues(prev => ({ + ...prev, + [varName]: value + })); + }; + + // 更新预览 + const updatePreview = () => { + let content = formData.template_content || ""; + + // 替换变量 + Object.entries(detectedVariables).forEach(([key]) => { + const exampleValue = exampleValues[key] || `[${key}]`; + const regex = new RegExp(`{${key}}`, 'g'); + content = content.replace(regex, exampleValue); + }); + + setPreviewContent(content); + }; + + // 当检测到的变量变化时,更新变量JSON + useEffect(() => { + const varsJson = JSON.stringify( + Object.keys(detectedVariables).reduce((acc, key) => { + acc[key] = exampleValues[key] || key; + return acc; + }, {} as Record) + ); + + setFormData(prev => ({ + ...prev, + variables: varsJson + })); + }, [detectedVariables, exampleValues]); + + // 处理表单提交 + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if (isViewMode) { + return; + } + + const formElement = e.target as HTMLFormElement; + const submittingFormData = new FormData(formElement); + + // 确保变量JSON被包含在提交中 + submittingFormData.set("variables", formData.variables || "{}"); + + submit(submittingFormData, { method: "post" }); + }; + + return ( +
+ {/* 页面头部 */} +
+

{pageTitle}

+
+ + + + {!isViewMode ? ( + + ) : ( + + + + )} +
+
+ + {/* 查看模式提示 */} + {isViewMode && ( +
+ +
+
您正在查看系统预设模板,此模板不可修改。如需基于此模板创建新模板,请点击"复制创建"按钮。
+
+
+ )} + + {/* 模板表单 */} +
+ {/* 模板ID - 隐藏字段 */} + + + {/* 模板信息卡片 */} +
+
提示词模板信息
+
+
+ {/* 模板名称 */} +
+ + +
建议使用"文档类型-功能"的命名方式
+
+ + {/* 模板类型 */} +
+ + +
+ + {/* 模板描述 */} +
+ + +
+ +
+ {/* 模板状态 */} +
+ +
+ + {formData.status === 'active' ? '启用' : '停用'} +
+
+ + {/* 模板版本 */} +
+ + +
+
+
+
+
+ + {/* 模板内容卡片 */} +
+
提示词模板内容
+
+
+ +
+
模板内容支持使用变量,变量格式为 {"{varName}"},在使用时会自动替换。系统将自动识别模板中的变量。
+
例如:请从以下{"{docType}"}文档中抽取关键信息...
+
+
+ + {/* 模板内容 */} +
+ + +
提示词模板是AI完成特定任务的指令,请清晰描述任务需求和输出格式
+
+ + {/* 变量识别区域 */} +
+ +
+ +
+
系统已自动识别出模板中的变量。变量以 {"{varName}"} 形式在模板中使用,无需手动定义。
+
+
+ +
+ {Object.keys(detectedVariables).length > 0 ? ( + Object.keys(detectedVariables).map(varName => ( +
+ {varName} +
+ )) + ) : ( +
+ 暂未识别到任何变量,请在模板内容中使用 {"{变量名}"} 格式添加变量 +
+ )} +
+ + +
+ + {/* 模板效果预览 */} +
+ +
+
变量赋值示例:
+
+ {Object.keys(detectedVariables).length > 0 ? ( + Object.keys(detectedVariables).map(varName => ( +
+ + handleExampleValueChange(varName, e.target.value)} + readOnly={isViewMode} + /> +
+ )) + ) : ( +
+ 暂无变量,无需赋值 +
+ )} +
+ +
预览效果:
+
+ {previewContent ? ( +
{previewContent}
+ ) : ( +
根据变量值生成的模板预览将显示在这里...
+ )} +
+ +
+ +
+
+
+
+
+ + {/* 底部按钮区域 */} +
+
+ {isViewMode && ( + + + + )} +
+
+ + + + {!isViewMode && ( + + )} +
+
+ +
+ ); +} \ No newline at end of file diff --git a/app/routes/prompts.tsx b/app/routes/prompts.tsx new file mode 100644 index 0000000..e08d1be --- /dev/null +++ b/app/routes/prompts.tsx @@ -0,0 +1,13 @@ +import { Outlet } from "@remix-run/react"; + +/** + * 提示词模板管理 - 父级路由 + * 仅作为嵌套路由的容器,不包含具体内容 + */ +export const handle = { + breadcrumb: "提示词模板管理" +} + +export default function Prompts() { + return ; +} \ No newline at end of file diff --git a/app/routes/reviewDocuments.tsx b/app/routes/reviewDocuments.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/app/routes/rule-groups.$groupId.tsx b/app/routes/rule-groups.$groupId.tsx deleted file mode 100644 index 5bd0c0a..0000000 --- a/app/routes/rule-groups.$groupId.tsx +++ /dev/null @@ -1,264 +0,0 @@ -import { json, redirect, type ActionFunctionArgs, type LoaderFunctionArgs, type MetaFunction } from "@remix-run/node"; -import { Form, useLoaderData, useNavigation, useActionData } from "@remix-run/react"; -import { useState, useEffect } from "react"; - -export const meta: MetaFunction = ({ data }) => { - return [ - { title: `编辑评查点分组 - ${data?.group?.name || '加载中'} - 中国烟草AI合同及卷宗审核系统` }, - { name: "description", content: "编辑评查点分组信息,包括名称、编码、描述和状态" }, - ]; -}; - -// 模拟数据 -const MOCK_GROUPS = [ - { - id: "1", - name: "合同条款类", - code: "CONTRACT", - description: "关于合同条款的评查点分组", - status: "active", - sortOrder: 1, - createdAt: "2024-01-01T00:00:00Z", - updatedAt: "2024-01-01T00:00:00Z", - }, - { - id: "2", - name: "合规性类", - code: "COMPLIANCE", - description: "关于合规性的评查点分组", - status: "active", - sortOrder: 2, - createdAt: "2024-01-01T00:00:00Z", - updatedAt: "2024-01-01T00:00:00Z", - }, - { - id: "3", - name: "风险提示类", - code: "RISK", - description: "关于风险提示的评查点分组", - status: "inactive", - sortOrder: 3, - createdAt: "2024-01-01T00:00:00Z", - updatedAt: "2024-01-01T00:00:00Z", - }, -]; - -export async function loader({ params }: LoaderFunctionArgs) { - const { groupId } = params; - - // 真实环境中,这里会调用API获取数据 - // const response = await fetch(`${process.env.API_URL}/api/rule-groups/${groupId}`); - // if (response.status === 404) { - // throw new Response("评查点分组不存在", { status: 404 }); - // } - // if (!response.ok) { - // throw new Response("获取评查点分组失败", { status: response.status }); - // } - // const group = await response.json(); - - // 使用模拟数据 - const group = MOCK_GROUPS.find(g => g.id === groupId); - if (!group) { - throw new Response("评查点分组不存在", { status: 404 }); - } - - return json({ group }); -} - -export async function action({ request, params }: ActionFunctionArgs) { - const formData = await request.formData(); - const groupId = params.groupId; - - const name = formData.get("name"); - const code = formData.get("code"); - const description = formData.get("description"); - const status = formData.get("status"); - const sortOrder = formData.get("sortOrder"); - - // 基本验证 - const errors = {}; - if (!name) errors.name = "分组名称不能为空"; - if (!code) errors.code = "分组编码不能为空"; - if (Object.keys(errors).length > 0) { - return json({ errors, values: Object.fromEntries(formData) }); - } - - // 构建更新数据 - const updateData = { - name, - code, - description, - status, - sortOrder: Number(sortOrder) || 0, - }; - - // 真实环境中,这里会调用API更新数据 - // const response = await fetch(`${process.env.API_URL}/api/rule-groups/${groupId}`, { - // method: "PUT", - // headers: { "Content-Type": "application/json" }, - // body: JSON.stringify(updateData), - // }); - // - // if (!response.ok) { - // throw new Response("更新评查点分组失败", { status: response.status }); - // } - - // 模拟更新成功 - console.log('保存分组数据:', { id: groupId, ...updateData }); - - // 重定向回列表页 - return redirect('/rule-groups'); -} - -export default function EditRuleGroup() { - const { group } = useLoaderData(); - const actionData = useActionData(); - const navigation = useNavigation(); - - const isSubmitting = navigation.state === "submitting"; - - const [formData, setFormData] = useState({ - name: group.name, - code: group.code, - description: group.description || "", - status: group.status, - sortOrder: group.sortOrder.toString(), - }); - - // 当actionData中有错误时,保留用户输入的值 - useEffect(() => { - if (actionData?.values) { - setFormData({ - name: actionData.values.name, - code: actionData.values.code, - description: actionData.values.description || "", - status: actionData.values.status, - sortOrder: actionData.values.sortOrder, - }); - } - }, [actionData]); - - const handleChange = (e) => { - const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); - }; - - return ( -
-
-

编辑评查点分组

-
- -
-
-
-
- - - {actionData?.errors?.name && ( -

{actionData.errors.name}

- )} -
- -
- - - {actionData?.errors?.code && ( -

{actionData.errors.code}

- )} -
-
- -
- - -
- -
-
- - -
- -
- - -
-
- -
- { - e.preventDefault(); - window.history.back(); - }} - > - 取消 - - -
- -
-
- ); -} \ No newline at end of file diff --git a/app/routes/rule-groups._index.tsx b/app/routes/rule-groups._index.tsx index 3855d0e..be0f40b 100644 --- a/app/routes/rule-groups._index.tsx +++ b/app/routes/rule-groups._index.tsx @@ -9,6 +9,10 @@ import { Table } from "~/components/ui/Table"; import { FilterPanel, FilterSelect, SearchFilter } from "~/components/ui/FilterPanel"; import { Pagination } from "~/components/ui/Pagination"; +export function links() { + return [{ rel: "stylesheet", href: indexStyles }]; +} + // 定义数据类型 interface RuleGroup { id: string; @@ -21,10 +25,6 @@ interface RuleGroup { children?: RuleGroup[]; } -export const handle = { - breadcrumb: "评查点分组" -}; - export const meta: MetaFunction = () => { return [ { title: "评查点分组 - 中国烟草AI合同及卷宗审核系统" }, @@ -32,9 +32,6 @@ export const meta: MetaFunction = () => { ]; }; -export function links() { - return [{ rel: "stylesheet", href: indexStyles }]; -} // 模拟数据 const MOCK_GROUPS: RuleGroup[] = [ @@ -263,20 +260,21 @@ export default function RuleGroupsIndex() { key: "operation", width: "180px", render: (_: unknown, record: RuleGroup) => ( - <> - - + ) } ]; diff --git a/app/routes/rule-groups.new.tsx b/app/routes/rule-groups.new.tsx index 4aa3a90..e0332a0 100644 --- a/app/routes/rule-groups.new.tsx +++ b/app/routes/rule-groups.new.tsx @@ -1,205 +1,497 @@ // app/routes/rule-groups.new.tsx -import { json, redirect, type ActionFunctionArgs, type MetaFunction } from "@remix-run/node"; -import { Form, useNavigation, useActionData } from "@remix-run/react"; -import { useState, useEffect } from "react"; +import { redirect, json, type ActionFunctionArgs, type LoaderFunctionArgs, type MetaFunction } from "@remix-run/node"; +import { useLoaderData, useActionData, useNavigation, Form } from "@remix-run/react"; +import { useEffect, useState } from "react"; +import { Button } from "~/components/ui/Button"; +import { Card } from "~/components/ui/Card"; +import ruleGroupsNewStyles from "~/styles/pages/rule-groups_new.css?url"; -export const meta: MetaFunction = () => { +// 类型定义 +interface RuleGroup { + id: string; + name: string; + code: string; + description?: string; + status: 'active' | 'inactive'; + parentId?: string | null; + sortOrder?: number; +} + +interface ParentGroup { + id: string; + name: string; +} + +// 定义加载器返回数据类型 +export interface LoaderData { + group?: RuleGroup; + parentGroups: ParentGroup[]; + isEdit: boolean; + error?: string; +} + +// 定义action返回数据类型 +export interface ActionData { + success?: boolean; + errors?: { + name?: string; + code?: string; + parentId?: string; + general?: string; + }; + values?: Record; +} + +// 样式链接 +export function links() { + return [{ rel: "stylesheet", href: ruleGroupsNewStyles }]; +} + +// 动态面包屑 +export const handle = { + breadcrumb: (data: LoaderData) => { + return data.isEdit ? "编辑分组" : "新增分组"; + } +}; + +// 页面元数据 +export const meta: MetaFunction = ({ location }) => { + const isEdit = new URLSearchParams(location.search).has("id"); + const title = isEdit ? "编辑评查点分组" : "新建评查点分组"; + return [ - { title: "新建评查点分组 - 中国烟草AI合同及卷宗审核系统" }, + { title: `${title} - 中国烟草AI合同及卷宗审核系统` }, { name: "description", content: "创建新的评查点分组,包括分组名称、编码、描述和状态" }, ]; }; +// 数据加载器 +export async function loader({ request }: LoaderFunctionArgs) { + console.log("rule-groups.new loader被调用,URL:", request.url); + try { + const url = new URL(request.url); + const id = url.searchParams.get("id"); + console.log("获取到的ID参数:", id); + + // 获取一级分组列表 (用于选择父级分组) + const parentGroups: ParentGroup[] = [ + { id: "1", name: "合同基本要素检查" }, + { id: "4", name: "销售合同专项检查" }, + { id: "5", name: "行政处罚规范性检查" } + ]; + + // 简化这里,初始时不获取数据,避免可能的错误 + let group: RuleGroup | undefined = undefined; + + // 如果有ID才尝试获取数据 + if (id) { + // 简化的模拟数据 + const demoData: Record = { + "1": { + id: "1", + name: "合同基本要素检查", + code: "contract-base", + description: "检查合同基本要素是否完整规范", + status: "active", + parentId: null, + sortOrder: 0 + }, + "2": { + id: "2", + name: "必备要素检查", + code: "essential-elements", + description: "检查合同中的必要信息项是否齐全", + status: "active", + parentId: "1", + sortOrder: 1 + }, + "3": { + id: "3", + name: "合同主体检查", + code: "contract-parties", + description: "检查合同主体信息是否规范", + status: "active", + parentId: "1", + sortOrder: 2 + }, + "4": { + id: "4", + name: "销售合同专项检查", + code: "contract-sales", + description: "针对销售合同的专项合规检查", + status: "active", + parentId: null, + sortOrder: 3 + }, + "5": { + id: "5", + name: "行政处罚规范性检查", + code: "punishment", + description: "对行政处罚文书的合规性检查", + status: "inactive", + parentId: null, + sortOrder: 4 + } + }; + + group = demoData[id]; + console.log("找到的group数据:", group); + } + + // 返回简化的数据结构 + return json({ + group, + parentGroups, + isEdit: !!group, + error: undefined // 显式返回error字段,无错误时为undefined + }); + } catch (error) { + console.error("loader函数出错:", error); + // 返回一个基本的响应,避免500错误 + return json({ + group: undefined, + parentGroups: [], + isEdit: false, + error: "加载数据时出错" + }); + } +} + +// 表单处理 export async function action({ request }: ActionFunctionArgs) { const formData = await request.formData(); - const name = formData.get("name"); - const code = formData.get("code"); - const description = formData.get("description"); - const status = formData.get("status") || "active"; - const sortOrder = formData.get("sortOrder") || "0"; + // 提取表单数据 + const id = formData.get("id") as string | null; + const name = formData.get("name") as string; + const code = formData.get("code") as string; + const description = formData.get("description") as string; + const status = formData.get("status") as string || "active"; + const groupType = formData.get("groupType") as string; + const parentId = groupType === "secondary" ? formData.get("parentId") as string : null; + const sortOrder = parseInt(formData.get("sortOrder") as string || "0", 10); - // 基本验证 - const errors = {}; - if (!name) errors.name = "分组名称不能为空"; - if (!code) errors.code = "分组编码不能为空"; - if (Object.keys(errors).length > 0) { - return json({ errors, values: Object.fromEntries(formData) }); + // 表单验证 + const errors: ActionData["errors"] = {}; + + if (!name || name.trim() === "") { + errors.name = "分组名称不能为空"; } - // 构建创建数据 - const createData = { - name, - code, - description, + if (!code || code.trim() === "") { + errors.code = "分组编码不能为空"; + } else if (!/^[a-zA-Z0-9-]+$/.test(code)) { + errors.code = "分组编码只能包含字母、数字和连字符"; + } + + if (groupType === "secondary" && (!parentId || parentId.trim() === "")) { + errors.parentId = "请选择上级分组"; + } + + if (Object.keys(errors).length > 0) { + return json({ + errors, + values: Object.fromEntries(formData) as Record + }); + } + + // 构建保存数据 + const saveData = { + id, + name: name.trim(), + code: code.trim(), + description: description?.trim() || "", status, - sortOrder: Number(sortOrder) || 0, + parentId, + sortOrder }; - // 真实环境中,这里会调用API创建数据 - // const response = await fetch(`${process.env.API_URL}/api/rule-groups`, { - // method: "POST", - // headers: { "Content-Type": "application/json" }, - // body: JSON.stringify(createData), - // }); - // - // if (!response.ok) { - // throw new Response("创建评查点分组失败", { status: response.status }); - // } - - // 模拟创建成功 - console.log('创建分组数据:', createData); - - // 重定向回列表页 - return redirect('/rule-groups'); + try { + // 实际应用中应调用API + console.log("保存分组数据:", saveData); + + // const response = await fetch(`${process.env.API_BASE_URL}/api/rule-groups${id ? `/${id}` : ''}`, { + // method: id ? "PUT" : "POST", + // headers: { + // "Content-Type": "application/json", + // }, + // body: JSON.stringify(saveData), + // }); + // + // if (!response.ok) { + // throw new Error(`保存失败: ${response.status}`); + // } + + // 保存成功,重定向到列表页 + return redirect("/rule-groups"); + } catch (error) { + console.error("保存分组失败:", error); + return json({ + success: false, + errors: { + general: "保存分组失败,请稍后重试" + }, + values: Object.fromEntries(formData) as Record + }); + } } -export default function NewRuleGroup() { +// 页面组件 +export default function RuleGroupNew() { + // 所有Hooks必须在组件顶部无条件调用 + const data = useLoaderData(); const actionData = useActionData(); const navigation = useNavigation(); - const isSubmitting = navigation.state === "submitting"; - const [formData, setFormData] = useState({ - name: "", - code: "", - description: "", - status: "active", - sortOrder: "0", - }); + // 表单状态 + const [groupType, setGroupType] = useState<"primary" | "secondary">("primary"); + const [showParentSelect, setShowParentSelect] = useState(false); - // 当actionData中有错误时,保留用户输入的值 + // 解构数据 + const { group, parentGroups, isEdit, error } = data; + + // 初始化表单状态 useEffect(() => { - if (actionData?.values) { - setFormData({ - name: actionData.values.name, - code: actionData.values.code, - description: actionData.values.description || "", - status: actionData.values.status, - sortOrder: actionData.values.sortOrder, - }); + if (group) { + if (group.parentId) { + setGroupType("secondary"); + setShowParentSelect(true); + } else { + setGroupType("primary"); + setShowParentSelect(false); + } } - }, [actionData]); + }, [group]); - const handleChange = (e) => { - const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); + // 处理分组类型变更 + const handleGroupTypeChange = (e: React.ChangeEvent) => { + const value = e.target.value as "primary" | "secondary"; + setGroupType(value); + setShowParentSelect(value === "secondary"); }; + // 如果加载数据时出错,显示错误信息 + if (error) { + return ( +
+

加载出错

+

{error}

+ +
+ ); + } + return ( -
-
-

新建评查点分组

+
+ {/* 页面头部 */} +
+
+

{isEdit ? "编辑评查点分组" : "新增评查点分组"}

+

创建新的评查点分组,用于组织管理评查点

+
+
+ + +
-
-
-
-
- - - {actionData?.errors?.name && ( -

{actionData.errors.name}

+
+ {/* 提示信息 */} +
+ +

评查点分组用于对评查点进行分类管理,合理的分组结构有助于更高效地组织和查找评查点。

+
+ + {/* 错误提示 */} + {actionData?.errors?.general && ( +
+ + {actionData.errors.general} +
+ )} + + {/* 表单 */} + + {/* 如果是编辑模式,添加ID */} + {group?.id && } + + {/* 基本信息区域 */} + +
+ +

基本信息

+
+
+ {/* 分组类型选择 */} +
+ + 分组类型 * + +
+ + +
+
一级分组作为顶层分类,二级分组需要选择所属的一级分组
+
+ + {/* 上级分组选择 */} + {showParentSelect && ( +
+ + + {actionData?.errors?.parentId && ( +
{actionData.errors.parentId}
+ )} +
选择此分组所属的上级分组
+
)} + + {/* 分组编码和名称 */} +
+
+
+ + + {actionData?.errors?.code && ( +
{actionData.errors.code}
+ )} +
编码只能包含字母、数字和连字符,且必须唯一
+
+
+
+
+ + + {actionData?.errors?.name && ( +
{actionData.errors.name}
+ )} +
请使用简洁明了的名称,不超过30个字符
+
+
+
- -
- - - {actionData?.errors?.code && ( -

{actionData.errors.code}

- )} -
-
+ -
- - -
- -
-
- - + {/* 详细配置区域 */} + +
+ +

详细配置

- -
- - +
+ {/* 分组描述 */} +
+ + +
详细描述有助于其他用户了解该分组的用途
+
+ + {/* 状态 */} +
+ + +
禁用状态的分组及其下的评查点将不会参与评查
+
+ + {/* 排序 */} +
+ + +
用于设置分组在列表中的显示顺序,默认为0
+
-
- -
- { - e.preventDefault(); - window.history.back(); - }} - > - 取消 - - -
+
diff --git a/app/routes/rule-groups.tsx b/app/routes/rule-groups.tsx index a33a0d2..a387191 100644 --- a/app/routes/rule-groups.tsx +++ b/app/routes/rule-groups.tsx @@ -1,10 +1,12 @@ -// app/routes/rule-groups.tsx import { Outlet } from "@remix-run/react"; +/** + * 评查点分组管理 - 父级路由 + * 仅作为嵌套路由的容器,不包含具体内容 + */ export const handle = { - breadcrumb: "评查规则库" -}; - -export default function RuleGroupsLayout() { - return + breadcrumb: "评查点分组" +} +export default function RuleGroups() { + return ; } \ No newline at end of file diff --git a/app/styles/components/button.css b/app/styles/components/button.css index 38a3fbe..f26ea15 100644 --- a/app/styles/components/button.css +++ b/app/styles/components/button.css @@ -94,7 +94,7 @@ } .ant-btn-default { - @apply bg-white border border-gray-300 text-gray-800 hover:border-[#00684a] focus:ring-[#00684a]; + @apply bg-white border border-gray-300 text-gray-800 focus:ring-gray-300; } .ant-btn-danger { diff --git a/app/styles/components/filter-panel.css b/app/styles/components/filter-panel.css index 2fec435..10a4eac 100644 --- a/app/styles/components/filter-panel.css +++ b/app/styles/components/filter-panel.css @@ -24,10 +24,10 @@ /* 筛选控件 */ .filter-control { - @apply w-full; + @apply w-full focus:border-[#00684a] focus:shadow-[0,0,0,2px,rgba(0,104,74,0.2)] focus:outline-none; } -/* 筛选操作按钮区域 */ +/* 筛选操作按钮区域 */ .filter-actions { @apply flex justify-end items-center pt-4 mt-4 border-t border-gray-100 space-x-3; } diff --git a/app/styles/pages/prompts_index.css b/app/styles/pages/prompts_index.css new file mode 100644 index 0000000..a93f171 --- /dev/null +++ b/app/styles/pages/prompts_index.css @@ -0,0 +1,99 @@ +/** + * 提示词模板管理页面样式 + */ + +.prompt-page { + --primary-color: var(--color-primary, #00684a); + --primary-hover: var(--color-primary-hover, #005a40); + --primary-light: rgba(0, 104, 74, 0.1); + --success-color: var(--color-success, #52c41a); + --warning-color: var(--color-warning, #faad14); + --error-color: var(--color-error, #ff4d4f); +} + +/* 页面头部 */ +.prompt-page .page-header { + @apply flex justify-between items-center mb-4; +} + +.prompt-page .page-title { + @apply text-xl font-medium; +} + +/* 搜索区域 */ +.prompt-page .search-container { + @apply mb-4; +} + +.prompt-page .search-form { + @apply flex flex-wrap items-end gap-4; +} + +.prompt-page .search-field { + @apply flex-1 min-w-[200px]; +} + +.prompt-page .search-actions { + @apply flex items-center; +} + +/* 数据表格 */ +.prompt-page .table-container { + @apply overflow-x-auto; +} + +/* 类型标签 */ +.prompt-page .type-badge { + @apply inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium mr-1; +} + +.prompt-page .type-extraction { + @apply bg-green-100 text-green-800; +} + +.prompt-page .type-evaluation { + @apply bg-yellow-100 text-yellow-800; +} + +.prompt-page .type-summary { + @apply bg-blue-100 text-blue-800; +} + +.prompt-page .type-common { + @apply bg-purple-100 text-purple-800; +} + +/* 状态标签 */ +.prompt-page .status-badge { + @apply inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium; +} + +.prompt-page .status-active { + @apply bg-green-100 text-green-800; +} + +.prompt-page .status-inactive { + @apply bg-red-100 text-red-800; +} + +.prompt-page .status-system { + @apply bg-blue-100 text-blue-800; +} + +/* 操作按钮 */ +.prompt-page .operation-btn { + @apply inline-flex items-center px-2 py-1 text-sm rounded-md hover:bg-gray-100 transition-colors duration-150 ease-in-out; +} + +.prompt-page .operation-btn i { + @apply mr-1; +} + +/* 分页 */ +.prompt-page .pagination-info { + @apply text-sm text-gray-500; +} + +.prompt-page .pagination-controls { + @apply flex items-center; +} \ No newline at end of file diff --git a/app/styles/pages/prompts_new.css b/app/styles/pages/prompts_new.css new file mode 100644 index 0000000..2e3586d --- /dev/null +++ b/app/styles/pages/prompts_new.css @@ -0,0 +1,194 @@ +/** + * 提示词模板编辑页面样式 + */ + +.prompt-new-page { + --primary-color: var(--color-primary, #00684a); + --primary-hover: var(--color-primary-hover, #005a40); + --primary-light: rgba(0, 104, 74, 0.1); + --success-color: var(--color-success, #52c41a); + --warning-color: var(--color-warning, #faad14); + --error-color: var(--color-error, #ff4d4f); + --text-color: rgba(0, 0, 0, 0.85); + --text-secondary: rgba(0, 0, 0, 0.45); + --border-color: #f0f0f0; + --bg-gray: #f5f5f5; +} + +/* 页面头部 */ +.prompt-new-page .page-header { + @apply flex justify-between items-center mb-4; +} + +.prompt-new-page .page-title { + @apply text-xl font-medium; +} + +/* 表单样式 */ +.prompt-new-page .form-group { + @apply mb-5; +} + +.prompt-new-page .form-label { + @apply block mb-1.5 text-sm font-medium text-gray-800; +} + +.prompt-new-page .form-input, +.prompt-new-page .form-select, +.prompt-new-page .form-textarea { + @apply w-full px-3 py-2 text-sm border border-gray-300 rounded transition-all duration-300; +} + +.prompt-new-page .form-input:focus, +.prompt-new-page .form-select:focus, +.prompt-new-page .form-textarea:focus { + @apply outline-none border-[var(--primary-color)] shadow-[0_0_0_2px_rgba(0,104,74,0.2)]; +} + +.prompt-new-page .form-textarea { + @apply min-h-[80px]; + resize: vertical; +} + +.prompt-new-page .form-code-editor { + @apply font-mono text-sm p-3 min-h-[300px] rounded border border-gray-300 w-full whitespace-pre-wrap; + font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; + line-height: 1.5; +} + +.prompt-new-page .help-text { + @apply text-[var(--text-secondary)] text-xs mt-1; +} + +/* 只读字段 */ +.prompt-new-page .read-only-field { + @apply bg-[var(--bg-gray)] cursor-not-allowed; +} + +/* 变量相关样式 */ +.prompt-new-page .var-container { + @apply flex flex-wrap gap-2 mt-2; +} + +.prompt-new-page .var-badge { + @apply inline-flex items-center px-2 py-1 rounded-full bg-[var(--primary-light)] text-[var(--primary-color)] text-xs mr-1.5 mb-1.5; +} + +.prompt-new-page .var-badge i { + @apply text-sm ml-1 cursor-pointer; +} + +.prompt-new-page .example-section { + @apply mt-2 border border-[var(--border-color)] rounded p-3; +} + +.prompt-new-page .example-header { + @apply font-medium mb-2 text-sm; +} + +.prompt-new-page .var-input-group { + @apply flex gap-2 mb-2; +} + +.prompt-new-page .var-input-group .form-input { + @apply flex-1 mb-0; +} + +/* 开关样式 */ +.prompt-new-page .switch { + @apply relative inline-block w-11 h-[22px]; +} + +.prompt-new-page .switch input { + @apply opacity-0 w-0 h-0; +} + +.prompt-new-page .slider { + @apply absolute cursor-pointer top-0 left-0 right-0 bottom-0 bg-gray-400 rounded-full transition-all duration-300; +} + +.prompt-new-page .slider:before { + content: ""; + @apply absolute h-[18px] w-[18px] left-[3px] bottom-[2px] bg-white rounded-full transition-all duration-300; +} + +.prompt-new-page input:checked + .slider { + @apply bg-[var(--primary-color)]; +} + +.prompt-new-page input:focus + .slider { + @apply shadow-[0_0_1px_var(--primary-color)]; +} + +.prompt-new-page input:checked + .slider:before { + @apply transform translate-x-5; +} + +/* 警告框样式 */ +.prompt-new-page .alert { + @apply p-3 rounded mb-4 flex items-start; +} + +.prompt-new-page .alert i { + @apply mr-2 mt-0.5; +} + +.prompt-new-page .alert-info { + @apply bg-[#e6f7ff] border border-[#91d5ff] text-[#1890ff]; +} + +.prompt-new-page .alert-warning { + @apply bg-[#fffbe6] border border-[#ffe58f] text-[#faad14]; +} + +/* 类型标签 */ +.prompt-new-page .type-tag { + @apply inline-flex items-center px-2 py-1 rounded text-xs mb-2; +} + +.prompt-new-page .type-common { + @apply bg-[var(--primary-light)] text-[var(--primary-color)]; +} + +.prompt-new-page .type-extraction { + @apply bg-[rgba(82,196,26,0.1)] text-[var(--success-color)]; +} + +.prompt-new-page .type-evaluation { + @apply bg-[rgba(250,173,20,0.1)] text-[var(--warning-color)]; +} + +.prompt-new-page .type-summary { + @apply bg-[rgba(24,144,255,0.1)] text-[#1890ff]; +} + +/* 代码块样式 */ +.prompt-new-page code { + @apply bg-gray-100 text-gray-800 px-1 py-0.5 rounded text-sm font-mono; +} + +/* 文本颜色 */ +.prompt-new-page .text-error { + @apply text-[var(--error-color)]; +} + +.prompt-new-page .text-secondary { + @apply text-[var(--text-secondary)]; +} + +.prompt-new-page .text-primary { + @apply text-[var(--primary-color)]; +} + +/* 卡片样式 */ +.prompt-new-page .ant-card { + @apply bg-white rounded-lg shadow-sm border border-gray-200 mb-4; +} + +.prompt-new-page .ant-card-header { + @apply px-4 py-3 border-b border-gray-100 font-medium; +} + +.prompt-new-page .ant-card-body { + @apply p-4; +} \ No newline at end of file diff --git a/app/styles/pages/rule-groups_index.css b/app/styles/pages/rule-groups_index.css index d4869ad..4c682ed 100644 --- a/app/styles/pages/rule-groups_index.css +++ b/app/styles/pages/rule-groups_index.css @@ -166,6 +166,14 @@ .rule-groups-page .ant-btn-text.text-primary { color: #00684a; } + + .rule-groups-page .operations-cell { + @apply flex space-x-2; + } + + .rule-groups-page .operation-btn { + @apply text-sm flex items-center text-[--color-primary] bg-transparent hover:underline p-2; + } .rule-groups-page .ant-btn-text.text-primary:hover { color: #00684a; diff --git a/app/styles/pages/rule-groups_new.css b/app/styles/pages/rule-groups_new.css new file mode 100644 index 0000000..e442bc1 --- /dev/null +++ b/app/styles/pages/rule-groups_new.css @@ -0,0 +1,154 @@ +/** + * 评查点分组新增/编辑页样式 + */ + +/* 页面容器 */ +.rule-group-new-page { + @apply p-5; +} + +/* 页面标题区域 */ +.rule-group-new-page .page-header { + @apply flex justify-between items-center mb-6; +} + +.rule-group-new-page .page-title { + @apply text-xl font-medium mb-1; +} + +.rule-group-new-page .page-subtitle { + @apply text-sm text-gray-500; +} + +/* 表单容器 */ +.rule-group-new-page .form-container { + @apply p-0; +} + +/* 提示信息区域 */ +.rule-group-new-page .info-message { + @apply flex items-center p-4 mb-5 bg-[rgba(0,104,74,0.05)] border border-[rgba(0,104,74,0.1)] rounded-md; +} + +.rule-group-new-page .info-message i { + @apply text-lg mr-3 text-[#00684a]; +} + +.rule-group-new-page .info-message p { + @apply text-sm text-gray-600 m-0; +} + +/* 表单区域 */ +.rule-group-new-page .form-section { + @apply bg-white rounded-md border border-gray-200 mb-6 transition-shadow duration-200 shadow-sm; +} + +.rule-group-new-page .form-section:hover { + @apply shadow-md; +} + +.rule-group-new-page .form-section-header { + @apply flex items-center p-4 border-b border-gray-100; +} + +.rule-group-new-page .form-section-header i { + @apply text-lg mr-2 text-[#00684a]; +} + +.rule-group-new-page .form-section-header h3 { + @apply text-base font-medium m-0; +} + +.rule-group-new-page .form-section-body { + @apply p-6; +} + +/* 表单元素 */ +.rule-group-new-page .form-group { + @apply mb-5; +} + +.rule-group-new-page .form-label { + @apply block text-sm font-medium mb-2 text-gray-700; +} + +.rule-group-new-page .form-label .required-mark { + @apply text-red-500 ml-1; +} + +.rule-group-new-page .form-input, +.rule-group-new-page .form-select, +.rule-group-new-page .form-textarea { + @apply w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-1 focus:ring-[#00684a] focus:border-[#00684a] outline-none text-sm transition-all duration-200; +} + +.rule-group-new-page .form-input:hover, +.rule-group-new-page .form-select:hover, +.rule-group-new-page .form-textarea:hover { + @apply border-[#00684a]; +} + +.rule-group-new-page .form-input.error, +.rule-group-new-page .form-select.error, +.rule-group-new-page .form-textarea.error { + @apply border-red-500 focus:ring-0 ; +} + +.rule-group-new-page .form-textarea { + @apply min-h-[120px] resize-y; +} + +.rule-group-new-page .form-tip { + @apply mt-1.5 text-xs text-gray-500; +} + +.rule-group-new-page .form-error { + @apply mt-1.5 text-xs text-red-500; +} + +/* 单选框组样式 */ +.rule-group-new-page .radio-group { + @apply flex gap-6; +} + +.rule-group-new-page .radio-item { + @apply flex items-center cursor-pointer; +} + +.rule-group-new-page .radio-input { + @apply mr-2 cursor-pointer; +} + +/* 行布局 */ +.rule-group-new-page .form-row { + @apply flex flex-col md:flex-row gap-6; +} + +.rule-group-new-page .form-col { + @apply flex-1 min-w-0; +} + +/* 按钮组 */ +.rule-group-new-page .form-actions { + @apply flex justify-end gap-3 mt-6 mb-10; +} + +/* 错误提示 */ +.rule-group-new-page .general-error { + @apply p-4 mb-6 bg-red-50 border border-red-100 rounded-md text-red-700 text-sm; +} + +/* 响应式调整 */ +@media (max-width: 768px) { + .rule-group-new-page .form-row { + @apply flex-col; + } + + .rule-group-new-page .page-header { + @apply flex-col items-start; + } + + .rule-group-new-page .page-header .header-actions { + @apply mt-4; + } +} \ No newline at end of file diff --git a/html/提示词-列表.html b/html/提示词-列表.html index 88215c4..dbbfaf7 100644 --- a/html/提示词-列表.html +++ b/html/提示词-列表.html @@ -4,8 +4,10 @@ 中国烟草AI合同及卷宗审核系统 - 提示词模板管理 - - + + +