From 540618b8caa2b16d64e949c3c402fd62e128420c Mon Sep 17 00:00:00 2001 From: yorn <1057707203@qq.com> Date: Thu, 27 Mar 2025 19:58:58 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=81=E8=A3=85=E5=85=AC=E5=85=B1=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=EF=BC=8C=E8=B0=83=E6=95=B4=E6=A0=B7=E5=BC=8F=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E5=B8=83=E5=B1=80=EF=BC=8C=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E9=A1=B5=E9=9D=A2=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/layout/Sidebar.tsx | 2 +- app/components/ui/Card.tsx | 8 +- app/components/ui/FileIcon.tsx | 45 ++ app/components/ui/FileTypeTag.tsx | 39 ++ app/components/ui/FilterPanel.tsx | 135 +++++ app/components/ui/Pagination.tsx | 36 +- app/components/ui/SearchBox.tsx | 23 +- app/components/ui/StatusBadge.tsx | 69 +++ app/components/ui/StatusDot.tsx | 15 +- app/components/ui/Table.tsx | 8 +- app/components/ui/Tag.tsx | 32 +- app/routes/rule-groups._index.tsx | 331 ++++++------ .../{rules.files.tsx => rules-files.tsx} | 449 ++++++++-------- app/routes/rules.$rulesId.tsx | 418 +++++++++++++++ app/routes/rules._index.tsx | 355 ++++++------- app/routes/rules.tsx | 2 +- app/styles/components/card.css | 2 +- app/styles/components/file-icon.css | 50 ++ app/styles/components/file-type-tag.css | 80 +++ app/styles/components/filter-panel.css | 57 +++ app/styles/components/pagination.css | 60 +++ app/styles/components/search-box.css | 80 +++ app/styles/components/status-badge.css | 72 +++ app/styles/components/status-dot.css | 83 +++ app/styles/components/table.css | 61 +++ app/styles/components/tag.css | 65 +++ app/styles/main.css | 17 + app/styles/pages/home.css | 4 +- app/styles/pages/rule-groups_index.css | 165 ++---- app/styles/pages/rules-files.css | 479 ++++++++++++++++++ app/styles/pages/rules_files.css | 240 --------- app/styles/pages/rules_index.css | 113 +++-- vite.config.ts | 5 + 33 files changed, 2613 insertions(+), 987 deletions(-) create mode 100644 app/components/ui/FileIcon.tsx create mode 100644 app/components/ui/FileTypeTag.tsx create mode 100644 app/components/ui/FilterPanel.tsx create mode 100644 app/components/ui/StatusBadge.tsx rename app/routes/{rules.files.tsx => rules-files.tsx} (50%) create mode 100644 app/routes/rules.$rulesId.tsx create mode 100644 app/styles/components/file-icon.css create mode 100644 app/styles/components/file-type-tag.css create mode 100644 app/styles/components/filter-panel.css create mode 100644 app/styles/components/pagination.css create mode 100644 app/styles/components/search-box.css create mode 100644 app/styles/components/status-badge.css create mode 100644 app/styles/components/status-dot.css create mode 100644 app/styles/components/tag.css create mode 100644 app/styles/pages/rules-files.css delete mode 100644 app/styles/pages/rules_files.css diff --git a/app/components/layout/Sidebar.tsx b/app/components/layout/Sidebar.tsx index a6429c0..c6ff34e 100644 --- a/app/components/layout/Sidebar.tsx +++ b/app/components/layout/Sidebar.tsx @@ -66,7 +66,7 @@ export function Sidebar({ onToggle, collapsed }: SidebarProps) { { id: 'rules-file', title: '评查文件列表', - path: '/rules/files', + path: '/rules-files', icon: 'ri-list-check-2' }, { diff --git a/app/components/ui/Card.tsx b/app/components/ui/Card.tsx index d5e925e..d28a5c5 100644 --- a/app/components/ui/Card.tsx +++ b/app/components/ui/Card.tsx @@ -20,11 +20,11 @@ export function Card({ noDivider = true, }: CardProps) { return ( -
+
{(title || extra) && ( -
+
{title && ( -
+
{icon && } {title}
@@ -36,7 +36,7 @@ export function Card({ )}
)} -
+
{children}
diff --git a/app/components/ui/FileIcon.tsx b/app/components/ui/FileIcon.tsx new file mode 100644 index 0000000..bd44c21 --- /dev/null +++ b/app/components/ui/FileIcon.tsx @@ -0,0 +1,45 @@ +/** + * 文件图标组件 + * 根据文件名后缀显示不同类型的图标 + */ +interface FileIconProps { + fileName: string; + className?: string; + size?: 'default' | 'sm' | 'lg'; +} + +export function FileIcon({ fileName, className = '', size }: FileIconProps) { + const sizeClass = size ? `file-icon-${size}` : ''; + + let fileType = 'unknown'; + let iconClass = 'ri-file-line'; + + if (fileName.endsWith('.pdf')) { + fileType = 'pdf'; + iconClass = 'ri-file-pdf-line'; + } else if (fileName.endsWith('.docx') || fileName.endsWith('.doc')) { + fileType = 'doc'; + iconClass = 'ri-file-word-2-line'; + } else if (fileName.endsWith('.xlsx') || fileName.endsWith('.xls')) { + fileType = 'xls'; + iconClass = 'ri-file-excel-2-line'; + } else if (fileName.endsWith('.pptx') || fileName.endsWith('.ppt')) { + fileType = 'ppt'; + iconClass = 'ri-file-ppt-2-line'; + } else if (fileName.endsWith('.zip') || fileName.endsWith('.rar')) { + fileType = 'zip'; + iconClass = 'ri-file-zip-line'; + } else if (fileName.endsWith('.png') || fileName.endsWith('.jpg') || fileName.endsWith('.jpeg') || fileName.endsWith('.gif')) { + fileType = 'img'; + iconClass = 'ri-image-line'; + } else if (fileName.endsWith('.txt')) { + fileType = 'txt'; + iconClass = 'ri-file-text-line'; + } + + return ( + + + + ); +} \ No newline at end of file diff --git a/app/components/ui/FileTypeTag.tsx b/app/components/ui/FileTypeTag.tsx new file mode 100644 index 0000000..68b7463 --- /dev/null +++ b/app/components/ui/FileTypeTag.tsx @@ -0,0 +1,39 @@ +import { FileType, FILE_TYPE_LABELS } from '~/routes/rules-files'; + +interface FileTypeTagProps { + fileType: FileType; + className?: string; + size?: 'default' | 'sm' | 'lg'; +} + +/** + * 文件类型标签组件 + * 根据文件类型显示不同样式的标签 + */ +export function FileTypeTag({ fileType, className = '', size }: FileTypeTagProps) { + const sizeClass = size ? `file-type-tag-${size}` : ''; + const tagClassName = `file-type-tag file-type-tag-${fileType.toLowerCase()} ${sizeClass} file-type-tag-with-icon ${className}`; + + // 根据文件类型选择图标 + const getFileTypeIcon = () => { + switch (fileType) { + case FileType.CONTRACT: + return ; + case FileType.LICENSE: + return ; + case FileType.PUNISHMENT: + return ; + case FileType.REPORT: + return ; + default: + return ; + } + }; + + return ( + + {getFileTypeIcon()} + {FILE_TYPE_LABELS[fileType]} + + ); +} \ No newline at end of file diff --git a/app/components/ui/FilterPanel.tsx b/app/components/ui/FilterPanel.tsx new file mode 100644 index 0000000..f47c0e1 --- /dev/null +++ b/app/components/ui/FilterPanel.tsx @@ -0,0 +1,135 @@ +import { SearchBox } from '~/components/ui/SearchBox'; + +interface FilterOption { + value: string; + label: string; +} + +interface FilterSelectProps { + label: string; + name: string; + value: string; + options: FilterOption[]; + onChange: (e: React.ChangeEvent) => void; + className?: string; +} + +/** + * 筛选下拉选择框组件 + */ +const FilterSelect = ({ label, name, value, options, onChange, className = '' }: FilterSelectProps) => ( +
+ + +
+); + +interface FilterPanelProps { + children: React.ReactNode; + className?: string; + actions?: React.ReactNode; // 按钮组,如搜索、重置按钮 + noActionDivider?: boolean; // 是否取消按钮组与内容之间的分割线 +} + +/** + * 通用筛选面板组件 + * 用于包裹筛选控件 + * + * 使用示例: + * ```tsx + * + * + * + * + * } + * > + * + * + * + */ +export function FilterPanel({ children, className = '', actions, noActionDivider = false }: FilterPanelProps) { + return ( +
+
+ {children} + {actions && ( +
+ {actions} +
+ )} +
+
+ ); +} + +interface SearchFilterProps { + label: string; + placeholder: string; + value: string; + onSearch: (value: string) => void; + buttonText?: string; + className?: string; + instantSearch?: boolean; // 是否启用即时搜索(输入内容时立即搜索) +} + +/** + * 搜索筛选组件 + * + * 使用示例: + * ```tsx + * // 带搜索按钮的搜索框 + * + * + * // 即时搜索的搜索框(无按钮) + * + */ +export function SearchFilter({ + label, + placeholder, + value, + onSearch, + buttonText = "搜索", + className = '', + instantSearch = false +}: SearchFilterProps) { + return ( +
+ + +
+ ); +} + +// 导出筛选下拉框组件 +export { FilterSelect }; \ No newline at end of file diff --git a/app/components/ui/Pagination.tsx b/app/components/ui/Pagination.tsx index 12ceab3..434acbc 100644 --- a/app/components/ui/Pagination.tsx +++ b/app/components/ui/Pagination.tsx @@ -1,5 +1,4 @@ // app/components/ui/Pagination.tsx -import React from 'react'; interface PaginationProps { currentPage: number; @@ -8,6 +7,8 @@ interface PaginationProps { onChange: (page: number) => void; onPageSizeChange?: (pageSize: number) => void; pageSizeOptions?: number[]; + showTotal?: boolean; + showPageSizeChanger?: boolean; } export function Pagination({ @@ -16,7 +17,9 @@ export function Pagination({ pageSize, onChange, onPageSizeChange, - pageSizeOptions = [10, 20, 50] + pageSizeOptions = [10, 20, 50], + showTotal = true, + showPageSizeChanger = true }: PaginationProps) { const totalPages = Math.ceil(total / pageSize); @@ -51,19 +54,24 @@ export function Pagination({ }; return ( -
- {onPageSizeChange && ( +
+ {showTotal && (
- 共 {total} 条 - + 共 {total} 条 + {onPageSizeChange && showPageSizeChanger && ( +
+ +
+ )}
)} diff --git a/app/components/ui/SearchBox.tsx b/app/components/ui/SearchBox.tsx index 0176d66..6e1aa08 100644 --- a/app/components/ui/SearchBox.tsx +++ b/app/components/ui/SearchBox.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { Button } from './Button'; interface SearchBoxProps { placeholder?: string; @@ -32,21 +31,29 @@ export function SearchBox({ } }; + const isIconOnly = buttonText === ''; + const isFilterControl = className.includes('filter-control'); + const searchBoxClass = `search-box ${className} ${isFilterControl ? 'search-box-row' : ''}`; + return ( -
- + - {buttonText && !className.includes('form-input-only') && ( - + {!className.includes('form-input-only') && ( + )}
); diff --git a/app/components/ui/StatusBadge.tsx b/app/components/ui/StatusBadge.tsx new file mode 100644 index 0000000..b499ae7 --- /dev/null +++ b/app/components/ui/StatusBadge.tsx @@ -0,0 +1,69 @@ +import { ReviewStatus, REVIEW_STATUS_LABELS } from '~/routes/rules-files'; + +interface StatusBadgeProps { + status: ReviewStatus; + issueCount?: number; + className?: string; + size?: 'default' | 'sm' | 'lg'; + clickable?: boolean; + onClick?: () => void; +} + +/** + * 文件评查状态标签组件 + * 根据评查状态显示不同样式的标签 + */ +export function StatusBadge({ + status, + issueCount = 0, + className = '', + size, + clickable = false, + onClick +}: StatusBadgeProps) { + const statusMap: Record = { + [ReviewStatus.PASS]: 'success', + [ReviewStatus.WARNING]: 'warning', + [ReviewStatus.FAIL]: 'error', + [ReviewStatus.PENDING]: 'processing' + }; + + const badgeType = statusMap[status] || 'default'; + const sizeClass = size ? `status-badge-${size}` : ''; + const clickableClass = clickable ? 'status-badge-clickable' : ''; + + // 根据状态选择图标 + const getStatusIcon = () => { + switch (status) { + case ReviewStatus.PASS: + return ; + case ReviewStatus.WARNING: + return ; + case ReviewStatus.FAIL: + return ; + case ReviewStatus.PENDING: + return ; + default: + return null; + } + }; + + const handleClick = () => { + if (clickable && onClick) { + onClick(); + } + }; + + return ( + + {getStatusIcon()} + {REVIEW_STATUS_LABELS[status]} + {issueCount > 0 && ` (${issueCount})`} + + ); +} \ No newline at end of file diff --git a/app/components/ui/StatusDot.tsx b/app/components/ui/StatusDot.tsx index ea99793..658b69f 100644 --- a/app/components/ui/StatusDot.tsx +++ b/app/components/ui/StatusDot.tsx @@ -4,12 +4,16 @@ interface StatusDotProps { status: StatusType | boolean; text?: string; className?: string; + size?: 'default' | 'sm' | 'lg'; + pulse?: boolean; } export function StatusDot({ status, text, - className = '' + className = '', + size = 'default', + pulse = false }: StatusDotProps) { // 如果status是布尔值,则转换为对应的状态类型 const statusType = typeof status === 'boolean' @@ -23,11 +27,14 @@ export function StatusDot({ statusType === 'warning' ? '警告' : statusType === 'processing' ? '处理中' : '未知' ); + + const sizeClass = size !== 'default' ? `status-dot-${size}` : ''; + const pulseClass = pulse ? 'status-dot-pulse' : ''; return ( - - - {statusText} + + + {statusText} ); } \ No newline at end of file diff --git a/app/components/ui/Table.tsx b/app/components/ui/Table.tsx index 796c3ad..0bf37bd 100644 --- a/app/components/ui/Table.tsx +++ b/app/components/ui/Table.tsx @@ -39,8 +39,8 @@ export function Table>({ }; return ( -
- +
+
{columns.map((column, index) => ( @@ -86,7 +86,7 @@ export function Table>({ @@ -96,7 +96,7 @@ export function Table>({
{emptyText}
{loading && ( -
+
加载中... diff --git a/app/components/ui/Tag.tsx b/app/components/ui/Tag.tsx index fde0db7..91e1a69 100644 --- a/app/components/ui/Tag.tsx +++ b/app/components/ui/Tag.tsx @@ -1,17 +1,43 @@ import React from 'react'; -export type TagColor = 'blue' | 'green' | 'cyan' | 'purple' | 'orange' | 'red' | 'default'; +export type TagColor = 'blue' | 'green' | 'red' | 'yellow' | 'purple' | 'gray' | 'cyan' | 'orange'; interface TagProps { color?: TagColor; children: React.ReactNode; className?: string; + size?: 'default' | 'sm' | 'lg'; + closable?: boolean; + clickable?: boolean; + onClose?: () => void; } -export function Tag({ color = 'default', children, className = '' }: TagProps) { +export function Tag({ + color = 'blue', + children, + className = '', + size, + closable = false, + clickable = false, + onClose +}: TagProps) { + const sizeClass = size ? `tag-${size}` : ''; + const closableClass = closable ? 'tag-closable' : ''; + const clickableClass = clickable ? 'tag-clickable' : ''; + + const handleClose = (e: React.MouseEvent) => { + e.stopPropagation(); + onClose?.(); + }; + return ( - + {children} + {closable && ( + + + + )} ); } \ No newline at end of file diff --git a/app/routes/rule-groups._index.tsx b/app/routes/rule-groups._index.tsx index 608d5ad..3855d0e 100644 --- a/app/routes/rule-groups._index.tsx +++ b/app/routes/rule-groups._index.tsx @@ -1,11 +1,13 @@ import { json, type MetaFunction } from "@remix-run/node"; -import { useLoaderData, Link, useNavigate } from "@remix-run/react"; +import { useLoaderData, Link, useNavigate, useSearchParams } from "@remix-run/react"; import { useState } from "react"; import indexStyles from "~/styles/pages/rule-groups_index.css?url"; import { Card } from "~/components/ui/Card"; import { Button } from "~/components/ui/Button"; -import { SearchBox } from "~/components/ui/SearchBox"; import { StatusDot } from "~/components/ui/StatusDot"; +import { Table } from "~/components/ui/Table"; +import { FilterPanel, FilterSelect, SearchFilter } from "~/components/ui/FilterPanel"; +import { Pagination } from "~/components/ui/Pagination"; // 定义数据类型 interface RuleGroup { @@ -103,8 +105,7 @@ export async function loader() { export default function RuleGroupsIndex() { const { groups } = useLoaderData(); const navigate = useNavigate(); - const [searchText, setSearchText] = useState(""); - const [groupCode, setGroupCode] = useState(""); + const [searchParams, setSearchParams] = useSearchParams(); const [expandedGroups, setExpandedGroups] = useState([]); // 处理展开/收起 @@ -131,14 +132,44 @@ export default function RuleGroupsIndex() { // 处理搜索名称 const handleNameSearch = (value: string) => { - setSearchText(value); - // 实际项目中这里可能需要调用API或过滤本地数据 + const newParams = new URLSearchParams(searchParams); + if (value) { + newParams.set('name', value); + } else { + newParams.delete('name'); + } + newParams.set('page', '1'); + setSearchParams(newParams); }; // 处理搜索编码 const handleCodeSearch = (value: string) => { - setGroupCode(value); - // 实际项目中这里可能需要调用API或过滤本地数据 + const newParams = new URLSearchParams(searchParams); + if (value) { + newParams.set('code', value); + } else { + newParams.delete('code'); + } + 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()); }; // 处理表格数据,包括父子级关系 @@ -158,6 +189,98 @@ export default function RuleGroupsIndex() { return result; }); + // 定义表格列配置 + const columns = [ + { + title: "分组名称", + key: "name", + width: "400px", + render: (_: unknown, record: (RuleGroup & { isParent?: boolean, parentId?: string })) => ( +
+ {record.isParent && ( + toggleGroup(record.id)} + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + toggleGroup(record.id); + } + }} + > + + + )} + + {record.name} + + + {record.isParent ? '一级分组' : '二级分组'} + +
+ ) + }, + { + title: "分组编码", + key: "code", + render: (_: unknown, record: RuleGroup) => record.code + }, + { + title: "评查点数量", + key: "ruleCount", + render: (_: unknown, record: RuleGroup) => ( + <> + + {record.ruleCount} + + {record.subGroupCount > 0 && ( + + | 子分组: {record.subGroupCount} + + )} + + ) + }, + { + title: "状态", + key: "status", + render: (_: unknown, record: RuleGroup) => ( + + ) + }, + { + title: "创建时间", + key: "createdAt", + render: (_: unknown, record: RuleGroup) => record.createdAt + }, + { + title: "操作", + key: "operation", + width: "180px", + render: (_: unknown, record: RuleGroup) => ( + <> + + + + ) + } + ]; + return (
{/* 页面头部 */} @@ -190,146 +313,70 @@ export default function RuleGroupsIndex() {
- {/* 搜索栏 */} - -
-
- - -
-
- - -
-
- - -
-
- -
-
-
- - {/* 数据表格 */} - -
- - - - - - - - - - - - - {processedData.map((item) => ( - - - - - - - - - ))} - -
分组名称分组编码评查点数量状态创建时间操作
-
- {item.isParent && ( - toggleGroup(item.id)} - role="button" - tabIndex={0} - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - toggleGroup(item.id); - } - }} - > - - - )} - - {item.name} - - - {item.isParent ? '一级分组' : '二级分组'} - -
-
{item.code} - - {item.ruleCount} - - {item.subGroupCount > 0 && ( - - | 子分组: {item.subGroupCount} - - )} - - - {item.createdAt} - - -
-
+ + } + noActionDivider={true} + > + - {/* 分页 */} -
-
- 共 {groups.length} 条记录,每页显示 10 条 -
-
- - - -
-
+ + + + + + {/* 数据表格 - 使用Table组件 */} + + + + {/* 分页 - 使用Pagination组件 */} + {}} + showTotal={true} + /> ); diff --git a/app/routes/rules.files.tsx b/app/routes/rules-files.tsx similarity index 50% rename from app/routes/rules.files.tsx rename to app/routes/rules-files.tsx index dd827dd..2d68e1e 100644 --- a/app/routes/rules.files.tsx +++ b/app/routes/rules-files.tsx @@ -1,15 +1,18 @@ import { json, type MetaFunction, type LoaderFunctionArgs } from "@remix-run/node"; -import { useLoaderData, useSearchParams, useSubmit } from "@remix-run/react"; +import { useLoaderData, useSearchParams } from "@remix-run/react"; import { Button } from "~/components/ui/Button"; import { Card } from "~/components/ui/Card"; +import { FileIcon } from "~/components/ui/FileIcon"; +import { FilterPanel, FilterSelect, SearchFilter } from "~/components/ui/FilterPanel"; +import { Pagination } from "~/components/ui/Pagination"; import { Table } from "~/components/ui/Table"; -import { SearchBox } from "~/components/ui/SearchBox"; -import rulesFilesStyles from "~/styles/pages/rules_files.css?url"; +import rulesFilesStyles from "~/styles/pages/rules-files.css?url"; export const links = () => [ { rel: "stylesheet", href: rulesFilesStyles } ]; + export const handle = { breadcrumb: "评查文件列表" }; @@ -196,7 +199,7 @@ export async function loader({ request }: LoaderFunctionArgs) { return fileDate >= today; }); break; - case DateRange.WEEK: + case DateRange.WEEK: { const weekStart = new Date(today); weekStart.setDate(today.getDate() - today.getDay()); filteredFiles = filteredFiles.filter(file => { @@ -204,13 +207,15 @@ export async function loader({ request }: LoaderFunctionArgs) { return fileDate >= weekStart; }); break; - case DateRange.MONTH: + } + case DateRange.MONTH: { const monthStart = new Date(today.getFullYear(), today.getMonth(), 1); filteredFiles = filteredFiles.filter(file => { const fileDate = new Date(file.uploadTime.split(' ')[0]); return fileDate >= monthStart; }); break; + } } } @@ -264,18 +269,20 @@ export async function loader({ request }: LoaderFunctionArgs) { } } +// 提取renderErrorBoundary函数作为命名导出 export function ErrorBoundary() { return (
-

出错了

+

出错了

加载评查文件列表时发生错误。请稍后再试,或联系管理员。

); } -export default function ReviewFilesList() { - const { files, totalCount, currentPage, pageSize, totalPages } = useLoaderData(); +// 在文件中定义一个与路由文件名匹配的命名函数组件 +export default function RulesFiles() { + const { files, totalCount, currentPage, pageSize } = useLoaderData(); const [searchParams, setSearchParams] = useSearchParams(); const handleFilterChange = (e: React.ChangeEvent) => { @@ -314,6 +321,14 @@ export default function ReviewFilesList() { setSearchParams(newParams); }; + // 添加页码大小变更处理函数 + const handlePageSizeChange = (size: number) => { + const newParams = new URLSearchParams(searchParams); + newParams.set('pageSize', size.toString()); + newParams.set('page', '1'); // 改变每页条数时重置为第一页 + setSearchParams(newParams); + }; + // 渲染问题摘要 const renderIssues = (issues: ReviewFile['issues']) => { if (issues.length === 0) { @@ -327,7 +342,7 @@ export default function ReviewFilesList() { return (
{issues.slice(0, 3).map((issue, index) => ( -
+
{issue.message}
@@ -336,29 +351,145 @@ export default function ReviewFilesList() { ); }; - // 渲染文件图标 - const renderFileIcon = (fileName: string) => { - if (fileName.endsWith('.pdf')) { - return ; - } else if (fileName.endsWith('.docx') || fileName.endsWith('.doc')) { - return ; - } else if (fileName.endsWith('.xlsx') || fileName.endsWith('.xls')) { - return ; - } else { - return ; + // 文件类型选项 + const fileTypeOptions = Object.keys(FILE_TYPE_LABELS).map(type => ({ + value: type, + label: FILE_TYPE_LABELS[type as FileType] + })); + + // 评查状态选项 + const reviewStatusOptions = Object.keys(REVIEW_STATUS_LABELS).map(status => ({ + value: status, + label: REVIEW_STATUS_LABELS[status as ReviewStatus] + })); + + // 时间范围选项 + const dateRangeOptions = [ + { value: DateRange.TODAY, label: '今天' }, + { value: DateRange.WEEK, label: '本周' }, + { value: DateRange.MONTH, label: '本月' }, + { value: DateRange.CUSTOM, label: '自定义时间段' } + ]; + + // 获取文件状态对应的图标和类名 + const getStatusInfo = (status: ReviewStatus) => { + switch (status) { + case ReviewStatus.PASS: + return { icon: "ri-checkbox-circle-line", className: "success" }; + case ReviewStatus.WARNING: + return { icon: "ri-alert-line", className: "warning" }; + case ReviewStatus.FAIL: + return { icon: "ri-close-circle-line", className: "error" }; + case ReviewStatus.PENDING: + return { icon: "ri-time-line", className: "processing" }; + default: + return { icon: "", className: "default" }; } }; + // 定义表格列配置 + const columns = [ + { + title: "文件名称", + key: "fileName", + width: "30%", + render: (_: unknown, file: ReviewFile) => ( +
+ +
+
{file.fileName}
+
+ {file.fileType === FileType.CONTRACT && "合同编号:"} + {file.fileType === FileType.LICENSE && "许可证号:"} + {file.fileType === FileType.PUNISHMENT && "文号:"} + {file.fileType === FileType.REPORT && "报表编号:"} + {file.fileCode} +
+
+
+ ) + }, + { + title: "文件类型", + key: "fileType", + width: "12%", + render: (_: unknown, file: ReviewFile) => ( + + {file.fileType === FileType.CONTRACT && } + {file.fileType === FileType.LICENSE && } + {file.fileType === FileType.PUNISHMENT && } + {file.fileType === FileType.REPORT && } + {file.fileType === FileType.OTHER && } + {FILE_TYPE_LABELS[file.fileType]} + + ) + }, + { + title: "上传时间", + key: "uploadTime", + width: "12%", + render: (_: unknown, file: ReviewFile) => ( +
+ {file.uploadTime.split(' ')[0]} +
+ {file.uploadTime.split(' ')[1]} +
+ ) + }, + { + title: "评查状态", + key: "reviewStatus", + width: "12%", + render: (_: unknown, file: ReviewFile) => { + const statusInfo = getStatusInfo(file.reviewStatus); + return ( + + + {REVIEW_STATUS_LABELS[file.reviewStatus]} + {file.issueCount > 0 && ` (${file.issueCount})`} + + ); + } + }, + { + title: "问题摘要", + key: "issues", + width: "20%", + render: (_: unknown, file: ReviewFile) => renderIssues(file.issues) + }, + { + title: "操作", + key: "operation", + width: "14%", + render: (_: unknown, file: ReviewFile) => ( + <> + {file.reviewStatus === ReviewStatus.PENDING ? ( + + ) : ( + + )} + + + ) + } + ]; + return (
{/* 页面头部 */}
-

评查文件列表

+

评查文件列表

总文件数: - {totalCount} + {totalCount}
{/* 筛选区域 */} - -
-
-
文件类型
- -
- -
-
评查状态
- -
- -
-
时间范围
- -
- -
-
搜索
-
- -
-
+ + + + + + + + -
- -
-
-
+ + {/* 文件列表 */} - -
- - - - - - - - - - - - {files.length > 0 ? ( - files.map((file) => ( - - - - - - - - - )) - ) : ( - - - - )} - -
文件名称文件类型上传时间评查状态问题摘要操作
-
- {renderFileIcon(file.fileName)} -
-
{file.fileName}
-
- {file.fileType === FileType.CONTRACT && "合同编号:"} - {file.fileType === FileType.LICENSE && "许可证号:"} - {file.fileType === FileType.PUNISHMENT && "文号:"} - {file.fileType === FileType.REPORT && "报表编号:"} - {file.fileCode} -
-
-
-
- - {file.fileType === FileType.CONTRACT && } - {file.fileType === FileType.LICENSE && } - {file.fileType === FileType.PUNISHMENT && } - {file.fileType === FileType.REPORT && } - {file.fileType === FileType.OTHER && } - {FILE_TYPE_LABELS[file.fileType]} - - - {file.uploadTime.split(' ')[0]} -
- {file.uploadTime.split(' ')[1]} -
- - {file.reviewStatus === ReviewStatus.PASS && } - {file.reviewStatus === ReviewStatus.WARNING && } - {file.reviewStatus === ReviewStatus.FAIL && } - {file.reviewStatus === ReviewStatus.PENDING && } - {REVIEW_STATUS_LABELS[file.reviewStatus]} - {file.issueCount > 0 && ` (${file.issueCount})`} - - - {renderIssues(file.issues)} - - {file.reviewStatus === ReviewStatus.PENDING ? ( - - ) : ( - - )} - -
- 暂无文件数据 -
+ + - {/* 分页 */} + {/* 分页组件 */} {totalCount > 0 && ( -
- - - {Array.from({ length: Math.min(totalPages, 5) }, (_, i) => { - // 显示当前页附近的页码,最多显示5个 - let pageNum; - if (totalPages <= 5) { - // 总页数少于5,直接显示所有页码 - pageNum = i + 1; - } else if (currentPage <= 3) { - // 当前页靠近开始 - pageNum = i + 1; - } else if (currentPage >= totalPages - 2) { - // 当前页靠近结尾 - pageNum = totalPages - 4 + i; - } else { - // 当前页在中间 - pageNum = currentPage - 2 + i; - } - - return ( - - ); - })} - - -
+ )} diff --git a/app/routes/rules.$rulesId.tsx b/app/routes/rules.$rulesId.tsx new file mode 100644 index 0000000..731fc2d --- /dev/null +++ b/app/routes/rules.$rulesId.tsx @@ -0,0 +1,418 @@ +import React, { useState } from 'react'; +import { redirect, type MetaFunction, type ActionFunctionArgs, type LoaderFunctionArgs } from '@remix-run/node'; +import { useLoaderData, useActionData, Form, useSubmit, useNavigate } from '@remix-run/react'; +import { Button } from '~/components/ui/Button'; +import { Card } from '~/components/ui/Card'; +import { Breadcrumb } from '~/components/layout/Breadcrumb'; +import type { Rule, RuleType, RulePriority } from '~/models/rule'; + +export const meta: MetaFunction = () => { + return [ + { title: "中国烟草AI合同及卷宗审核系统 - 评查规则详情" }, + { name: "description", content: "评查规则详情编辑页面" } + ]; +}; + +export const handle = { + breadcrumb: '编辑评查点' +}; + +interface LoaderData { + rule: Rule; + ruleTypes: { label: string; value: RuleType }[]; + rulePriorities: { label: string; value: RulePriority }[]; + groupOptions: { label: string; value: string }[]; +} + +export async function loader({ params }: LoaderFunctionArgs) { + const { ruleId } = params; + + // 判断是否为新建规则 + const isNewRule = ruleId === 'new'; + + // 模拟数据,实际项目中应从API获取 + const rule: Rule = isNewRule ? { + id: '', + name: '', + description: '', + content: '', + type: 'text', + priority: 'medium', + groupId: '', + groupName: '', + isActive: true, + createdAt: '', + updatedAt: '' + } : { + id: ruleId, + name: '许可证编号格式检查', + description: '检查烟草专卖零售许可证编号是否符合"烟零许(年份)序号号"的标准格式', + content: '许可证编号应当符合"烟零许(年份)序号号"的标准格式,如"烟零许(2023)12345号"', + type: 'regex', + priority: 'high', + groupId: '1', + groupName: '专卖许可证规则组', + isActive: true, + createdAt: '2023-10-15 09:30', + updatedAt: '2023-12-10 14:20' + }; + + // 规则类型选项 + const ruleTypes = [ + { label: '文本匹配', value: 'text' }, + { label: '正则表达式', value: 'regex' }, + { label: '数值范围', value: 'range' }, + { label: '日期检查', value: 'date' }, + { label: 'AI智能检查', value: 'ai' } + ]; + + // 规则优先级选项 + const rulePriorities = [ + { label: '低', value: 'low' }, + { label: '中', value: 'medium' }, + { label: '高', value: 'high' }, + { label: '关键', value: 'critical' } + ]; + + // 规则组选项 + const groupOptions = [ + { label: '专卖许可证规则组', value: '1' }, + { label: '合同协议规则组', value: '2' }, + { label: '财务票据规则组', value: '3' }, + { label: '采购订单规则组', value: '4' }, + { label: '销售报表规则组', value: '5' } + ]; + + return Response.json({ + rule, + ruleTypes, + rulePriorities, + groupOptions + }); +} + +interface ActionData { + success?: boolean; + errors?: { + name?: string; + description?: string; + content?: string; + type?: string; + priority?: string; + groupId?: string; + general?: string; + }; +} + +export async function action({ request, params }: ActionFunctionArgs) { + const { ruleId } = params; + const formData = await request.formData(); + const isNewRule = ruleId === 'new'; + + // 获取表单数据 + const name = formData.get('name')?.toString() || ''; + const description = formData.get('description')?.toString() || ''; + const content = formData.get('content')?.toString() || ''; + const type = formData.get('type')?.toString() || ''; + const priority = formData.get('priority')?.toString() || ''; + const groupId = formData.get('groupId')?.toString() || ''; + const isActive = formData.get('isActive') === 'true'; + + // 表单验证 + const errors: ActionData['errors'] = {}; + + if (!name.trim()) { + errors.name = '规则名称不能为空'; + } + + if (!content.trim()) { + errors.content = '规则内容不能为空'; + } + + if (!type) { + errors.type = '必须选择规则类型'; + } + + if (!priority) { + errors.priority = '必须选择规则优先级'; + } + + if (!groupId) { + errors.groupId = '必须选择规则所属组'; + } + + if (Object.keys(errors).length > 0) { + return Response.json({ errors }); + } + + // 模拟API保存操作,实际项目中应调用API + try { + // 在这里调用API进行保存 + console.log('保存规则:', { + id: isNewRule ? 'new-id' : ruleId, + name, + description, + content, + type, + priority, + groupId, + isActive + }); + + // 成功后重定向到规则列表页 + return redirect('/rules'); + } catch (error) { + return Response.json({ + errors: { + general: '保存规则失败,请重试' + } + }); + } +} + +export default function RuleDetail() { + const { rule, ruleTypes, rulePriorities, groupOptions } = useLoaderData(); + const actionData = useActionData(); + const navigate = useNavigate(); + const submit = useSubmit(); + + const [formData, setFormData] = useState({ + name: rule.name, + description: rule.description, + content: rule.content, + type: rule.type, + priority: rule.priority, + groupId: rule.groupId, + isActive: rule.isActive + }); + + const isNewRule = !rule.id; + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData(prev => ({ ...prev, [name]: value })); + }; + + const handleSwitchChange = (name: string, checked: boolean) => { + setFormData(prev => ({ ...prev, [name]: checked })); + }; + + const handleCancel = () => { + navigate('/rules'); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + // 使用useSubmit提交表单 + const formElement = e.currentTarget; + submit(formElement, { method: 'post' }); + }; + + return ( +
+ + +
+

{isNewRule ? '新增评查规则' : '编辑评查规则'}

+
+ + +
+ {actionData?.errors?.general && ( +
+ + {actionData.errors.general} +
+ )} + +
+

基本信息

+ +
+
+ + + {actionData?.errors?.name && ( +
{actionData.errors.name}
+ )} +
+ +
+ + + {actionData?.errors?.groupId && ( +
{actionData.errors.groupId}
+ )} +
+
+ +
+
+ + +
+
+
+ +
+

规则设置

+ +
+
+ + + {actionData?.errors?.type && ( +
{actionData.errors.type}
+ )} +
+ +
+ + + {actionData?.errors?.priority && ( +
{actionData.errors.priority}
+ )} +
+ +
+ +
+ + + {formData.isActive ? '启用' : '禁用'} +
+
+
+ +
+
+ + + {actionData?.errors?.content && ( +
{actionData.errors.content}
+ )} + {formData.type === 'regex' && ( +
+ + 输入正则表达式,用于匹配文档内容 +
+ )} + {formData.type === 'ai' && ( +
+ + 请使用自然语言描述规则检查的要求,AI将自动理解并执行检查 +
+ )} +
+
+
+ +
+

测试工具

+ +
+
+ + +
+ +
+ +
+
+
+ +
+ + +
+ +
+
+ ); +} \ No newline at end of file diff --git a/app/routes/rules._index.tsx b/app/routes/rules._index.tsx index 71f5b08..24afe01 100644 --- a/app/routes/rules._index.tsx +++ b/app/routes/rules._index.tsx @@ -3,7 +3,6 @@ import { json, type MetaFunction, type LoaderFunctionArgs, redirect } from "@rem import { useLoaderData, useSearchParams, useSubmit } from "@remix-run/react"; import { Button } from '~/components/ui/Button'; import { Card } from '~/components/ui/Card'; -import { SearchBox } from '~/components/ui/SearchBox'; import { Tag } from '~/components/ui/Tag'; import { StatusDot } from '~/components/ui/StatusDot'; import rulesStyles from "~/styles/pages/rules_index.css?url"; @@ -11,14 +10,16 @@ import type { Rule } from '~/models/rule'; import { RULE_TYPE_LABELS, RULE_TYPE_COLORS, RULE_PRIORITY_LABELS, RULE_PRIORITY_COLORS } from '~/models/rule'; import type { TagColor } from '~/components/ui/Tag'; import { Link } from '@remix-run/react'; - +import { Table } from '~/components/ui/Table'; +import { FilterPanel, FilterSelect, SearchFilter } from '~/components/ui/FilterPanel'; +import { Pagination } from '~/components/ui/Pagination'; export const links = () => [ { rel: "stylesheet", href: rulesStyles } ]; -export const handle = { - breadcrumb: "评查点列表" -}; +// export const handle = { +// breadcrumb: "评查点列表" +// }; export const meta: MetaFunction = () => { return [ @@ -301,8 +302,8 @@ const priorityLabels = { 'low': '低' }; -export default function RulesList() { - const { rules, groups, totalCount, currentPage, pageSize, totalPages } = useLoaderData(); +export default function RulesIndex() { + const { rules, groups, totalCount, currentPage, pageSize } = useLoaderData(); const [searchParams, setSearchParams] = useSearchParams(); const submit = useSubmit(); @@ -371,213 +372,179 @@ export default function RulesList() { setSearchParams(newParams); }; - const handlePageSizeChange = (e: React.ChangeEvent) => { - const newPageSize = e.target.value; + const handlePageSizeChange = (size: number) => { const newParams = new URLSearchParams(searchParams); - newParams.set('pageSize', newPageSize); + newParams.set('pageSize', size.toString()); newParams.set('page', '1'); // 更改每页条数时,重置到第一页 setSearchParams(newParams); }; + // 处理重置筛选 + const handleReset = () => { + setSearchParams(new URLSearchParams()); + }; + + // 定义表格列配置 + const columns = [ + { + title: "评查点编码", + dataIndex: "code" as keyof Rule, + key: "code", + align: "center" as const + }, + { + title: "评查点名称", + dataIndex: "name" as keyof Rule, + key: "name", + align: "center" as const + }, + { + title: "评查点类型", + key: "ruleType", + align: "center" as const, + render: (_: unknown, record: Rule) => { + const typeColor = RULE_TYPE_COLORS[record.ruleType] as TagColor; + return ( + + {typeLabels[record.ruleType as keyof typeof typeLabels] || RULE_TYPE_LABELS[record.ruleType]} + + ); + } + }, + { + title: "所属规则组", + dataIndex: "groupName" as keyof Rule, + key: "groupName", + align: "center" as const + }, + { + title: "优先级", + key: "priority", + align: "center" as const, + render: (_: unknown, record: Rule) => { + const priorityColor = RULE_PRIORITY_COLORS[record.priority] as TagColor; + return ( + + {priorityLabels[record.priority as keyof typeof priorityLabels] || RULE_PRIORITY_LABELS[record.priority]} + + ); + } + }, + { + title: "状态", + key: "isActive", + align: "center" as const, + className: "status-column", + render: (_: unknown, record: Rule) => ( + + ) + }, + { + title: "创建时间", + dataIndex: "createdAt" as keyof Rule, + key: "createdAt", + align: "center" as const + }, + { + title: "操作", + key: "operation", + align: "center" as const, + render: (_: unknown, record: Rule) => ( +
+ + 编辑 + + + +
+ ) + } + ]; + return (
{/* 页面头部 */}

评查点管理

-
{/* 筛选区域 */} - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
+ + + + ({ value: group.id, label: group.name }))} + onChange={handleFilterChange} + className="mr-3 w-80" + /> + + + + + - {/* 评查点列表 */} + + {/* 评查点列表 - 使用Table组件 */} -
-
- - - - - - - - - - - - - - {rules.length > 0 ? ( - rules.map((rule) => { - const typeColor = RULE_TYPE_COLORS[rule.ruleType] as TagColor; - const priorityColor = RULE_PRIORITY_COLORS[rule.priority] as TagColor; - - return ( - - - - - - - - - - - ); - }) - ) : ( - - - - )} - -
评查点编码评查点名称评查点类型所属规则组优先级状态创建时间操作
{rule.code}{rule.name} - - {typeLabels[rule.ruleType as keyof typeof typeLabels] || RULE_TYPE_LABELS[rule.ruleType]} - - {rule.groupName} - - {priorityLabels[rule.priority as keyof typeof priorityLabels] || RULE_PRIORITY_LABELS[rule.priority]} - - - - {rule.createdAt} - - 编辑 - - - -
- 暂无评查点数据 -
-
+ {/* 分页 */} {totalCount > 0 && ( -
-
- 共 {totalCount} 条 - -
- -
- - - {Array.from({ length: Math.min(totalPages, 5) }, (_, i) => { - // 显示当前页附近的页码,最多显示5个 - let pageNum; - if (totalPages <= 5) { - // 总页数少于5,直接显示所有页码 - pageNum = i + 1; - } else if (currentPage <= 3) { - // 当前页靠近开始 - pageNum = i + 1; - } else if (currentPage >= totalPages - 2) { - // 当前页靠近结尾 - pageNum = totalPages - 4 + i; - } else { - // 当前页在中间 - pageNum = currentPage - 2 + i; - } - - return ( - - ); - })} - - -
-
+ )} diff --git a/app/routes/rules.tsx b/app/routes/rules.tsx index 90a0010..d6446d1 100644 --- a/app/routes/rules.tsx +++ b/app/routes/rules.tsx @@ -16,7 +16,7 @@ export const meta: MetaFunction = () => { }; export const handle = { - breadcrumb: "评查规则库" + breadcrumb: "评查点列表" }; /** diff --git a/app/styles/components/card.css b/app/styles/components/card.css index 26fcc70..87dd79f 100644 --- a/app/styles/components/card.css +++ b/app/styles/components/card.css @@ -26,7 +26,7 @@ /* 卡片内容 */ .card-body { - @apply p-5; + @apply p-1; } /* 卡片底部 */ diff --git a/app/styles/components/file-icon.css b/app/styles/components/file-icon.css new file mode 100644 index 0000000..80ac539 --- /dev/null +++ b/app/styles/components/file-icon.css @@ -0,0 +1,50 @@ +/** + * 文件图标组件样式 + */ + +/* 文件图标容器 */ +.file-icon { + @apply inline-flex items-center justify-center w-10 h-10 rounded-md text-xl; +} + +/* 文件图标类型 */ +.file-icon-doc { + @apply bg-blue-100 text-blue-600; +} + +.file-icon-pdf { + @apply bg-red-100 text-red-600; +} + +.file-icon-xls { + @apply bg-green-100 text-green-600; +} + +.file-icon-ppt { + @apply bg-orange-100 text-orange-600; +} + +.file-icon-zip { + @apply bg-purple-100 text-purple-600; +} + +.file-icon-img { + @apply bg-pink-100 text-pink-600; +} + +.file-icon-txt { + @apply bg-gray-100 text-gray-600; +} + +.file-icon-unknown { + @apply bg-gray-100 text-gray-600; +} + +/* 文件图标尺寸 */ +.file-icon-sm { + @apply w-8 h-8 text-base; +} + +.file-icon-lg { + @apply w-12 h-12 text-2xl; +} \ No newline at end of file diff --git a/app/styles/components/file-type-tag.css b/app/styles/components/file-type-tag.css new file mode 100644 index 0000000..5710f9d --- /dev/null +++ b/app/styles/components/file-type-tag.css @@ -0,0 +1,80 @@ +/** + * 文件类型标签组件样式 + */ + +/* 文件类型标签基础样式 */ +.file-type-tag { + @apply inline-flex items-center px-2 py-1 rounded text-xs font-medium; +} + +/* 文件类型颜色 */ +.file-type-tag-doc { + @apply bg-blue-100 text-blue-800; +} + +.file-type-tag-pdf { + @apply bg-red-100 text-red-800; +} + +.file-type-tag-xls { + @apply bg-green-100 text-green-800; +} + +.file-type-tag-ppt { + @apply bg-orange-100 text-orange-800; +} + +.file-type-tag-zip { + @apply bg-purple-100 text-purple-800; +} + +.file-type-tag-img { + @apply bg-pink-100 text-pink-800; +} + +.file-type-tag-txt { + @apply bg-gray-100 text-gray-800; +} + +.file-type-tag-unknown { + @apply bg-gray-100 text-gray-800; +} + +/* 特定业务文件类型 */ +.file-type-tag-contract { + @apply bg-blue-100 text-blue-800; +} + +.file-type-tag-license { + @apply bg-green-100 text-green-800; +} + +.file-type-tag-punishment { + @apply bg-orange-100 text-orange-800; +} + +.file-type-tag-report { + @apply bg-cyan-100 text-cyan-800; +} + +.file-type-tag-other { + @apply bg-purple-100 text-purple-800; +} + +/* 文件类型尺寸 */ +.file-type-tag-sm { + @apply px-1.5 py-0 text-xs; +} + +.file-type-tag-lg { + @apply px-2.5 py-1 text-sm; +} + +/* 带图标的文件类型标签 */ +.file-type-tag-with-icon { + @apply pl-1.5; +} + +.file-type-tag i { + @apply mr-1 text-sm; +} \ No newline at end of file diff --git a/app/styles/components/filter-panel.css b/app/styles/components/filter-panel.css new file mode 100644 index 0000000..2fec435 --- /dev/null +++ b/app/styles/components/filter-panel.css @@ -0,0 +1,57 @@ +/** + * 筛选面板组件样式 + */ + +/* 筛选面板容器 */ +.filter-panel { + @apply bg-white rounded-lg border border-gray-200 shadow-sm p-4 mb-5; +} + +/* 筛选条件列表 */ +.filter-list { + @apply flex flex-wrap items-end gap-3; +} + +/* 筛选项 */ +.filter-item { + @apply mb-0; +} + +/* 筛选标签 */ +.filter-label { + @apply block text-sm font-medium mb-1 text-gray-700; +} + +/* 筛选控件 */ +.filter-control { + @apply w-full; +} + +/* 筛选操作按钮区域 */ +.filter-actions { + @apply flex justify-end items-center pt-4 mt-4 border-t border-gray-100 space-x-3; +} + +/* 无分割线的按钮区域 */ +.filter-actions-no-divider { + @apply border-t-0 pt-0 mt-2; +} + +/* 收起/展开状态 */ +.filter-panel-collapsed { + @apply max-h-[120px] overflow-hidden relative; +} + +.filter-panel-collapsed::after { + content: ''; + @apply absolute bottom-0 left-0 right-0 h-12 bg-gradient-to-t from-white to-transparent; +} + +/* 收起/展开切换按钮 */ +.filter-toggle { + @apply text-sm text-[#00684a] cursor-pointer hover:text-[#005a3f] flex items-center; +} + +.filter-toggle i { + @apply ml-1; +} \ No newline at end of file diff --git a/app/styles/components/pagination.css b/app/styles/components/pagination.css new file mode 100644 index 0000000..8dff12f --- /dev/null +++ b/app/styles/components/pagination.css @@ -0,0 +1,60 @@ +/** + * 分页组件样式 + */ + +/* 分页容器 */ +.ant-pagination { + @apply flex items-center space-x-1; +} + +/* 分页项 */ +.ant-pagination-item { + @apply flex items-center justify-center min-w-[32px] h-8 px-3 + border border-gray-200 rounded-md text-sm text-gray-700 + transition-all duration-200 cursor-pointer; +} + +.ant-pagination-item:hover { + @apply border-[#00684a] text-[#00684a]; + /* @apply border-[#30184a] text-[#30184a]; */ +} + +.ant-pagination-item-active { + @apply border-[#00684a] bg-[#00684a] text-white font-medium; +} + +.ant-pagination-item-active:hover { + @apply text-white; +} + +/* 上一页/下一页按钮 */ +.ant-pagination-prev, +.ant-pagination-next { + @apply flex items-center justify-center w-8 h-8 + border border-gray-200 rounded-md text-gray-600 + transition-all duration-200; +} + +.ant-pagination-prev:hover, +.ant-pagination-next:hover { + @apply border-[#00684a] text-[#00684a]; +} + +/* 禁用状态 */ +.ant-pagination-disabled { + @apply opacity-50 cursor-not-allowed; +} + +.ant-pagination-disabled:hover { + @apply border-gray-200 text-gray-600; +} + +/* 显示总数和每页显示条数 */ +.ant-pagination-options { + @apply flex items-center text-sm text-gray-500 mr-4; +} + +.ant-pagination-options-size-changer { + @apply ml-2 px-2 py-1 border border-gray-200 rounded text-sm + focus:outline-none focus:ring-1 focus:ring-[#00684a] focus:border-[#00684a]; +} \ No newline at end of file diff --git a/app/styles/components/search-box.css b/app/styles/components/search-box.css new file mode 100644 index 0000000..7e0312a --- /dev/null +++ b/app/styles/components/search-box.css @@ -0,0 +1,80 @@ +/** + * 搜索框组件样式 + */ + +/* 搜索框容器 */ +.search-box { + @apply relative; +} + +/* 搜索框与按钮并排显示 */ +.search-box-row { + @apply flex items-center; +} + +.search-box-row .form-input { + @apply rounded-r-none; +} + +.search-box-row .search-button { + @apply rounded-l-none h-full flex items-center; +} + +/* 搜索输入框 */ +.form-input { + @apply w-full py-2 px-4 border border-gray-200 rounded-md text-sm + focus:outline-none focus:ring-1 focus:ring-[#00684a] focus:border-[#00684a] + placeholder-gray-400 transition-all duration-200; +} + +/* 搜索按钮 */ +.search-button { + @apply bg-[#00684a] text-white px-3 py-2 rounded-md border border-[#00684a]; +} + +.search-button:hover { + @apply bg-[#005a3f] border-[#005a3f]; +} + +.search-button i { + @apply mr-1; +} + +/* 仅图标按钮样式 */ +.icon-only-btn { + @apply px-2; +} + +.icon-only-btn i { + @apply mr-0; +} + +/* 搜索框大小 */ +.search-box-sm .form-input { + @apply py-1.5 text-sm; +} + +.search-box-lg .form-input { + @apply py-2.5 text-base; +} + +/* 搜索图标 */ +.search-box-icon { + @apply absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400; +} + +/* 清除按钮 */ +.search-box-clear { + @apply absolute right-3 top-1/2 transform -translate-y-1/2 + text-gray-400 hover:text-gray-600 cursor-pointer transition-colors duration-200; +} + +/* 带边框的搜索框 */ +.search-box-bordered { + @apply shadow-sm; +} + +/* 搜索框禁用状态 */ +.form-input:disabled { + @apply bg-gray-100 cursor-not-allowed opacity-70; +} \ No newline at end of file diff --git a/app/styles/components/status-badge.css b/app/styles/components/status-badge.css new file mode 100644 index 0000000..4672389 --- /dev/null +++ b/app/styles/components/status-badge.css @@ -0,0 +1,72 @@ +/** + * 状态徽章组件样式 + */ + +/* 状态徽章基础样式 */ +.status-badge { + @apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium; +} + +/* 状态徽章尺寸 */ +.status-badge-sm { + @apply px-2 py-0.5 text-xs; +} + +.status-badge-lg { + @apply px-3 py-1 text-sm; +} + +/* 状态徽章类型 */ +.status-badge-success { + @apply bg-green-100 text-green-800; +} + +.status-badge-processing { + @apply bg-blue-100 text-blue-800; +} + +.status-badge-warning { + @apply bg-yellow-100 text-yellow-800; +} + +.status-badge-error { + @apply bg-red-100 text-red-800; +} + +.status-badge-default { + @apply bg-gray-100 text-gray-800; +} + +/* 带图标的状态徽章 */ +.status-badge-with-icon { + @apply pl-1.5; +} + +.status-badge i { + @apply mr-1; +} + +/* 可点击的状态徽章 */ +.status-badge-clickable { + @apply cursor-pointer transition-colors duration-200; +} + +.status-badge-clickable.status-badge-success:hover { + @apply bg-green-200; +} + +.status-badge-clickable.status-badge-processing:hover { + @apply bg-blue-200; +} + +.status-badge-clickable.status-badge-warning:hover { + @apply bg-yellow-200; +} + +.status-badge-clickable.status-badge-error:hover { + @apply bg-red-200; +} + +.status-badge-clickable.status-badge-default:hover { + @apply bg-gray-200; +} \ No newline at end of file diff --git a/app/styles/components/status-dot.css b/app/styles/components/status-dot.css new file mode 100644 index 0000000..9bc52ae --- /dev/null +++ b/app/styles/components/status-dot.css @@ -0,0 +1,83 @@ +/** + * 状态点组件样式 + */ + +/* 状态点基础样式 */ +.status-dot { + @apply inline-block w-2 h-2 rounded-full; +} + +/* 状态点类型 */ +.status-dot-success { + @apply bg-green-500; +} + +.status-dot-processing { + @apply bg-blue-500; +} + +.status-dot-warning { + @apply bg-yellow-500; +} + +.status-dot-error { + @apply bg-red-500; +} + +.status-dot-default { + @apply bg-gray-400; +} + +/* 状态点尺寸 */ +.status-dot-sm { + @apply w-1.5 h-1.5; +} + +.status-dot-lg { + @apply w-3 h-3; +} + +/* 带脉冲动画的状态点 */ +.status-dot-pulse { + @apply relative; +} + +.status-dot-pulse::after { + content: ''; + @apply absolute w-full h-full rounded-full -left-1 -top-1 animate-ping; +} + +.status-dot-pulse.status-dot-success::after { + @apply bg-green-400 opacity-60; +} + +.status-dot-pulse.status-dot-processing::after { + @apply bg-blue-400 opacity-60; +} + +.status-dot-pulse.status-dot-warning::after { + @apply bg-yellow-400 opacity-60; +} + +.status-dot-pulse.status-dot-error::after { + @apply bg-red-400 opacity-60; +} + +/* 带文本的状态点 */ +.status-dot-with-text { + @apply flex items-center justify-center; +} + +.status-dot-text { + @apply ml-1.5 text-sm; + color: inherit; +} + +/* 状态文本颜色 */ +.status-dot-success + .status-dot-text { + @apply text-green-600; +} + +.status-dot-default + .status-dot-text { + @apply text-gray-500; +} \ No newline at end of file diff --git a/app/styles/components/table.css b/app/styles/components/table.css index 60c759a..ea0de8d 100644 --- a/app/styles/components/table.css +++ b/app/styles/components/table.css @@ -2,6 +2,67 @@ * 表格组件样式 */ +/* 表格容器 */ +.ant-table-wrapper { + @apply w-full overflow-x-auto relative; +} + +/* 表格 */ +.ant-table { + @apply w-full border-collapse text-sm; +} + +/* 表头 */ +.ant-table thead th { + @apply bg-gray-50 font-medium py-3 px-4 text-left text-gray-700 border-b border-gray-200; +} + +/* 表格内容 */ +.ant-table tbody td { + @apply py-3 px-4 border-b border-gray-100; +} + +/* 表格行 */ +.ant-table tbody tr { + @apply bg-white hover:bg-gray-50 transition-colors duration-150; +} + +/* 空状态 */ +.ant-table-empty { + @apply py-12 text-center text-gray-500; +} + +/* 带边框的表格 */ +.ant-table-bordered, +.ant-table-bordered .ant-table thead th, +.ant-table-bordered .ant-table tbody td { + @apply border border-gray-200; +} + +/* 表格加载状态 */ +.ant-table-loading { + @apply relative opacity-60; +} + +.ant-table-loading-indicator { + @apply absolute inset-0 flex items-center justify-center bg-white bg-opacity-70; +} + +/* 表格行选中状态 */ +.ant-table tr.selected { + @apply bg-[rgba(0,104,74,0.05)]; +} + +/* 表格排序图标 */ +.ant-table-column-sorter { + @apply ml-1 text-gray-400 inline-flex flex-col; +} + +.ant-table-column-sorter-up.active, +.ant-table-column-sorter-down.active { + @apply text-[#00684a]; +} + @layer components { /* 基础表格 */ .table-container { diff --git a/app/styles/components/tag.css b/app/styles/components/tag.css new file mode 100644 index 0000000..3270e35 --- /dev/null +++ b/app/styles/components/tag.css @@ -0,0 +1,65 @@ +/** + * 标签组件样式 + */ + +/* 标签基础样式 */ +.tag { + @apply inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium; +} + +/* 标签类型 */ +.tag-blue { + @apply bg-blue-100 text-blue-800; +} + +.tag-green { + @apply bg-green-100 text-green-800; +} + +.tag-red { + @apply bg-red-100 text-red-800; +} + +.tag-yellow { + @apply bg-yellow-100 text-yellow-800; +} + +.tag-purple { + @apply bg-purple-100 text-purple-800; +} + +.tag-gray { + @apply bg-gray-100 text-gray-800; +} + +/* 添加新的颜色 */ +.tag-cyan { + @apply bg-cyan-100 text-cyan-800; +} + +.tag-orange { + @apply bg-orange-100 text-orange-800; +} + +/* 标签尺寸 */ +.tag-sm { + @apply px-2 py-0 text-xs; +} + +.tag-lg { + @apply px-3 py-1 text-sm; +} + +/* 可关闭的标签 */ +.tag-closable { + @apply pr-1; +} + +.tag-close-icon { + @apply ml-1 cursor-pointer text-opacity-70 hover:text-opacity-100 transition-opacity duration-200; +} + +/* 可点击的标签 */ +.tag-clickable { + @apply cursor-pointer hover:opacity-80 transition-opacity duration-200; +} \ No newline at end of file diff --git a/app/styles/main.css b/app/styles/main.css index 78a7a83..308d68d 100644 --- a/app/styles/main.css +++ b/app/styles/main.css @@ -3,6 +3,23 @@ * 包含应用所有样式 */ +/* 导入组件样式 */ +@import './components/button.css'; +@import './components/card.css'; +@import './components/form.css'; +@import './components/navigation.css'; +@import './components/table.css'; +@import './components/badge.css'; +@import './components/pagination.css'; +@import './components/search-box.css'; +@import './components/filter-panel.css'; +@import './components/file-icon.css'; +@import './components/status-badge.css'; +@import './components/file-type-tag.css'; +@import './components/status-dot.css'; +@import './components/tag.css'; +/* @import './components/modal.css'; */ + /* Tailwind 基础指令 */ @tailwind base; @tailwind components; diff --git a/app/styles/pages/home.css b/app/styles/pages/home.css index 9146baa..64678a9 100644 --- a/app/styles/pages/home.css +++ b/app/styles/pages/home.css @@ -96,7 +96,7 @@ @apply flex items-center; } -/* 状态标签 */ +/* 状态标签 - 与status-badge.css重复,已注释 .status-badge { @apply inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium; } @@ -111,7 +111,7 @@ .status-badge.status-error { @apply bg-[rgba(245,34,45,0.1)] text-[#f5222d]; -} +} */ /* 卡片样式 */ .dashboard-card { diff --git a/app/styles/pages/rule-groups_index.css b/app/styles/pages/rule-groups_index.css index 4a0dd05..d4869ad 100644 --- a/app/styles/pages/rule-groups_index.css +++ b/app/styles/pages/rule-groups_index.css @@ -93,23 +93,6 @@ color: var(--color-primary); } - /* 状态点样式 */ - .rule-groups-page .status-dot { - display: inline-block; - width: 8px; - height: 8px; - border-radius: 50%; - margin-right: 6px; - } - - .rule-groups-page .status-success { - background-color: #52c41a; - } - - .rule-groups-page .status-error { - background-color: #f5222d; - } - /* 表单样式 */ .rule-groups-page .form-label { display: block; @@ -136,89 +119,6 @@ outline: none; } - /* 卡片样式 */ - .rule-groups-page .ant-card { - background-color: white; - border-radius: 8px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - border: 1px solid #e9ecef; - } - - .rule-groups-page .ant-card-body { - padding: 16px; - } - - /* 表格样式 */ - .rule-groups-page .ant-table { - width: 100%; - border-collapse: collapse; - } - - .rule-groups-page .ant-table th, - .rule-groups-page .ant-table td { - padding: 12px 16px; - text-align: left; - border-bottom: 1px solid #e9ecef; - } - - .rule-groups-page .ant-table th { - background-color: #f8f9fa; - font-weight: 400; /* 减少文字粗细度 */ - color: #495057; - font-size: 14px; - } - - /* 分页样式 */ - .rule-groups-page .ant-pagination { - display: flex; - align-items: center; - gap: 8px; - } - - .rule-groups-page .ant-pagination-item { - width: 32px; - height: 32px; - display: flex; - align-items: center; - justify-content: center; - border: 1px solid #dee2e6; - border-radius: 4px; - background-color: white; - cursor: pointer; - transition: all 0.2s; - } - - .rule-groups-page .ant-pagination-item:hover { - border-color: #00684a; - color: #00684a; - } - - .rule-groups-page .ant-pagination-item-active { - background-color: #00684a; - border-color: #00684a; - color: white; - } - - .rule-groups-page .ant-pagination-prev, - .rule-groups-page .ant-pagination-next { - width: 32px; - height: 32px; - display: flex; - align-items: center; - justify-content: center; - border: 1px solid #dee2e6; - border-radius: 4px; - background-color: white; - cursor: pointer; - transition: all 0.2s; - } - - .rule-groups-page .ant-pagination-prev:disabled, - .rule-groups-page .ant-pagination-next:disabled { - opacity: 0.5; - cursor: not-allowed; - } - /* 特定链接样式 */ .rule-groups-page .badge { display: inline-flex; @@ -253,12 +153,12 @@ color: #f5222d; } - /* 行操作按钮 */ + /* 文本按钮样式 */ .rule-groups-page .ant-btn-text { - background: transparent; - margin: 10px; + background-color: transparent; border: none; - padding: 4px 0; + padding: 4px 8px; + font-size: 14px; cursor: pointer; transition: color 0.2s; } @@ -268,7 +168,8 @@ } .rule-groups-page .ant-btn-text.text-primary:hover { - color: #005a3f; + color: #00684a; + text-decoration: underline; } .rule-groups-page .ant-btn-text.text-error { @@ -276,21 +177,21 @@ } .rule-groups-page .ant-btn-text.text-error:hover { - color: #cf1f29; + color: #f5222d; + text-decoration: underline; } - /* 响应式调整 */ @media (max-width: 768px) { .rule-groups-page .flex-wrap { - flex-direction: column; + flex-wrap: wrap; } .rule-groups-page .flex-1 { - width: 100%; + flex: 1 1 100%; } .rule-groups-page .ant-table { - font-size: 14px; + font-size: 12px; } } @@ -317,3 +218,47 @@ width: 100%; } + /* 表格组件样式兼容 - 确保Table组件渲染的表格保持树形结构样式 */ + .rule-groups-page .tree-table tr:nth-child(odd) { + background-color: rgba(0, 104, 74, 0.02); + } + + .rule-groups-page .tree-table tr:hover td { + background-color: rgba(0, 104, 74, 0.05); + } + + /* 确保父子关系行高一致 */ + .rule-groups-page .tree-table tr td { + padding: 12px 16px; + vertical-align: middle; + } + + /* 父行背景色 */ + .rule-groups-page .tree-table tr:has(.parent-badge) { + background-color: #f9f9f9; + } + + /* 确保链接样式一致 */ + .rule-groups-page .tree-table a { + /* color: inherit; */ + text-decoration: none; + } + + .rule-groups-page .tree-table a:hover { + text-decoration: underline; + } + + /* 搜索筛选面板样式调整 */ + .rule-groups-page .filter-panel { + padding: 16px; + } + + .rule-groups-page .filter-item { + margin-bottom: 0; + } + + .rule-groups-page .filter-actions { + margin-top: 8px; + padding-top: 8px; + } + diff --git a/app/styles/pages/rules-files.css b/app/styles/pages/rules-files.css new file mode 100644 index 0000000..de64183 --- /dev/null +++ b/app/styles/pages/rules-files.css @@ -0,0 +1,479 @@ +/* 评查文件列表页面样式 */ +.review-files-page { + /* 所有样式都包含在此命名空间内 */ +} + +/* 筛选区域 - 与filter-panel.css重复,已注释 +.review-files-page .filter-panel { + background-color: white; + border-radius: 6px; + border: 1px solid var(--color-gray-200); + padding: 12px 16px; + margin-bottom: 16px; + display: flex; + flex-wrap: wrap; + align-items: flex-end; + gap: 12px; +} */ + +.review-files-page .form-label { + display: block; + margin-bottom: 4px; + font-size: 14px; + font-weight: 400; + color: var(--color-gray-800); +} + +/* 搜索框样式 - 与search-box.css重复,已注释 +.review-files-page .search-box { + display: flex; + align-items: stretch; + width: 100%; + border-radius: 4px; + overflow: visible; + position: relative; +} + +.review-files-page .search-box .form-input { + flex: 1; + font-size: 14px; + padding: 8px 12px; + border: 1px solid #d9d9d9; + border-right: none; + outline: none; + background-color: transparent; + height: 38px; + border-radius: 4px 0 0 4px; + box-shadow: none; + transition: all 0.3s; +} + +.review-files-page .search-box .form-input:focus { + outline: none; + border-color: var(--color-primary); + box-shadow: 0 0 0 2px rgba(0, 104, 74, 0.1); +} */ + +.review-files-page .search-box .search-button { + border-top-left-radius: 0 !important; + border-bottom-left-radius: 0 !important; + border-top-right-radius: 4px !important; + border-bottom-right-radius: 4px !important; + margin: 0 !important; + border: 1px solid var(--color-primary) !important; + height: 38px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + min-width: 40px !important; + background-color: var(--color-primary) !important; + color: white !important; + cursor: pointer !important; + padding: 0 16px !important; + transition: all 0.3s !important; + outline: none !important; +} + +.review-files-page .search-box .search-button:hover { + background-color: var(--color-primary-hover) !important; +} + +.review-files-page .search-box .search-button:focus { + outline: none !important; + box-shadow: 0 0 0 2px rgba(0, 104, 74, 0.2) !important; +} + +.review-files-page .search-box .search-button i { + font-size: 16px !important; + margin-right: 0 !important; +} + +.review-files-page .search-box .search-button span { + margin-left: 4px !important; +} + +/* 仅图标按钮样式 */ +.review-files-page .search-box .icon-only-btn { + padding: 0 12px !important; + min-width: 40px !important; +} + +/* 确保旧的按钮样式不影响搜索按钮 */ +.review-files-page .search-box .ant-btn { + line-height: normal !important; +} + +/* 表格样式 - 与table.css重复,已注释 +.review-files-page .ant-table { + width: 100%; + border-collapse: separate; + border-spacing: 0; +} + +.review-files-page .ant-table th { + background-color: #fafafa; + font-weight: 400; + padding: 16px; + text-align: left; + border-bottom: 1px solid #f0f0f0; + color: rgba(0, 0, 0, 0.85); + font-size: 14px; +} + +.review-files-page .ant-table td { + padding: 16px; + border-bottom: 1px solid #f0f0f0; + text-align: left; +} + +.review-files-page .ant-table tr:hover td { + background-color: #f5f5f5; +} */ + +/* 文件类型徽章 - 与file-type-tag.css重复,已注释 +.review-files-page .file-type-badge { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 400; +} + +.review-files-page .file-type-badge i { + margin-right: 4px; + font-size: 14px; +} */ + +.review-files-page .file-type-contract { + background-color: #e6f7ff; + color: #1890ff; +} + +.review-files-page .file-type-license { + background-color: #f6ffed; + color: #52c41a; +} + +.review-files-page .file-type-punishment { + background-color: #fff7e6; + color: #fa8c16; +} + +.review-files-page .file-type-report { + background-color: #e6fffb; + color: #13c2c2; +} + +.review-files-page .file-type-other { + background-color: #f9f0ff; + color: #722ed1; +} + +/* 状态徽章 - 与status-badge.css重复,已注释 +.review-files-page .status-badge { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 400; +} + +.review-files-page .status-badge i { + margin-right: 4px; +} */ + +.review-files-page .status-pass { + background-color: #f6ffed; + color: var(--color-success); +} + +.review-files-page .status-warning { + background-color: #fffbe6; + color: var(--color-warning); +} + +.review-files-page .status-fail { + background-color: #fff1f0; + color: var(--color-error); +} + +.review-files-page .status-pending { + background-color: #f9f0ff; + color: #722ed1; +} + +/* 严重程度指示器 */ +.review-files-page .severity-indicator { + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + margin-right: 8px; + vertical-align: middle; +} + +.review-files-page .severity-info { + background-color: #1890ff; +} + +.review-files-page .severity-warning { + background-color: #faad14; +} + +.review-files-page .severity-error { + background-color: #f5222d; +} + +.review-files-page .severity-critical { + background-color: #722ed1; +} + +/* 分页样式 - 与pagination.css重复,已注释 +.review-files-page .pagination { + display: flex; + justify-content: flex-end; + margin-top: 16px; + padding: 16px; +} + +.review-files-page .pagination-item { + min-width: 32px; + height: 32px; + margin-right: 8px; + display: inline-flex; + justify-content: center; + align-items: center; + border: 1px solid #d9d9d9; + border-radius: 4px; + font-size: 14px; +} */ + +/* 更新分页样式,与rules._index.tsx保持一致 */ +.review-files-page .ant-pagination { + display: flex; + align-items: center; + margin-top: 16px; + padding: 16px; + justify-content: flex-end; +} + +.review-files-page .ant-pagination-right { + display: flex; + align-items: center; + gap: 4px; +} + +.review-files-page .ant-pagination-item { + min-width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid #dee2e6; + border-radius: 4px; + background-color: white; + cursor: pointer; + transition: all 0.2s; + padding: 0 8px; + margin-right: 4px; +} + +.review-files-page .ant-pagination-item-active { + background-color: var(--color-primary); + border-color: var(--color-primary); + color: white; +} + +.review-files-page .ant-pagination-disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.review-files-page .ant-pagination-prev, +.review-files-page .ant-pagination-next { + min-width: 32px; + height: 32px; +} + +.review-files-page .ant-pagination-options { + display: flex; + align-items: center; + margin-right: auto; + min-width: 200px; + padding-right: 12px; +} + +.review-files-page .ant-pagination-options span.text-sm { + white-space: nowrap; + flex-shrink: 0; + color: var(--color-gray-700); + margin-right: 12px; +} + +.review-files-page .ant-pagination-options-size-changer { + margin-left: 0; + border: 1px solid var(--color-gray-300); + border-radius: 4px; + height: 32px; + padding: 0 8px; + min-width: 110px; + outline: none; + background-color: white; + color: var(--color-gray-800); + transition: all 0.2s; + font-size: 14px; +} + +.review-files-page .ant-pagination-options-size-changer:hover, +.review-files-page .ant-pagination-options-size-changer:focus { + border-color: var(--color-primary); + outline: none; + box-shadow: 0 0 0 2px rgba(0, 104, 74, 0.1); +} + +/* 表单组件样式 */ +.review-files-page .form-select { + width: 100%; + height: 38px; + padding: 8px 12px; + border: 1px solid #d9d9d9; + border-radius: 4px; + font-size: 14px; + transition: all 0.2s; + background-color: white; + color: rgba(0, 0, 0, 0.85); +} + +.review-files-page .form-select:focus { + border-color: var(--color-primary); + box-shadow: 0 0 0 2px rgba(0, 104, 74, 0.2); + outline: none; +} + +/* 内容卡片样式 */ +.review-files-page .ant-card { + background-color: white; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + border: 1px solid #e9ecef; + margin-bottom: 16px; +} + +.review-files-page .ant-card-body { + padding: 16px; +} + +/* 文本颜色辅助类 */ +.review-files-page .text-success { + color: var(--color-success); +} + +.review-files-page .text-warning { + color: var(--color-warning); +} + +.review-files-page .text-error { + color: var(--color-error); +} + +.review-files-page .text-secondary { + color: var(--color-gray-600); +} + +/* 错误容器样式 */ +.review-files-page .error-container { + text-align: center; + padding: 48px; + background-color: #fff; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + max-width: 500px; + margin: 48px auto; +} + +/* 按钮尺寸样式 */ +.review-files-page .ant-btn-sm { + padding: 4px 8px; + font-size: 12px; + height: 38px; + line-height: 1; + background-color: white; + border: 1px solid #e9ecef; + transition: all 0.3s; +} + +/* 操作按钮样式 - 根据需求更新 */ +/* 默认按钮样式 - 非主要按钮 */ +.review-files-page .ant-btn-default { + background-color: white; + border: 1px solid #e9ecef; + color: rgba(0, 0, 0, 0.85); +} + +.review-files-page .ant-btn-default:hover { + border-color: var(--color-primary); + color: var(--color-primary); +} + +/* 主要按钮样式 - 确认按钮 */ +.review-files-page .ant-btn-primary { + background-color: var(--color-primary); + border-color: var(--color-primary); + color: white; +} + +.review-files-page .ant-btn-primary:hover { + background-color: var(--color-primary-hover); + border-color: var(--color-primary-hover); + color: white; +} + +/* 确保链接按钮样式一致 */ +.review-files-page .ant-btn-primary a { + color: white !important; + text-decoration: none; +} + +.review-files-page .ant-btn-primary:hover a { + color: white !important; +} + +.review-files-page .ant-btn-default a { + color: rgba(0, 0, 0, 0.85); + text-decoration: none; +} + +.review-files-page .ant-btn-default:hover a { + color: var(--color-primary); +} + +/* 结果统计样式 */ +.review-files-page .result-summary { + display: flex; + align-items: center; + margin-bottom: 16px; +} + +.review-files-page .result-tag { + display: inline-flex; + align-items: center; + margin-right: 24px; + font-size: 14px; +} + +.review-files-page .result-tag-count { + font-weight: 400; + margin-left: 4px; +} + +/* 确保使用Table组件后的样式一致性 */ +/* .review-files-page .files-table th { + text-align: left !important; +} + +.review-files-page .files-table td { + text-align: center !important; + vertical-align: middle !important; +} */ diff --git a/app/styles/pages/rules_files.css b/app/styles/pages/rules_files.css deleted file mode 100644 index 639ec24..0000000 --- a/app/styles/pages/rules_files.css +++ /dev/null @@ -1,240 +0,0 @@ -/* 评查文件列表页面样式 */ -.review-files-page { - /* 所有样式都包含在此命名空间内 */ -} - -/* 筛选区域 */ -.review-files-page .card-container { - margin-bottom: 16px; -} - -/* 搜索框样式 */ -.review-files-page .search-box { - display: flex; - align-items: center; - width: 100%; -} - -.review-files-page .search-box .form-input { - flex: 1; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - padding: 8px 12px; - border: none; - outline: none; -} - -.review-files-page .search-box .ant-btn { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -/* 表格样式 */ -.review-files-page .ant-table { - width: 100%; - border-collapse: collapse; -} - -.review-files-page .ant-table th { - background-color: #fafafa; - font-weight: 500; - padding: 16px; - text-align: left; - border-bottom: 1px solid #f0f0f0; -} - -.review-files-page .ant-table td { - padding: 16px; - border-bottom: 1px solid #f0f0f0; -} - -.review-files-page .ant-table tr:hover { - background-color: rgba(0, 0, 0, 0.02); -} - -/* 文件类型徽章 */ -.review-files-page .file-type-badge { - display: inline-flex; - align-items: center; - padding: 2px 8px; - border-radius: 4px; - font-size: 12px; - font-weight: 500; -} - -.review-files-page .file-type-badge i { - margin-right: 4px; - font-size: 14px; -} - -.review-files-page .file-type-contract { - background-color: #e6f7ff; - color: #1890ff; -} - -.review-files-page .file-type-license { - background-color: #f6ffed; - color: #52c41a; -} - -.review-files-page .file-type-punishment { - background-color: #fff7e6; - color: #fa8c16; -} - -.review-files-page .file-type-report { - background-color: #e6fffb; - color: #13c2c2; -} - -.review-files-page .file-type-other { - background-color: #f9f0ff; - color: #722ed1; -} - -/* 状态徽章 */ -.review-files-page .status-badge { - display: inline-flex; - align-items: center; - padding: 2px 8px; - border-radius: 4px; - font-size: 12px; - font-weight: 500; -} - -.review-files-page .status-pass { - background-color: #f6ffed; - color: #52c41a; -} - -.review-files-page .status-warning { - background-color: #fffbe6; - color: #faad14; -} - -.review-files-page .status-fail { - background-color: #fff1f0; - color: #f5222d; -} - -.review-files-page .status-pending { - background-color: #f9f0ff; - color: #722ed1; -} - -/* 严重程度指示器 */ -.review-files-page .severity-indicator { - display: inline-block; - width: 16px; - height: 16px; - border-radius: 50%; - margin-right: 8px; - vertical-align: middle; -} - -.review-files-page .severity-info { - background-color: #1890ff; -} - -.review-files-page .severity-warning { - background-color: #faad14; -} - -.review-files-page .severity-error { - background-color: #f5222d; -} - -.review-files-page .severity-critical { - background-color: #722ed1; -} - -/* 分页样式 */ -.review-files-page .pagination { - display: flex; - justify-content: flex-end; - margin-top: 16px; - padding: 16px; -} - -.review-files-page .pagination-item { - min-width: 32px; - height: 32px; - margin-right: 8px; - display: inline-flex; - justify-content: center; - align-items: center; - border: 1px solid #d9d9d9; - border-radius: 4px; - font-size: 14px; - cursor: pointer; - transition: all 0.3s; - background-color: transparent; -} - -.review-files-page .pagination-item:hover { - border-color: var(--color-primary, #1677ff); - color: var(--color-primary, #1677ff); -} - -.review-files-page .pagination-item.active { - border-color: var(--color-primary, #1677ff); - background-color: var(--color-primary, #1677ff); - color: white; -} - -.review-files-page .pagination-item.disabled { - color: rgba(0, 0, 0, 0.25); - border-color: #d9d9d9; - cursor: not-allowed; -} - -/* 表单组件样式 */ -.review-files-page .form-select { - width: 100%; - height: 38px; - padding: 8px 12px; - border: 1px solid #d9d9d9; - border-radius: 4px; - font-size: 14px; - transition: all 0.2s; - background-color: white; -} - -.review-files-page .form-select:focus { - border-color: var(--color-primary, #1677ff); - box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.2); - outline: none; -} - -/* 内容卡片样式 */ -.review-files-page .content-card .ant-card-body { - padding: 0 !important; -} - -/* 文本颜色辅助类 */ -.review-files-page .text-success { - color: #52c41a; -} - -.review-files-page .text-warning { - color: #faad14; -} - -.review-files-page .text-error { - color: #f5222d; -} - -.review-files-page .text-secondary { - color: rgba(0, 0, 0, 0.45); -} - -/* 错误容器样式 */ -.review-files-page .error-container { - text-align: center; - padding: 48px; - background-color: #fff; - border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - max-width: 500px; - margin: 48px auto; -} \ No newline at end of file diff --git a/app/styles/pages/rules_index.css b/app/styles/pages/rules_index.css index 85e81ff..64ab09b 100644 --- a/app/styles/pages/rules_index.css +++ b/app/styles/pages/rules_index.css @@ -3,12 +3,21 @@ /* 所有样式都包含在此命名空间内 */ } +/* 新增评查点按钮样式 */ +.rules-page .btn-add-rule { + @apply bg-[#00684a] text-white; +} + +.rules-page .btn-add-rule:hover { + @apply bg-[#005a3f] text-white; +} + /* 筛选区域 */ .rules-page .filter-card { margin-bottom: 1rem; } -/* 搜索框样式 */ +/* 搜索框样式 - 与search-box.css重复,已注释 .rules-page .search-box { display: flex; align-items: center; @@ -25,13 +34,12 @@ border-bottom-left-radius: 0; } -/* 无按钮搜索框样式 */ .rules-page .search-box.form-input-only .form-input { border-radius: 0.25rem; width: 100%; -} +} */ -/* 表格样式 */ +/* 表格样式 - 与table.css重复,已注释 .rules-page .ant-table { width: 100%; border-collapse: collapse; @@ -39,7 +47,7 @@ .rules-page .ant-table th { background-color: #f9f9f9; - font-weight: 400; /* 减少文字粗细度 */ + font-weight: 400; padding: 12px 16px; text-align: left; border-bottom: 1px solid #e9ecef; @@ -48,11 +56,54 @@ .rules-page .ant-table td { padding: 12px 16px; border-bottom: 1px solid #e9ecef; - font-weight: 400; /* 减少文字粗细度 */ + font-weight: 400; } .rules-page .ant-table tr:hover { background-color: rgba(0, 0, 0, 0.02); +} */ + +/* 表格自定义样式 */ +.rules-page .ant-table { + width: 100%; + border-collapse: collapse; +} + +.rules-page .ant-table th { + background-color: #f9f9f9; + font-weight: 500; + padding: 10px; + text-align: center; + border-bottom: 1px solid #e9ecef; + color: #333; + font-size: 14px; +} + +.rules-page .ant-table td { + padding: 10px; + border-bottom: 1px solid #e9ecef; + font-weight: 400; + text-align: center; + vertical-align: middle; + font-size: 14px; +} + +.rules-page .ant-table tr:hover { + background-color: rgba(0, 0, 0, 0.02); +} + +/* 使用Table组件时的样式 */ +.rules-page .rules-table th { + text-align: center !important; +} + +.rules-page .rules-table td { + text-align: center !important; +} + +.rules-page .ant-table .status-column { + text-align: center; + width: 80px; } /* 表格操作列样式 */ @@ -63,12 +114,12 @@ /* 操作按钮样式 - 改为文本按钮样式 */ .rules-page .operations-cell .ant-btn { background: transparent; - border: 1px solid #e9ecef; + border: none; padding: 4px 8px; margin-right: 4px; border-radius: 4px; font-size: 14px; - color: #495057; + color: #00684a; height: auto; min-width: auto; box-shadow: none; @@ -80,10 +131,9 @@ display: inline-flex; align-items: center; justify-content: center; - border: 1px solid #e4e4e4; - background-color: #ffffff; - color: #333; - border-radius: 4px; + background-color: transparent; + color: #00684a; + border: none; line-height: 1; padding: 4px 8px; font-size: 13px; @@ -97,23 +147,24 @@ } .rules-page .operation-btn:hover { - border-color: #00684a; - color: #00684a; + color: #005a3f; + text-decoration: underline; } .rules-page .operation-btn-danger { - background-color: #f5222d; - border-color: #f5222d; - color: white; + color: #f5222d; + background-color: transparent; + border: none; } .rules-page .operation-btn-danger:hover { - background-color: #cf1f29; - border-color: #cf1f29; - color: white !important; + color: #cf1f29 !important; + background-color: transparent; + border: none; + text-decoration: underline; } -/* 状态点样式 */ +/* 状态点样式 - 与status-dot.css重复,已注释 .rules-page .status-dot { display: inline-block; width: 8px; @@ -128,9 +179,9 @@ .rules-page .status-dot-default { background-color: #d9d9d9; -} +} */ -/* 标签自定义样式 */ +/* 标签自定义样式 - 与tag.css重复,已注释 .rules-page .ant-tag { display: inline-flex; align-items: center; @@ -138,7 +189,7 @@ font-size: 12px; border-radius: 4px; margin-right: 8px; - font-weight: 400; /* 减少文字粗细度 */ + font-weight: 400; } .rules-page .ant-tag-blue { @@ -169,9 +220,9 @@ .rules-page .ant-tag-red { background-color: rgba(245, 34, 45, 0.1); color: #f5222d; -} +} */ -/* 分页样式 */ +/* 分页样式 - 与pagination.css重复,已注释 .rules-page .ant-pagination { display: flex; align-items: center; @@ -228,7 +279,7 @@ border-radius: 4px; height: 32px; padding: 0 8px; -} +} */ /* 卡片内容调整 */ .rules-page .content-card .ant-card-body { @@ -304,6 +355,7 @@ margin-bottom: 16px; } +/* 卡片样式 - 与card.css重复,已注释 .rules-page .ant-card { background-color: white; border-radius: 8px; @@ -314,16 +366,15 @@ .rules-page .ant-card-body { padding: 16px; -} +} */ -/* 按钮样式修正 */ +/* 按钮样式修正 - 与button.css重复,已注释 .rules-page .ant-btn-sm { height: 32px; padding: 0 8px; font-size: 14px; } -/* 按钮悬停覆盖样式 */ .rules-page .ant-btn-primary { color: white !important; } @@ -331,7 +382,7 @@ .rules-page .ant-btn-primary:hover { color: white !important; background-color: #005a3f; -} +} */ /* 针对操作列中的按钮 .rules-page .operations-cell .ant-btn-default:hover { diff --git a/vite.config.ts b/vite.config.ts index e4e8cef..457c172 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -21,4 +21,9 @@ export default defineConfig({ }), tsconfigPaths(), ], + server: { + host: '0.0.0.0', + port: 5173, + open: true + }, });