diff --git a/crop-x-new/next-env.d.ts b/crop-x-new/next-env.d.ts
index c4b7818..9edff1c 100644
--- a/crop-x-new/next-env.d.ts
+++ b/crop-x-new/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-import "./.next/dev/types/routes.d.ts";
+import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/crop-x-new/package-lock.json b/crop-x-new/package-lock.json
index 5b49d96..c846cd7 100644
--- a/crop-x-new/package-lock.json
+++ b/crop-x-new/package-lock.json
@@ -39,6 +39,7 @@
"axios": "^1.13.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "date-fns": "^4.1.0",
"dotenv": "^17.2.3",
"lucide-react": "^0.553.0",
"next": "16.0.1",
@@ -46,7 +47,9 @@
"npx": "^10.2.2",
"openapi-fetch": "^0.15.0",
"react": "19.2.0",
+ "react-day-picker": "^9.11.1",
"react-dom": "19.2.0",
+ "react-hook-form": "^7.66.0",
"recharts": "^3.4.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0",
@@ -60,6 +63,7 @@
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.0.1",
+ "eslint-plugin-unused-imports": "^4.3.0",
"tailwindcss": "^4",
"tw-animate-css": "^1.4.0",
"typescript": "^5"
@@ -318,6 +322,12 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@date-fns/tz": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmmirror.com/@date-fns/tz/-/tz-1.4.1.tgz",
+ "integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==",
+ "license": "MIT"
+ },
"node_modules/@emnapi/core": {
"version": "1.7.0",
"resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.7.0.tgz",
@@ -4924,6 +4934,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/date-fns": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-4.1.0.tgz",
+ "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
+ "node_modules/date-fns-jalali": {
+ "version": "4.1.0-0",
+ "resolved": "https://registry.npmmirror.com/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz",
+ "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==",
+ "license": "MIT"
+ },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
@@ -5671,6 +5697,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/eslint-plugin-unused-imports": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmmirror.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.3.0.tgz",
+ "integrity": "sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0",
+ "eslint": "^9.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@typescript-eslint/eslint-plugin": {
+ "optional": true
+ }
+ }
+ },
"node_modules/eslint-scope": {
"version": "8.4.0",
"resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-8.4.0.tgz",
@@ -12544,6 +12586,27 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-day-picker": {
+ "version": "9.11.1",
+ "resolved": "https://registry.npmmirror.com/react-day-picker/-/react-day-picker-9.11.1.tgz",
+ "integrity": "sha512-l3ub6o8NlchqIjPKrRFUCkTUEq6KwemQlfv3XZzzwpUeGwmDJ+0u0Upmt38hJyd7D/vn2dQoOoLV/qAp0o3uUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@date-fns/tz": "^1.4.1",
+ "date-fns": "^4.1.0",
+ "date-fns-jalali": "^4.1.0-0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/gpbl"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
"node_modules/react-dom": {
"version": "19.2.0",
"resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.0.tgz",
@@ -12556,6 +12619,22 @@
"react": "^19.2.0"
}
},
+ "node_modules/react-hook-form": {
+ "version": "7.66.0",
+ "resolved": "https://registry.npmmirror.com/react-hook-form/-/react-hook-form-7.66.0.tgz",
+ "integrity": "sha512-xXBqsWGKrY46ZqaHDo+ZUYiMUgi8suYu5kdrS20EG8KiL7VRQitEbNjm+UcrDYrNi1YLyfpmAeGjCZYXLT9YBw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
+ }
+ },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz",
diff --git a/crop-x-new/package.json b/crop-x-new/package.json
index 4b3fe96..3c181ce 100644
--- a/crop-x-new/package.json
+++ b/crop-x-new/package.json
@@ -42,6 +42,7 @@
"axios": "^1.13.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "date-fns": "^4.1.0",
"dotenv": "^17.2.3",
"lucide-react": "^0.553.0",
"next": "16.0.1",
@@ -49,7 +50,9 @@
"npx": "^10.2.2",
"openapi-fetch": "^0.15.0",
"react": "19.2.0",
+ "react-day-picker": "^9.11.1",
"react-dom": "19.2.0",
+ "react-hook-form": "^7.66.0",
"recharts": "^3.4.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0",
@@ -63,6 +66,7 @@
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.0.1",
+ "eslint-plugin-unused-imports": "^4.3.0",
"tailwindcss": "^4",
"tw-animate-css": "^1.4.0",
"typescript": "^5"
diff --git a/crop-x-new/src/app/(app)/ai-crop-model/data-sense-center/device-parameter/components/AddParameterDialog.tsx b/crop-x-new/src/app/(app)/ai-crop-model/data-sense-center/device-parameter/components/AddParameterDialog.tsx
index 7cf085b..0d00078 100644
--- a/crop-x-new/src/app/(app)/ai-crop-model/data-sense-center/device-parameter/components/AddParameterDialog.tsx
+++ b/crop-x-new/src/app/(app)/ai-crop-model/data-sense-center/device-parameter/components/AddParameterDialog.tsx
@@ -147,7 +147,7 @@ export function AddParameterDialog({ open, onOpenChange, editingParam, selectedT
if (paramForm.min) newParam.min = parseFloat(paramForm.min);
if (paramForm.max) newParam.max = parseFloat(paramForm.max);
} else if (paramForm.type === 'boolean') {
- newParam.defaultValue = paramForm.defaultValue === 'true' || paramForm.defaultValue === true;
+ newParam.defaultValue = String(paramForm.defaultValue).toLowerCase() === 'true';
} else if (paramForm.type === 'select') {
newParam.options = paramForm.options;
newParam.defaultValue = paramForm.defaultValue || (paramForm.options[0]?.value || '');
diff --git a/crop-x-new/src/app/(app)/ai-crop-model/data-sense-center/iot/page.tsx b/crop-x-new/src/app/(app)/ai-crop-model/data-sense-center/iot/page.tsx
index c0ba227..d2d640a 100644
--- a/crop-x-new/src/app/(app)/ai-crop-model/data-sense-center/iot/page.tsx
+++ b/crop-x-new/src/app/(app)/ai-crop-model/data-sense-center/iot/page.tsx
@@ -1082,11 +1082,10 @@ export default function IoTIoTPage() {
diff --git a/crop-x-new/src/app/(app)/ai-crop-model/model-application/generation/components/ApplicationList.tsx b/crop-x-new/src/app/(app)/ai-crop-model/model-application/generation/components/ApplicationList.tsx
index e77866b..d92c764 100644
--- a/crop-x-new/src/app/(app)/ai-crop-model/model-application/generation/components/ApplicationList.tsx
+++ b/crop-x-new/src/app/(app)/ai-crop-model/model-application/generation/components/ApplicationList.tsx
@@ -33,6 +33,7 @@ import {
Table as TableIcon,
Type,
Rocket,
+ AlertCircle
} from 'lucide-react';
import { toast } from 'sonner';
@@ -271,7 +272,7 @@ export default function ApplicationList({ state, dispatch }: ApplicationListProp
)
}
\ No newline at end of file
diff --git a/crop-x-new/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryDetailDialog.tsx b/crop-x-new/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryDetailDialog.tsx
index 6a683be..cb543c4 100644
--- a/crop-x-new/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryDetailDialog.tsx
+++ b/crop-x-new/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryDetailDialog.tsx
@@ -9,7 +9,8 @@ import { ScrollArea } from '@/components/ui/scroll-area';
import { Card } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { FileText, Building, CreditCard, User } from 'lucide-react';
-import { AuditRecord, Enterprise, AuditStatus } from '../types';
+import { AuditRecord } from './auditHistoryApi';
+import { Enterprise, AuditStatus } from '../types';
interface AuditHistoryDetailDialogProps {
record: AuditRecord | null;
diff --git a/crop-x-new/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryList.tsx b/crop-x-new/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryList.tsx
index 9d96356..8f8df46 100644
--- a/crop-x-new/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryList.tsx
+++ b/crop-x-new/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryList.tsx
@@ -6,7 +6,8 @@ import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Eye } from 'lucide-react';
-import { AuditRecord, AuditStatus } from '../types';
+import { AuditRecord } from './auditHistoryApi';
+import { AuditStatus } from '../types';
interface AuditHistoryListProps {
records: AuditRecord[];
diff --git a/crop-x-new/src/app/(app)/central-config/tenant/audit-history/components/auditHistoryApi.ts b/crop-x-new/src/app/(app)/central-config/tenant/audit-history/components/auditHistoryApi.ts
index db1c5aa..483dd2b 100644
--- a/crop-x-new/src/app/(app)/central-config/tenant/audit-history/components/auditHistoryApi.ts
+++ b/crop-x-new/src/app/(app)/central-config/tenant/audit-history/components/auditHistoryApi.ts
@@ -59,6 +59,10 @@ export interface AuditLogsQueryParams {
size?: number;
order_by?: string;
sort_order?: 'asc' | 'desc';
+ search_keyword?: string;
+ action?: string;
+ audit_status?: string;
+ date_range?: string;
}
// 审核记录页面数据类型(转换后的)
@@ -70,10 +74,14 @@ export interface AuditRecord {
auditType: 'register' | 'update';
submitTime: string;
actionTime: string;
+ auditTime: string; // 审核时间,与actionTime相同
actionBy: string;
+ auditor: string; // 审核人,与actionBy相同
result: 'pending' | 'approved' | 'rejected' | 'draft';
auditStatus: string;
auditComment?: string;
+ reason?: string; // 审核原因,与auditComment相同
+ remarks?: string; // 备注信息
changeSummary: string;
ipAddress?: string;
userAgent?: string;
@@ -103,16 +111,19 @@ export interface AuditRecord {
};
}
+// 调用计数器
+let callCount = 0;
+
/**
* 获取审核历史记录数据
*/
export async function fetchAuditLogs(params: AuditLogsQueryParams = {}): Promise {
try {
// 调用计数器
- console.log(`[API] fetchAuditLogs 调用次数: ${++fetchAuditLogs.callCount || (fetchAuditLogs.callCount = 1)}`, params);
+ console.log(`[API] fetchAuditLogs 调用次数: ${++callCount}`, params);
// 构建查询参数对象
- const queryParams: any = {};
+ const queryParams: Record = {};
queryParams.tenant_id = "";
if (params.page) queryParams.page = params.page;
@@ -127,21 +138,39 @@ export async function fetchAuditLogs(params: AuditLogsQueryParams = {}): Promise
// 使用SDK API调用审核历史查询接口,添加缓存破坏器和认证头部
const token = getAuthToken();
const response = await getTenantAuditLogsApiV1TenantsAuditLogsGet({
- query: {
- ...queryParams,
- // 添加时间戳防止缓存
- _t: Date.now(),
- },
+ query: queryParams,
headers: token ? {
'Authorization': `Bearer ${token}`,
} : undefined,
});
if (response.error) {
- throw new Error(`API error: ${response.error.message || 'Unknown error'}`);
+ // 尝试多种可能的错误消息路径
+ const errorDetail = response.error.detail as any;
+ let errorMessage = 'Unknown error';
+
+ if (typeof errorDetail === 'string') {
+ errorMessage = errorDetail;
+ } else if (errorDetail?.message) {
+ errorMessage = errorDetail.message;
+ } else if (Array.isArray(errorDetail)) {
+ errorMessage = errorDetail.map(d => d.msg || d.message || 'Error').join(', ');
+ } else if ((response.error as any).message) {
+ errorMessage = (response.error as any).message;
+ }
+
+ throw new Error(`API error: ${errorMessage}`);
}
- const data = response.data as any;
+ const data = response.data as unknown as {
+ data?: AuditLogData[];
+ total?: number;
+ page?: number;
+ size?: number;
+ total_pages?: number;
+ has_next?: boolean;
+ has_prev?: boolean;
+ };
// 转换响应数据格式以匹配现有的接口
return {
@@ -190,10 +219,14 @@ export function transformAuditLogData(log: AuditLogData): AuditRecord {
auditType,
submitTime: formatDate(log.action_time),
actionTime: formatDate(log.action_time),
+ auditTime: formatDate(log.action_time), // 审核时间,与actionTime相同
actionBy: log.action_by,
+ auditor: log.action_by, // 审核人,与actionBy相同
result,
auditStatus: log.snapshot_audit_status,
auditComment: log.snapshot_audit_comment,
+ reason: log.snapshot_audit_comment, // 审核原因,与auditComment相同
+ remarks: log.change_summary, // 备注信息,使用变更摘要
changeSummary: log.change_summary,
ipAddress: log.ip_address,
userAgent: log.user_agent,
diff --git a/crop-x-new/src/app/(app)/central-config/tenant/audit-history/page.tsx b/crop-x-new/src/app/(app)/central-config/tenant/audit-history/page.tsx
index b5e8b11..4b8827b 100644
--- a/crop-x-new/src/app/(app)/central-config/tenant/audit-history/page.tsx
+++ b/crop-x-new/src/app/(app)/central-config/tenant/audit-history/page.tsx
@@ -6,7 +6,7 @@
*/
'use client';
-import { useMemo, useState, useCallback, useEffect ,useRef} from 'react';
+import React, { useState, useCallback, useEffect ,useRef} from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { ScrollArea } from '@/components/ui/scroll-area';
@@ -29,7 +29,27 @@ import SearchFormPagination, {
type TableColumnConfig
} from '@/components/common/searchFormPagination';
-import { fetchAuditLogs, transformAuditLogData, AuditLogsQueryParams, AuditLogData } from './components/auditHistoryApi';
+import { fetchAuditLogs, transformAuditLogData, AuditLogsQueryParams, AuditRecord, AuditLogData } from './components/auditHistoryApi';
+
+// URL参数类型定义
+interface UrlParams {
+ search?: string;
+ action?: string;
+ audit_status?: string;
+ date_range?: string;
+ page?: number;
+ size?: number;
+}
+
+// 分页状态类型定义
+interface PaginationState {
+ page: number;
+ size: number;
+ total: number;
+ totalPages: number;
+ hasNext: boolean;
+ hasPrev: boolean;
+}
// Utility functions
const getActionBadge = (action: string) => {
@@ -94,7 +114,7 @@ export default function AuditHistoryPage() {
// 对话框状态管理
const [dialogs, setDialogs] = useState({
showViewDialog: false,
- selectedRecord: null as AuditLogData | null
+ selectedRecord: null as AuditRecord | null
});
const dispatch = (action: any) => {
@@ -224,8 +244,8 @@ export default function AuditHistoryPage() {
},
];
// 简化的状态管理 - 只需要存储数据和加载状态
- const [records, setRecords] = useState([]);
- const [pagination, setPagination] = useState({
+ const [records, setRecords] = useState([]);
+ const [pagination, setPagination] = useState({
page: 1,
size: 10,
total: 0,
@@ -253,7 +273,7 @@ export default function AuditHistoryPage() {
} = {}) => {
try {
// 优先从URL读取参数
- let urlParams = {};
+ let urlParams: UrlParams = {};
if (typeof window !== 'undefined') {
const params = new URLSearchParams(window.location.search);
urlParams = {
@@ -304,6 +324,12 @@ export default function AuditHistoryPage() {
params.search_keyword = currentFilters.search;
}
+ // 添加排序条件
+ if (currentSortBy) {
+ params.order_by = currentSortBy;
+ params.sort_order = currentSortOrder;
+ }
+
if (currentFilters.action && currentFilters.action !== 'all') {
params.action = currentFilters.action;
}
@@ -482,23 +508,22 @@ useEffect(() => {
{/* 使用SearchFormPagination组件 */}
- }
- emptyText="暂无审核记录"
- sizeOptions={[10, 20, 50, 100]}
-
- />
+ {React.createElement(SearchFormPagination as any, {
+ formTitle: "审核历史记录",
+ searchFields,
+ columns,
+ data: records,
+ loading,
+ error,
+ pagination: pagination as any,
+ onPageChange: handlePageChange,
+ onSizeChange: handleSizeChange,
+ onSearch: handleSearch,
+ onSort: handleSort,
+ emptyIcon: ,
+ emptyText: "暂无审核记录",
+ sizeOptions: [10, 20, 50, 100]
+ })}
{/* View Audit Record Details Dialog */}