diff --git a/app/api/home/home.ts b/app/api/home/home.ts index 58329a3..6688486 100644 --- a/app/api/home/home.ts +++ b/app/api/home/home.ts @@ -123,7 +123,8 @@ export async function getHomeData(): Promise { select: 'count', filter: { or: `(audit_status.eq.0,audit_status.eq.2,audit_status.is.null)`, - created_at: `gte.${startOfToday}` + created_at: `gte.${startOfToday}`, + is_test_document: `eq.false` } }; const todayPendingCount = await handleApiResponse<{count: number}[]>( @@ -138,7 +139,8 @@ export async function getHomeData(): Promise { select: 'count', filter: { and: `(audit_status.neq.0,audit_status.neq.2)`, - created_at: `gte.${startOfThisMonth}` + created_at: `gte.${startOfThisMonth}`, + is_test_document: `eq.false` } }; const thisMonthReviewedCount = await handleApiResponse<{count: number}[]>( @@ -154,7 +156,8 @@ export async function getHomeData(): Promise { select: 'count', filter: { or: `(audit_status.eq.1,audit_status.eq.-1)`, - and: `(created_at.gte.${startOfLastMonth},created_at.lte.${endOfLastMonth})` + and: `(created_at.gte.${startOfLastMonth},created_at.lte.${endOfLastMonth})`, + is_test_document: `eq.false` } }; const lastMonthReviewedCount = await handleApiResponse<{count: number}[]>( @@ -180,7 +183,8 @@ export async function getHomeData(): Promise { select: 'count', filter: { audit_status: `eq.1`, - created_at: `gte.${startOfThisMonth}` + created_at: `gte.${startOfThisMonth}`, + is_test_document: `eq.false` } }; const thisMonthTotalCount = await handleApiResponse<{count: number}[]>( @@ -201,7 +205,8 @@ export async function getHomeData(): Promise { select: 'count', filter: { audit_status: `eq.1`, - and: `(created_at.gte.${startOfLastMonth},created_at.lte.${endOfLastMonth})` + and: `(created_at.gte.${startOfLastMonth},created_at.lte.${endOfLastMonth})`, + is_test_document: `eq.false` } }; const lastMonthTotalCount = await handleApiResponse<{count: number}[]>( diff --git a/app/components/rules/new/ReviewSettings.tsx b/app/components/rules/new/ReviewSettings.tsx index 9edee58..7516478 100644 --- a/app/components/rules/new/ReviewSettings.tsx +++ b/app/components/rules/new/ReviewSettings.tsx @@ -1597,9 +1597,7 @@ export function ReviewSettings({ value={ typeof config.prompt === 'string' && config.prompt ? config.prompt - : `请判断以下{字段}内容是否符合规范要求,仅回答"符合"或"不符合",并简要说明理由。 - -{字段内容}` + : `` } onChange={(e) => handleRuleConfigChange(id, { prompt: e.target.value })} > diff --git a/app/components/ui/DateRangePicker.tsx b/app/components/ui/DateRangePicker.tsx index 117df66..03443e8 100644 --- a/app/components/ui/DateRangePicker.tsx +++ b/app/components/ui/DateRangePicker.tsx @@ -1,3 +1,4 @@ +import { useRef, useEffect, useState } from "react"; import dateRangePickerStyles from "~/styles/components/date-range-picker.css?url"; export interface DateRangePickerProps { @@ -10,6 +11,8 @@ export interface DateRangePickerProps { className?: string; startId?: string; endId?: string; + format?: string; + placeholder?: string; } export function links() { @@ -18,6 +21,27 @@ export function links() { ]; } +// 格式化日期函数 +function formatDate(dateString: string, format = "yyyy-MM-dd"): string { + if (!dateString) return ""; + + try { + const date = new Date(dateString); + if (isNaN(date.getTime())) return dateString; + + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + + return format + .replace("yyyy", year.toString()) + .replace("MM", month) + .replace("dd", day); + } catch (e) { + return dateString; + } +} + /** * 日期范围选择器组件 * 用于选择日期范围,如开始日期和结束日期 @@ -31,31 +55,135 @@ export function DateRangePicker({ endLabel = "至", className = "", startId = "date-start", - endId = "date-end" + endId = "date-end", + format = "yyyy-MM-dd", + placeholder = "请选择时间" }: DateRangePickerProps) { + // 使用ref获取input元素 + const startInputRef = useRef(null); + const endInputRef = useRef(null); + + // 添加状态跟踪哪个日期输入框被聚焦 + const [focusedInput, setFocusedInput] = useState(null); + + // 格式化显示日期 + const formattedStartDate = startDate ? formatDate(startDate, format) : ""; + const formattedEndDate = endDate ? formatDate(endDate, format) : ""; + + // 保持原始日期输入框的显示格式 + useEffect(() => { + if (startInputRef.current) { + startInputRef.current.setAttribute('data-display-value', formattedStartDate || placeholder); + } + if (endInputRef.current) { + endInputRef.current.setAttribute('data-display-value', formattedEndDate || placeholder); + } + }, [formattedStartDate, formattedEndDate, placeholder]); + + // 处理日期输入框全局点击 + const handleWrapperClick = (inputRef: React.RefObject) => { + if (inputRef.current) { + // console.log('Wrapper clicked, triggering date input'); + // 点击整个包装器区域时,触发输入框点击 + inputRef.current.focus(); + inputRef.current.click(); + + try { + // 检查并记录showPicker是否可用 + const hasShowPicker = typeof inputRef.current.showPicker === 'function'; + console.log('showPicker API available:', hasShowPicker); + + // 尝试使用showPicker API + if (hasShowPicker) { + inputRef.current.showPicker(); + console.log('showPicker called successfully'); + } + } catch (error) { + console.error('Failed to show date picker:', error); + } + } + }; + + // 处理键盘事件 + const handleKeyDown = (e: React.KeyboardEvent, inputRef: React.RefObject) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleWrapperClick(inputRef); + } + }; + + // 处理聚焦和失焦 + const handleFocus = (id: string) => { + setFocusedInput(id); + }; + + const handleBlur = () => { + setFocusedInput(null); + }; + return (
- onStartDateChange(e.target.value)} - /> +
{ + e.stopPropagation(); + handleWrapperClick(startInputRef); + }} + onKeyDown={(e) => handleKeyDown(e, startInputRef)} + tabIndex={0} + role="button" + aria-label={`选择${startLabel}日期`} + > + onStartDateChange(e.target.value)} + onFocus={() => handleFocus(startId)} + onBlur={handleBlur} + /> +
+ {formattedStartDate || placeholder} +
+
- onEndDateChange(e.target.value)} - /> +
{ + e.stopPropagation(); + handleWrapperClick(endInputRef); + }} + onKeyDown={(e) => handleKeyDown(e, endInputRef)} + tabIndex={0} + role="button" + aria-label={`选择${endLabel}日期`} + > + onEndDateChange(e.target.value)} + onFocus={() => handleFocus(endId)} + onBlur={handleBlur} + /> +
+ {formattedEndDate || placeholder} +
+
@@ -70,28 +198,132 @@ export function SimpleDateRangePicker({ onEndDateChange, className = "", startId = "date-start-simple", - endId = "date-end-simple" + endId = "date-end-simple", + format = "yyyy-MM-dd", + placeholder = "请选择时间" }: Omit) { + // 使用ref获取input元素 + const startInputRef = useRef(null); + const endInputRef = useRef(null); + + // 添加状态跟踪哪个日期输入框被聚焦 + const [focusedInput, setFocusedInput] = useState(null); + + // 格式化显示日期 + const formattedStartDate = startDate ? formatDate(startDate, format) : ""; + const formattedEndDate = endDate ? formatDate(endDate, format) : ""; + + // 保持原始日期输入框的显示格式 + useEffect(() => { + if (startInputRef.current) { + startInputRef.current.setAttribute('data-display-value', formattedStartDate || placeholder); + } + if (endInputRef.current) { + endInputRef.current.setAttribute('data-display-value', formattedEndDate || placeholder); + } + }, [formattedStartDate, formattedEndDate, placeholder]); + + // 处理日期输入框全局点击 + const handleWrapperClick = (inputRef: React.RefObject) => { + if (inputRef.current) { + console.log('Wrapper clicked, triggering date input'); + // 点击整个包装器区域时,触发输入框点击 + inputRef.current.focus(); + inputRef.current.click(); + + try { + // 检查并记录showPicker是否可用 + const hasShowPicker = typeof inputRef.current.showPicker === 'function'; + console.log('showPicker API available:', hasShowPicker); + + // 尝试使用showPicker API + if (hasShowPicker) { + inputRef.current.showPicker(); + console.log('showPicker called successfully'); + } + } catch (error) { + console.error('Failed to show date picker:', error); + } + } + }; + + // 处理键盘事件 + const handleKeyDown = (e: React.KeyboardEvent, inputRef: React.RefObject) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleWrapperClick(inputRef); + } + }; + + // 处理聚焦和失焦 + const handleFocus = (id: string) => { + setFocusedInput(id); + }; + + const handleBlur = () => { + setFocusedInput(null); + }; + return (
- onStartDateChange(e.target.value)} - aria-label="开始日期" - /> +
{ + e.stopPropagation(); + handleWrapperClick(startInputRef); + }} + onKeyDown={(e) => handleKeyDown(e, startInputRef)} + tabIndex={0} + role="button" + aria-label="选择开始日期" + > + onStartDateChange(e.target.value)} + onFocus={() => handleFocus(startId)} + onBlur={handleBlur} + aria-label="开始日期" + /> +
+ {formattedStartDate || placeholder} +
+
- onEndDateChange(e.target.value)} - aria-label="结束日期" - /> +
{ + e.stopPropagation(); + handleWrapperClick(endInputRef); + }} + onKeyDown={(e) => handleKeyDown(e, endInputRef)} + tabIndex={0} + role="button" + aria-label="选择结束日期" + > + onEndDateChange(e.target.value)} + onFocus={() => handleFocus(endId)} + onBlur={handleBlur} + aria-label="结束日期" + /> +
+ {formattedEndDate || placeholder} +
+
); diff --git a/app/routes/files.upload.tsx b/app/routes/files.upload.tsx index 3bcad90..3f48da5 100644 --- a/app/routes/files.upload.tsx +++ b/app/routes/files.upload.tsx @@ -402,10 +402,32 @@ export default function FilesUpload() { // 处理文件选择 const handleFilesSelected = (files: FileList) => { if (files.length > 0) { - const newFiles = Array.from(files); - setCurrentFiles(newFiles); - if (fileType) { - startUpload(newFiles); + // 验证文件类型,只允许PDF文件 + const validFiles: File[] = []; + let hasInvalidFiles = false; + + Array.from(files).forEach(file => { + if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) { + validFiles.push(file); + } else { + hasInvalidFiles = true; + } + }); + + if (hasInvalidFiles) { + // 显示错误提示 + messageService.error('只能上传PDF格式的文件', { + title: '文件类型错误', + confirmText: '确定', + cancelText: '', + }); + } + + if (validFiles.length > 0) { + setCurrentFiles(validFiles); + if (fileType) { + startUpload(validFiles); + } } } }; @@ -433,6 +455,15 @@ export default function FilesUpload() { // 开始上传文件 const startUpload = async (files: File[]) => { try { + // 再次验证所有文件类型,确保只有PDF文件 + const invalidFiles = files.filter(file => + file.type !== 'application/pdf' && !file.name.toLowerCase().endsWith('.pdf') + ); + + if (invalidFiles.length > 0) { + throw new Error('只能上传PDF格式的文件'); + } + setUploadStage("uploading"); setUploadProgress(0); @@ -564,6 +595,8 @@ export default function FilesUpload() { // 显示错误提示 messageService.error(`文件上传失败:${error instanceof Error ? error.message : '未知错误'}`, { title: '文件上传失败', + confirmText: '确定', + cancelText: '', onConfirm: () => { resetUpload(); } diff --git a/app/styles/components/date-range-picker.css b/app/styles/components/date-range-picker.css index 0fe8d2b..5fdb975 100644 --- a/app/styles/components/date-range-picker.css +++ b/app/styles/components/date-range-picker.css @@ -20,20 +20,83 @@ color: #666; } +/* 隐藏原生日期选择器的外观 */ +.date-input-wrapper { + position: relative; + width: 100%; + z-index: 1; +} + +/* 输入框聚焦时的样式 */ +.date-input-wrapper.focused .date-display { + border-color: var(--primary-color); + box-shadow: 0 0 0 2px rgba(0,104,74, 0.2); +} + +/* + 修改日期输入的位置和大小,使其覆盖整个显示区域, + 提高z-index确保它能接收点击事件 +*/ .date-input { - padding: 6px 8px; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0; + cursor: pointer; + z-index: 2; /* 提高z-index以确保接收点击事件 */ + margin: 0; + padding: 0; +} + +/* 自定义显示样式 */ +.date-display { + padding: 6px 12px; border: 1px solid #e0e0e0; border-radius: 4px; font-size: 14px; color: #333; - transition: all 0.2s ease; background-color: #fff; width: 100%; + min-height: 36px; + display: flex; + align-items: center; + transition: all 0.2s ease; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 8px center; + background-size: 16px; + padding-right: 32px; + cursor: pointer; + outline: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + position: relative; + z-index: 1; } -.date-input:focus { +/* 占位符样式 */ +.date-display.placeholder { + color: #999; +} + +/* 悬停状态 */ +.date-display:hover { + border-color: #c0c0c0; + background-color: #fafafa; +} + +/* 焦点/键盘聚焦状态 */ +.date-display:focus { + border-color: var(--primary-color); + box-shadow: 0 0 0 2px rgba(0,104,74, 0.2); +} + +.date-input:focus + .date-display { border-color: var(--primary-color); - outline: none; box-shadow: 0 0 0 2px rgba(0,104,74, 0.2); } @@ -41,8 +104,8 @@ margin: 0 4px; color: #999; font-size: 14px; - align-self: flex-end; - padding-bottom: 8px; + align-self: center; + padding-bottom: 0; } .simple-date-range-picker .date-range-fields { @@ -50,17 +113,23 @@ align-items: center; } -.simple-date-range-picker .date-input { +.simple-date-range-picker .date-input-wrapper { min-width: 130px; max-width: 150px; } +/* 增强按钮式外观 */ +.date-display:active { + background-color: #f5f5f5; + border-color: #a0a0a0; +} + /* 响应式调整 */ @media (max-width: 640px) { .date-range-fields { flex-direction: column; align-items: flex-start; - gap: 8px; + gap: 12px; } .date-separator { @@ -82,17 +151,47 @@ color: #b0b0b0; } - .date-input { + .date-display { background-color: #1f1f1f; border-color: #444; color: #e0e0e0; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23aaa' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E"); } - .date-input:focus { + /* 深色模式占位符 */ + .date-display.placeholder { + color: #777; + } + + /* 深色模式悬停状态 */ + .date-display:hover { + border-color: #555; + background-color: #2a2a2a; + } + + /* 深色模式聚焦的输入容器 */ + .date-input-wrapper.focused .date-display { border-color: #177ddc; box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2); } + .date-input:focus + .date-display { + border-color: #177ddc; + box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2); + } + + /* 深色模式焦点状态 */ + .date-display:focus { + border-color: #177ddc; + box-shadow: 0 0 0 2px rgba(23, 125, 220, 0.2); + } + + /* 深色模式按下状态 */ + .date-display:active { + background-color: #2d2d2d; + border-color: #666; + } + .date-separator { color: #888; }