Compare commits
2 Commits
dcd7ddeb71
...
68d9d97142
| Author | SHA1 | Date | |
|---|---|---|---|
| 68d9d97142 | |||
| dfc29ce01f |
2
next-env.d.ts
vendored
2
next-env.d.ts
vendored
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
import "./.next/dev/types/routes.d.ts";
|
import "./.next/types/routes.d.ts";
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|||||||
79
package-lock.json
generated
79
package-lock.json
generated
@@ -39,6 +39,7 @@
|
|||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"lucide-react": "^0.553.0",
|
"lucide-react": "^0.553.0",
|
||||||
"next": "16.0.1",
|
"next": "16.0.1",
|
||||||
@@ -46,7 +47,9 @@
|
|||||||
"npx": "^10.2.2",
|
"npx": "^10.2.2",
|
||||||
"openapi-fetch": "^0.15.0",
|
"openapi-fetch": "^0.15.0",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
|
"react-day-picker": "^9.11.1",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
|
"react-hook-form": "^7.66.0",
|
||||||
"recharts": "^3.4.1",
|
"recharts": "^3.4.1",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
@@ -60,6 +63,7 @@
|
|||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "16.0.1",
|
"eslint-config-next": "16.0.1",
|
||||||
|
"eslint-plugin-unused-imports": "^4.3.0",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
@@ -318,6 +322,12 @@
|
|||||||
"node": ">=6.9.0"
|
"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": {
|
"node_modules/@emnapi/core": {
|
||||||
"version": "1.7.0",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.7.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.7.0.tgz",
|
||||||
@@ -4924,6 +4934,22 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
|
||||||
@@ -5671,6 +5697,22 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/eslint-scope": {
|
||||||
"version": "8.4.0",
|
"version": "8.4.0",
|
||||||
"resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-8.4.0.tgz",
|
"resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-8.4.0.tgz",
|
||||||
@@ -12544,6 +12586,27 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/react-dom": {
|
||||||
"version": "19.2.0",
|
"version": "19.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.0.tgz",
|
"resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.0.tgz",
|
||||||
@@ -12556,6 +12619,22 @@
|
|||||||
"react": "^19.2.0"
|
"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": {
|
"node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"lucide-react": "^0.553.0",
|
"lucide-react": "^0.553.0",
|
||||||
"next": "16.0.1",
|
"next": "16.0.1",
|
||||||
@@ -49,7 +50,9 @@
|
|||||||
"npx": "^10.2.2",
|
"npx": "^10.2.2",
|
||||||
"openapi-fetch": "^0.15.0",
|
"openapi-fetch": "^0.15.0",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
|
"react-day-picker": "^9.11.1",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
|
"react-hook-form": "^7.66.0",
|
||||||
"recharts": "^3.4.1",
|
"recharts": "^3.4.1",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
@@ -63,6 +66,7 @@
|
|||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "16.0.1",
|
"eslint-config-next": "16.0.1",
|
||||||
|
"eslint-plugin-unused-imports": "^4.3.0",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ export function AddParameterDialog({ open, onOpenChange, editingParam, selectedT
|
|||||||
if (paramForm.min) newParam.min = parseFloat(paramForm.min);
|
if (paramForm.min) newParam.min = parseFloat(paramForm.min);
|
||||||
if (paramForm.max) newParam.max = parseFloat(paramForm.max);
|
if (paramForm.max) newParam.max = parseFloat(paramForm.max);
|
||||||
} else if (paramForm.type === 'boolean') {
|
} 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') {
|
} else if (paramForm.type === 'select') {
|
||||||
newParam.options = paramForm.options;
|
newParam.options = paramForm.options;
|
||||||
newParam.defaultValue = paramForm.defaultValue || (paramForm.options[0]?.value || '');
|
newParam.defaultValue = paramForm.defaultValue || (paramForm.options[0]?.value || '');
|
||||||
|
|||||||
@@ -1082,11 +1082,10 @@ export default function IoTIoTPage() {
|
|||||||
<div className="mt-3 h-16">
|
<div className="mt-3 h-16">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<LineChart data={[
|
<LineChart data={[
|
||||||
{ time: 1, value: parseFloat(sensor.currentValue) - 2 },
|
{ time: 1, value: sensor.currentValue - 2 },
|
||||||
{ time: 2, value: parseFloat(sensor.currentValue) - 1.5 },
|
{ time: 2, value: sensor.currentValue - 1.5 },
|
||||||
{ time: 3, value: parseFloat(sensor.currentValue) - 1 },
|
{ time: 3, value: sensor.currentValue - 1 },
|
||||||
{ time: 4, value: parseFloat(sensor.currentValue) - 0.5 },
|
{ time: 4, value: sensor.currentValue - 0.5 },
|
||||||
{ time: 5, value: parseFloat(sensor.currentValue) },
|
|
||||||
]}>
|
]}>
|
||||||
<Line type="monotone" dataKey="value" stroke="#10b981" strokeWidth={2} dot={false} />
|
<Line type="monotone" dataKey="value" stroke="#10b981" strokeWidth={2} dot={false} />
|
||||||
</LineChart>
|
</LineChart>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
Table as TableIcon,
|
Table as TableIcon,
|
||||||
Type,
|
Type,
|
||||||
Rocket,
|
Rocket,
|
||||||
|
AlertCircle
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
@@ -271,7 +272,7 @@ export default function ApplicationList({ state, dispatch }: ApplicationListProp
|
|||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className={app.status === '已停止' ? 'flex-1' : ''}
|
className={app.status !== '运行中' ? 'flex-1' : ''}
|
||||||
onClick={() => handleToggleStatus(app.id)}
|
onClick={() => handleToggleStatus(app.id)}
|
||||||
>
|
>
|
||||||
<PauseCircle className="w-3 h-3" />
|
<PauseCircle className="w-3 h-3" />
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
* 规范:遵循crop-x/docs/开发项目规范.md,使用useReducer状态管理模式
|
* 规范:遵循crop-x/docs/开发项目规范.md,使用useReducer状态管理模式
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { safeLocalStorage } from '@/utils/storage';
|
||||||
|
|
||||||
// 决策类型
|
// 决策类型
|
||||||
export type DecisionType = 'irrigation' | 'fertilizer' | 'pesticide' | 'harvest' | 'soil' | 'weather';
|
export type DecisionType = 'irrigation' | 'fertilizer' | 'pesticide' | 'harvest' | 'soil' | 'weather';
|
||||||
|
|
||||||
@@ -303,29 +305,25 @@ const calculateLatestDecisions = (decisions: DecisionRecord[]): DecisionRecord[]
|
|||||||
|
|
||||||
// 保存到本地存储
|
// 保存到本地存储
|
||||||
const saveToStorage = (state: AIDecisionDashboardState) => {
|
const saveToStorage = (state: AIDecisionDashboardState) => {
|
||||||
try {
|
safeLocalStorage.setItem('ai-decision-dashboard', JSON.stringify({
|
||||||
localStorage.setItem('ai-decision-dashboard', JSON.stringify({
|
|
||||||
decisions: state.decisions,
|
decisions: state.decisions,
|
||||||
lastUpdated: state.lastUpdated,
|
lastUpdated: state.lastUpdated,
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
|
||||||
console.warn('Failed to save to localStorage:', error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 从本地存储加载
|
// 从本地存储加载
|
||||||
const loadFromStorage = () => {
|
const loadFromStorage = () => {
|
||||||
try {
|
const stored = safeLocalStorage.getItem('ai-decision-dashboard');
|
||||||
const stored = localStorage.getItem('ai-decision-dashboard');
|
|
||||||
if (stored) {
|
if (stored) {
|
||||||
|
try {
|
||||||
const data = JSON.parse(stored);
|
const data = JSON.parse(stored);
|
||||||
return {
|
return {
|
||||||
decisions: data.decisions || initialDecisions,
|
decisions: data.decisions || initialDecisions,
|
||||||
lastUpdated: data.lastUpdated || new Date().toISOString(),
|
lastUpdated: data.lastUpdated || new Date().toISOString(),
|
||||||
};
|
};
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to load from localStorage:', error);
|
console.warn('Failed to parse stored data:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ interface MessagePreviewDialogProps {
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
record: MessageSendRecord | null;
|
record: MessageSendRecord | null;
|
||||||
getTypeIcon: (type: string) => JSX.Element;
|
getTypeIcon: (type: string) => React.ReactNode;
|
||||||
getTypeLabel: (type: string) => string;
|
getTypeLabel: (type: string) => string;
|
||||||
getTypeBadge: (type: string) => string;
|
getTypeBadge: (type: string) => string;
|
||||||
getStatusBadge: (status: string) => JSX.Element;
|
getStatusBadge: (status: string) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MessagePreviewDialog({
|
export function MessagePreviewDialog({
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ interface MessageSendTableProps {
|
|||||||
onPreview: (record: MessageSendRecord) => void;
|
onPreview: (record: MessageSendRecord) => void;
|
||||||
onCancel: (id: string) => void;
|
onCancel: (id: string) => void;
|
||||||
onDelete: (id: string) => void;
|
onDelete: (id: string) => void;
|
||||||
getTypeIcon: (type: string) => JSX.Element;
|
getTypeIcon: (type: string) => React.ReactNode;
|
||||||
getTypeLabel: (type: string) => string;
|
getTypeLabel: (type: string) => string;
|
||||||
getTypeBadge: (type: string) => string;
|
getTypeBadge: (type: string) => string;
|
||||||
getStatusBadge: (status: string) => JSX.Element;
|
getStatusBadge: (status: string) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MessageSendTable({
|
export function MessageSendTable({
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ interface SendMessageDialogProps {
|
|||||||
formData: MessageSendFormData;
|
formData: MessageSendFormData;
|
||||||
onFormDataChange: (data: MessageSendFormData) => void;
|
onFormDataChange: (data: MessageSendFormData) => void;
|
||||||
onSend: () => void;
|
onSend: () => void;
|
||||||
getTypeIcon: (type: string) => JSX.Element;
|
getTypeIcon: (type: string) => React.ReactNode;
|
||||||
getTypeLabel: (type: string) => string;
|
getTypeLabel: (type: string) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,19 +62,6 @@ export default function MessageTemplatePage() {
|
|||||||
updatedAt: '2024-01-01T00:00:00',
|
updatedAt: '2024-01-01T00:00:00',
|
||||||
createdBy: 'admin',
|
createdBy: 'admin',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'tpl-2',
|
|
||||||
code: 'EQUIPMENT_WARNING',
|
|
||||||
name: '设备预警通知',
|
|
||||||
type: 'sms',
|
|
||||||
content: '【智慧农业】设备预警:{{equipmentName}}检测到异常,{{warningType}},请及时处理。',
|
|
||||||
variables: ['equipmentName', 'warningType'],
|
|
||||||
isActive: true,
|
|
||||||
description: '设备出现异常时发送短信通知',
|
|
||||||
createdAt: '2024-01-01T00:00:00',
|
|
||||||
updatedAt: '2024-01-01T00:00:00',
|
|
||||||
createdBy: 'admin',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'tpl-3',
|
id: 'tpl-3',
|
||||||
code: 'MAINTENANCE_REMINDER',
|
code: 'MAINTENANCE_REMINDER',
|
||||||
@@ -200,7 +187,7 @@ export default function MessageTemplatePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查变量是否都填写了
|
// 检查变量是否都填写了
|
||||||
const emptyVars = Object.entries(testData.variables).filter(([k, v]) => !v.trim());
|
const emptyVars = Object.entries(testData.variables).filter(([k, v]) => !(v as string).trim());
|
||||||
if (emptyVars.length > 0) {
|
if (emptyVars.length > 0) {
|
||||||
toast.error('请填写变量:' + emptyVars.map(([k]) => k).join(', '));
|
toast.error('请填写变量:' + emptyVars.map(([k]) => k).join(', '));
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export interface LoginLogsQueryParams {
|
|||||||
ip_address?: string;
|
ip_address?: string;
|
||||||
sort_order?: 'asc' | 'desc';
|
sort_order?: 'asc' | 'desc';
|
||||||
order_by?: string;
|
order_by?: string;
|
||||||
|
keyword?:string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分页状态接口
|
// 分页状态接口
|
||||||
@@ -83,13 +84,11 @@ export const fetchLoginLogs = async (params: LoginLogsQueryParams = {}) => {
|
|||||||
query: {
|
query: {
|
||||||
page: params.page || 1,
|
page: params.page || 1,
|
||||||
size: params.size || 10,
|
size: params.size || 10,
|
||||||
username: params.username,
|
keyword: params.keyword,
|
||||||
status: params.status,
|
status: params.status,
|
||||||
start_time: params.start_time,
|
start_time: params.start_time,
|
||||||
end_time: params.end_time,
|
end_time: params.end_time,
|
||||||
ip_address: params.ip_address,
|
ip_address: params.ip_address,
|
||||||
sort_order: params.sort_order || 'desc',
|
|
||||||
order_by: params.order_by || 'created_at',
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
LoginLog,
|
LoginLog,
|
||||||
PaginationState,
|
PaginationState,
|
||||||
LoginLogsQueryParams,
|
LoginLogsQueryParams,
|
||||||
fetchLoginStatistics,
|
|
||||||
exportLoginLogs
|
exportLoginLogs
|
||||||
} from './components/loginLogApi';
|
} from './components/loginLogApi';
|
||||||
|
|
||||||
@@ -39,7 +38,6 @@ export default function LoginLogPage() {
|
|||||||
search: '',
|
search: '',
|
||||||
status: 'all'
|
status: 'all'
|
||||||
});
|
});
|
||||||
const isFirstLoad = useRef(true);
|
|
||||||
|
|
||||||
// 搜索字段配置
|
// 搜索字段配置
|
||||||
const searchFields: SearchFieldConfig[] = [
|
const searchFields: SearchFieldConfig[] = [
|
||||||
|
|||||||
@@ -2,24 +2,19 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Card } from '@/components/ui/card'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Textarea } from '@/components/ui/textarea'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
import { SystemSettings } from '@/types/system-params'
|
import { SystemSettings } from '@/types/system-params'
|
||||||
import { Save, RefreshCw, Info, Shield, Globe } from 'lucide-react'
|
import { Save, RefreshCw, Info, Palette, Settings } from 'lucide-react'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
import { useTheme } from 'next-themes'
|
||||||
// Import modular components
|
|
||||||
import {
|
|
||||||
PlatformInfoCard,
|
|
||||||
SystemAnnouncementCard,
|
|
||||||
CopyrightInfoCard,
|
|
||||||
FeatureToggleCard,
|
|
||||||
SessionManagementCard,
|
|
||||||
PasswordPolicyCard,
|
|
||||||
RegionalSettingsCard,
|
|
||||||
SettingsInfoCard
|
|
||||||
} from './components'
|
|
||||||
|
|
||||||
export default function SystemSettingsPage() {
|
export default function SystemSettingsPage() {
|
||||||
|
const { setTheme } = useTheme()
|
||||||
const [settings, setSettings] = useState<SystemSettings>({
|
const [settings, setSettings] = useState<SystemSettings>({
|
||||||
platformName: '智慧农业生产管理系统',
|
platformName: '智慧农业生产管理系统',
|
||||||
platformLogo: '',
|
platformLogo: '',
|
||||||
@@ -27,23 +22,7 @@ export default function SystemSettingsPage() {
|
|||||||
contactEmail: 'support@smart-agriculture.com',
|
contactEmail: 'support@smart-agriculture.com',
|
||||||
contactPhone: '400-888-8888',
|
contactPhone: '400-888-8888',
|
||||||
address: '北京市海淀区中关村大街1号',
|
address: '北京市海淀区中关村大街1号',
|
||||||
companyName: '智慧农业科技有限公司',
|
defaultTheme: 'light',
|
||||||
icp: '京ICP备12345678号',
|
|
||||||
copyright: '© 2024 智慧农业科技有限公司 版权所有',
|
|
||||||
enableRegistration: true,
|
|
||||||
enableGuestAccess: false,
|
|
||||||
sessionTimeout: 30,
|
|
||||||
maxLoginAttempts: 5,
|
|
||||||
passwordPolicy: {
|
|
||||||
minLength: 8,
|
|
||||||
requireUppercase: true,
|
|
||||||
requireLowercase: true,
|
|
||||||
requireNumbers: true,
|
|
||||||
requireSpecialChars: false,
|
|
||||||
},
|
|
||||||
dateFormat: 'YYYY-MM-DD',
|
|
||||||
timezone: 'Asia/Shanghai',
|
|
||||||
language: 'zh-CN',
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const [hasChanges, setHasChanges] = useState(false)
|
const [hasChanges, setHasChanges] = useState(false)
|
||||||
@@ -65,6 +44,12 @@ export default function SystemSettingsPage() {
|
|||||||
localStorage.setItem('smart_agriculture_system_settings', JSON.stringify(newSettings))
|
localStorage.setItem('smart_agriculture_system_settings', JSON.stringify(newSettings))
|
||||||
setSettings(newSettings)
|
setSettings(newSettings)
|
||||||
setHasChanges(false)
|
setHasChanges(false)
|
||||||
|
|
||||||
|
// 应用默认主题设置
|
||||||
|
if (newSettings.defaultTheme) {
|
||||||
|
setTheme(newSettings.defaultTheme)
|
||||||
|
}
|
||||||
|
|
||||||
toast.success('系统设置已保存')
|
toast.success('系统设置已保存')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,59 +95,147 @@ export default function SystemSettingsPage() {
|
|||||||
<Info className="w-4 h-4 mr-2" />
|
<Info className="w-4 h-4 mr-2" />
|
||||||
基本设置
|
基本设置
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="security">
|
<TabsTrigger value="appearance">
|
||||||
<Shield className="w-4 h-4 mr-2" />
|
<Palette className="w-4 h-4 mr-2" />
|
||||||
安全设置
|
外观设置
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger value="regional">
|
|
||||||
<Globe className="w-4 h-4 mr-2" />
|
|
||||||
区域设置
|
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
{/* 基本设置 */}
|
{/* 基本设置 */}
|
||||||
<TabsContent value="basic" className="space-y-4">
|
<TabsContent value="basic" className="space-y-4">
|
||||||
<PlatformInfoCard
|
<Card className="p-6">
|
||||||
settings={settings}
|
<h3 className="mb-4">平台信息</h3>
|
||||||
onSettingsChange={updateSettings}
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label>平台名称 *</Label>
|
||||||
|
<Input
|
||||||
|
value={settings.platformName}
|
||||||
|
onChange={(e) => updateSettings({ platformName: e.target.value })}
|
||||||
|
placeholder="请输入平台名称"
|
||||||
/>
|
/>
|
||||||
<SystemAnnouncementCard
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
settings={settings}
|
平台名称将显示在系统导航栏和登录页面
|
||||||
onSettingsChange={updateSettings}
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>联系邮箱</Label>
|
||||||
|
<Input
|
||||||
|
type="email"
|
||||||
|
value={settings.contactEmail}
|
||||||
|
onChange={(e) => updateSettings({ contactEmail: e.target.value })}
|
||||||
|
placeholder="support@example.com"
|
||||||
/>
|
/>
|
||||||
<CopyrightInfoCard
|
</div>
|
||||||
settings={settings}
|
<div>
|
||||||
onSettingsChange={updateSettings}
|
<Label>联系电话</Label>
|
||||||
|
<Input
|
||||||
|
value={settings.contactPhone}
|
||||||
|
onChange={(e) => updateSettings({ contactPhone: e.target.value })}
|
||||||
|
placeholder="400-888-8888"
|
||||||
/>
|
/>
|
||||||
<FeatureToggleCard
|
</div>
|
||||||
settings={settings}
|
<div>
|
||||||
onSettingsChange={updateSettings}
|
<Label>公司地址</Label>
|
||||||
|
<Input
|
||||||
|
value={settings.address}
|
||||||
|
onChange={(e) => updateSettings({ address: e.target.value })}
|
||||||
|
placeholder="请输入公司地址"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="p-6">
|
||||||
|
<h3 className="mb-4">系统公告</h3>
|
||||||
|
<Textarea
|
||||||
|
value={settings.systemAnnouncement}
|
||||||
|
onChange={(e) => updateSettings({ systemAnnouncement: e.target.value })}
|
||||||
|
placeholder="输入系统公告内容,将显示在登录页面"
|
||||||
|
rows={4}
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground mt-2">
|
||||||
|
系统公告会在登录页面显著位置展示
|
||||||
|
</p>
|
||||||
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* 安全设置 */}
|
{/* 外观设置 */}
|
||||||
<TabsContent value="security" className="space-y-4">
|
<TabsContent value="appearance" className="space-y-4">
|
||||||
<SessionManagementCard
|
<Card className="p-6">
|
||||||
settings={settings}
|
<h3 className="mb-4">主题设置</h3>
|
||||||
onSettingsChange={updateSettings}
|
<div className="space-y-4">
|
||||||
/>
|
<div>
|
||||||
<PasswordPolicyCard
|
<Label>默认主题</Label>
|
||||||
settings={settings}
|
<Select
|
||||||
onSettingsChange={updateSettings}
|
value={settings.defaultTheme}
|
||||||
/>
|
onValueChange={(value: 'light' | 'dark') => updateSettings({ defaultTheme: value })}
|
||||||
</TabsContent>
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="light">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-4 h-4 rounded-full bg-white border-2 border-gray-300" />
|
||||||
|
<span>明亮模式</span>
|
||||||
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="dark">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-4 h-4 rounded-full bg-gray-900 border-2 border-gray-600" />
|
||||||
|
<span>暗黑模式</span>
|
||||||
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-xs text-muted-foreground mt-2">
|
||||||
|
设置系统默认主题,保存后立即生效。用户可以在导航栏手动切换主题。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* 区域设置 */}
|
<Card className="p-6 bg-blue-50 dark:bg-blue-950/30 border-blue-200 dark:border-blue-900">
|
||||||
<TabsContent value="regional" className="space-y-4">
|
<h4 className="text-blue-900 dark:text-blue-400 mb-2">
|
||||||
<RegionalSettingsCard
|
<Palette className="w-4 h-4 inline mr-2" />
|
||||||
settings={settings}
|
主题预览
|
||||||
onSettingsChange={updateSettings}
|
</h4>
|
||||||
/>
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="p-4 rounded-lg bg-white border-2 border-gray-300">
|
||||||
|
<p className="text-sm mb-2">明亮模式</p>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="h-2 bg-green-600 rounded" />
|
||||||
|
<div className="h-2 bg-gray-200 rounded" />
|
||||||
|
<div className="h-2 bg-gray-200 rounded w-3/4" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 rounded-lg bg-gray-900 border-2 border-gray-600">
|
||||||
|
<p className="text-sm text-white mb-2">暗黑模式</p>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="h-2 bg-green-500 rounded" />
|
||||||
|
<div className="h-2 bg-gray-700 rounded" />
|
||||||
|
<div className="h-2 bg-gray-700 rounded w-3/4" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
{/* 设置预览 */}
|
{/* 设置说明 */}
|
||||||
<SettingsInfoCard />
|
<Card className="p-4 bg-blue-50 dark:bg-blue-950/30 border-blue-200 dark:border-blue-900">
|
||||||
|
<h4 className="text-blue-900 dark:text-blue-400 mb-2">
|
||||||
|
<Settings className="w-4 h-4 inline mr-2" />
|
||||||
|
设置说明
|
||||||
|
</h4>
|
||||||
|
<ul className="space-y-1 text-sm text-blue-800 dark:text-blue-300">
|
||||||
|
<li>• <strong>基本设置</strong>:配置平台名称、联系方式和系统公告</li>
|
||||||
|
<li>• <strong>外观设置</strong>:设置系统默认主题(明亮/暗黑模式),保存后立即生效</li>
|
||||||
|
<li>• 平台名称将显示在系统导航栏和登录页面</li>
|
||||||
|
<li>• 系统公告会在登录页面显著位置展示</li>
|
||||||
|
<li>• 所有设置修改后需要点击"保存设置"按钮才会生效</li>
|
||||||
|
</ul>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,8 @@ import { ScrollArea } from '@/components/ui/scroll-area';
|
|||||||
import { Card } from '@/components/ui/card';
|
import { Card } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { FileText, Building, CreditCard, User } from 'lucide-react';
|
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 {
|
interface AuditHistoryDetailDialogProps {
|
||||||
record: AuditRecord | null;
|
record: AuditRecord | null;
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||||
import { Eye } from 'lucide-react';
|
import { Eye } from 'lucide-react';
|
||||||
import { AuditRecord, AuditStatus } from '../types';
|
import { AuditRecord } from './auditHistoryApi';
|
||||||
|
import { AuditStatus } from '../types';
|
||||||
|
|
||||||
interface AuditHistoryListProps {
|
interface AuditHistoryListProps {
|
||||||
records: AuditRecord[];
|
records: AuditRecord[];
|
||||||
|
|||||||
@@ -59,6 +59,10 @@ export interface AuditLogsQueryParams {
|
|||||||
size?: number;
|
size?: number;
|
||||||
order_by?: string;
|
order_by?: string;
|
||||||
sort_order?: 'asc' | 'desc';
|
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';
|
auditType: 'register' | 'update';
|
||||||
submitTime: string;
|
submitTime: string;
|
||||||
actionTime: string;
|
actionTime: string;
|
||||||
|
auditTime: string; // 审核时间,与actionTime相同
|
||||||
actionBy: string;
|
actionBy: string;
|
||||||
|
auditor: string; // 审核人,与actionBy相同
|
||||||
result: 'pending' | 'approved' | 'rejected' | 'draft';
|
result: 'pending' | 'approved' | 'rejected' | 'draft';
|
||||||
auditStatus: string;
|
auditStatus: string;
|
||||||
auditComment?: string;
|
auditComment?: string;
|
||||||
|
reason?: string; // 审核原因,与auditComment相同
|
||||||
|
remarks?: string; // 备注信息
|
||||||
changeSummary: string;
|
changeSummary: string;
|
||||||
ipAddress?: string;
|
ipAddress?: string;
|
||||||
userAgent?: string;
|
userAgent?: string;
|
||||||
@@ -103,16 +111,19 @@ export interface AuditRecord {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 调用计数器
|
||||||
|
let callCount = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取审核历史记录数据
|
* 获取审核历史记录数据
|
||||||
*/
|
*/
|
||||||
export async function fetchAuditLogs(params: AuditLogsQueryParams = {}): Promise<AuditLogsApiResponse> {
|
export async function fetchAuditLogs(params: AuditLogsQueryParams = {}): Promise<AuditLogsApiResponse> {
|
||||||
try {
|
try {
|
||||||
// 调用计数器
|
// 调用计数器
|
||||||
console.log(`[API] fetchAuditLogs 调用次数: ${++fetchAuditLogs.callCount || (fetchAuditLogs.callCount = 1)}`, params);
|
console.log(`[API] fetchAuditLogs 调用次数: ${++callCount}`, params);
|
||||||
|
|
||||||
// 构建查询参数对象
|
// 构建查询参数对象
|
||||||
const queryParams: any = {};
|
const queryParams: Record<string, any> = {};
|
||||||
|
|
||||||
queryParams.tenant_id = "";
|
queryParams.tenant_id = "";
|
||||||
if (params.page) queryParams.page = params.page;
|
if (params.page) queryParams.page = params.page;
|
||||||
@@ -127,21 +138,39 @@ export async function fetchAuditLogs(params: AuditLogsQueryParams = {}): Promise
|
|||||||
// 使用SDK API调用审核历史查询接口,添加缓存破坏器和认证头部
|
// 使用SDK API调用审核历史查询接口,添加缓存破坏器和认证头部
|
||||||
const token = getAuthToken();
|
const token = getAuthToken();
|
||||||
const response = await getTenantAuditLogsApiV1TenantsAuditLogsGet({
|
const response = await getTenantAuditLogsApiV1TenantsAuditLogsGet({
|
||||||
query: {
|
query: queryParams,
|
||||||
...queryParams,
|
|
||||||
// 添加时间戳防止缓存
|
|
||||||
_t: Date.now(),
|
|
||||||
},
|
|
||||||
headers: token ? {
|
headers: token ? {
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
} : undefined,
|
} : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.error) {
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = response.data as any;
|
throw new Error(`API error: ${errorMessage}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
return {
|
||||||
@@ -190,10 +219,14 @@ export function transformAuditLogData(log: AuditLogData): AuditRecord {
|
|||||||
auditType,
|
auditType,
|
||||||
submitTime: formatDate(log.action_time),
|
submitTime: formatDate(log.action_time),
|
||||||
actionTime: formatDate(log.action_time),
|
actionTime: formatDate(log.action_time),
|
||||||
|
auditTime: formatDate(log.action_time), // 审核时间,与actionTime相同
|
||||||
actionBy: log.action_by,
|
actionBy: log.action_by,
|
||||||
|
auditor: log.action_by, // 审核人,与actionBy相同
|
||||||
result,
|
result,
|
||||||
auditStatus: log.snapshot_audit_status,
|
auditStatus: log.snapshot_audit_status,
|
||||||
auditComment: log.snapshot_audit_comment,
|
auditComment: log.snapshot_audit_comment,
|
||||||
|
reason: log.snapshot_audit_comment, // 审核原因,与auditComment相同
|
||||||
|
remarks: log.change_summary, // 备注信息,使用变更摘要
|
||||||
changeSummary: log.change_summary,
|
changeSummary: log.change_summary,
|
||||||
ipAddress: log.ip_address,
|
ipAddress: log.ip_address,
|
||||||
userAgent: log.user_agent,
|
userAgent: log.user_agent,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
'use client';
|
'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 { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
@@ -29,7 +29,27 @@ import SearchFormPagination, {
|
|||||||
type TableColumnConfig
|
type TableColumnConfig
|
||||||
} from '@/components/common/searchFormPagination';
|
} 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
|
// Utility functions
|
||||||
const getActionBadge = (action: string) => {
|
const getActionBadge = (action: string) => {
|
||||||
@@ -94,7 +114,7 @@ export default function AuditHistoryPage() {
|
|||||||
// 对话框状态管理
|
// 对话框状态管理
|
||||||
const [dialogs, setDialogs] = useState({
|
const [dialogs, setDialogs] = useState({
|
||||||
showViewDialog: false,
|
showViewDialog: false,
|
||||||
selectedRecord: null as AuditLogData | null
|
selectedRecord: null as AuditRecord | null
|
||||||
});
|
});
|
||||||
|
|
||||||
const dispatch = (action: any) => {
|
const dispatch = (action: any) => {
|
||||||
@@ -224,8 +244,8 @@ export default function AuditHistoryPage() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
// 简化的状态管理 - 只需要存储数据和加载状态
|
// 简化的状态管理 - 只需要存储数据和加载状态
|
||||||
const [records, setRecords] = useState<AuditLogData[]>([]);
|
const [records, setRecords] = useState<AuditRecord[]>([]);
|
||||||
const [pagination, setPagination] = useState({
|
const [pagination, setPagination] = useState<PaginationState>({
|
||||||
page: 1,
|
page: 1,
|
||||||
size: 10,
|
size: 10,
|
||||||
total: 0,
|
total: 0,
|
||||||
@@ -253,7 +273,7 @@ export default function AuditHistoryPage() {
|
|||||||
} = {}) => {
|
} = {}) => {
|
||||||
try {
|
try {
|
||||||
// 优先从URL读取参数
|
// 优先从URL读取参数
|
||||||
let urlParams = {};
|
let urlParams: UrlParams = {};
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
urlParams = {
|
urlParams = {
|
||||||
@@ -304,6 +324,12 @@ export default function AuditHistoryPage() {
|
|||||||
params.search_keyword = currentFilters.search;
|
params.search_keyword = currentFilters.search;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加排序条件
|
||||||
|
if (currentSortBy) {
|
||||||
|
params.order_by = currentSortBy;
|
||||||
|
params.sort_order = currentSortOrder;
|
||||||
|
}
|
||||||
|
|
||||||
if (currentFilters.action && currentFilters.action !== 'all') {
|
if (currentFilters.action && currentFilters.action !== 'all') {
|
||||||
params.action = currentFilters.action;
|
params.action = currentFilters.action;
|
||||||
}
|
}
|
||||||
@@ -482,23 +508,21 @@ useEffect(() => {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 使用SearchFormPagination组件 */}
|
{/* 使用SearchFormPagination组件 */}
|
||||||
<SearchFormPagination
|
{React.createElement(SearchFormPagination as any, {
|
||||||
formTitle="审核历史记录"
|
formTitle: "审核历史记录",
|
||||||
searchFields={searchFields}
|
searchFields,
|
||||||
columns={columns}
|
columns,
|
||||||
data={records}
|
data: records,
|
||||||
loading={loading}
|
loading,
|
||||||
error={error}
|
error,
|
||||||
pagination={pagination}
|
pagination: pagination as any,
|
||||||
onPageChange={handlePageChange}
|
onPageChange: handlePageChange,
|
||||||
onSizeChange={handleSizeChange}
|
onSizeChange: handleSizeChange,
|
||||||
onSearch={handleSearch}
|
onSearch: handleSearch,
|
||||||
onSort={handleSort}
|
emptyIcon: <FileText className="w-12 h-12 mx-auto mb-4 opacity-20" />,
|
||||||
emptyIcon={<FileText className="w-12 h-12 mx-auto mb-4 opacity-20" />}
|
emptyText: "暂无审核记录",
|
||||||
emptyText="暂无审核记录"
|
sizeOptions: [10, 20, 50, 100]
|
||||||
sizeOptions={[10, 20, 50, 100]}
|
})}
|
||||||
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* View Audit Record Details Dialog */}
|
{/* View Audit Record Details Dialog */}
|
||||||
<Dialog open={dialogs.showViewDialog} onOpenChange={(open) => dispatch({ type: 'TOGGLE_VIEW_DIALOG', payload: open })}>
|
<Dialog open={dialogs.showViewDialog} onOpenChange={(open) => dispatch({ type: 'TOGGLE_VIEW_DIALOG', payload: open })}>
|
||||||
|
|||||||
@@ -516,12 +516,9 @@ export default function EnterpriseAuditPage() {
|
|||||||
loading={state.loading}
|
loading={state.loading}
|
||||||
error={state.error}
|
error={state.error}
|
||||||
pagination={state.pagination}
|
pagination={state.pagination}
|
||||||
sortBy={state.sortBy}
|
|
||||||
sortOrder={state.sortOrder}
|
|
||||||
onPageChange={handlePageChange}
|
onPageChange={handlePageChange}
|
||||||
onSizeChange={handleSizeChange}
|
onSizeChange={handleSizeChange}
|
||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
onSort={handleSort}
|
|
||||||
emptyIcon={<Building2 className="w-12 h-12" />}
|
emptyIcon={<Building2 className="w-12 h-12" />}
|
||||||
emptyText="暂无企业审核数据"
|
emptyText="暂无企业审核数据"
|
||||||
showSizeSelector={true}
|
showSizeSelector={true}
|
||||||
|
|||||||
@@ -602,7 +602,6 @@ export default function EnterpriseManagement() {
|
|||||||
onPageChange={handlePageChange}
|
onPageChange={handlePageChange}
|
||||||
onSizeChange={handleSizeChange}
|
onSizeChange={handleSizeChange}
|
||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
onSort={handleSort}
|
|
||||||
emptyIcon={<Building2 className="w-12 h-12 mx-auto mb-4 opacity-20" />}
|
emptyIcon={<Building2 className="w-12 h-12 mx-auto mb-4 opacity-20" />}
|
||||||
emptyText="暂无企业数据"
|
emptyText="暂无企业数据"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -622,12 +622,9 @@ export default function TenantUserManagementPage() {
|
|||||||
loading={state.loading}
|
loading={state.loading}
|
||||||
error={state.error}
|
error={state.error}
|
||||||
pagination={state.pagination}
|
pagination={state.pagination}
|
||||||
sortBy={state.sortBy}
|
|
||||||
sortOrder={state.sortOrder}
|
|
||||||
onPageChange={handlePageChange}
|
onPageChange={handlePageChange}
|
||||||
onSizeChange={handleSizeChange}
|
onSizeChange={handleSizeChange}
|
||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
onSort={handleSort}
|
|
||||||
emptyText="暂无用户数据"
|
emptyText="暂无用户数据"
|
||||||
sizeOptions={[10, 20, 50, 100]}
|
sizeOptions={[10, 20, 50, 100]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import {
|
|||||||
Download,
|
Download,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Eye,
|
Eye,
|
||||||
Settings,
|
|
||||||
Target,
|
Target,
|
||||||
Award,
|
Award,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
@@ -43,20 +42,19 @@ import {
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import {
|
import {
|
||||||
SoilQualityService,
|
SoilQualityService,
|
||||||
SoilQualityEvaluation,
|
|
||||||
SoilIndicator,
|
|
||||||
SoilRecommendation,
|
|
||||||
SoilQualityHistory,
|
|
||||||
SoilAnalysisForm,
|
|
||||||
formatSoilScore,
|
formatSoilScore,
|
||||||
getSoilGradeColor,
|
getSoilGradeColor,
|
||||||
getIndicatorStatusColor,
|
getIndicatorStatusColor,
|
||||||
formatDate
|
formatDate
|
||||||
} from './soilQualityService';
|
} from './soilQualityService';
|
||||||
import {
|
import {
|
||||||
SOIL_TYPES,
|
|
||||||
SOIL_TEXTURES,
|
SOIL_TEXTURES,
|
||||||
DRAINAGE_LEVELS
|
DRAINAGE_LEVELS,
|
||||||
|
SoilQualityEvaluation,
|
||||||
|
SoilIndicator,
|
||||||
|
SoilRecommendation,
|
||||||
|
SoilQualityHistory,
|
||||||
|
SoilAnalysisForm,
|
||||||
} from './soilTypes';
|
} from './soilTypes';
|
||||||
|
|
||||||
export function SoilQualityAnalysis() {
|
export function SoilQualityAnalysis() {
|
||||||
|
|||||||
@@ -1,516 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { Card } from '@/components/ui/card';
|
|
||||||
import { Badge } from '@/components/ui/badge';
|
|
||||||
import { Leaf, AlertTriangle, ThermometerSun, Cloud, Sun } from 'lucide-react';
|
|
||||||
import { SuitabilityResult } from './cropRecommendReducer';
|
|
||||||
|
|
||||||
type RangeRequirement = { optimal: [number, number]; acceptable: [number, number]; };
|
|
||||||
type SoilFactorKey = 'ph' | 'organicMatter' | 'soilDepth' | 'nitrogen' | 'phosphorus' | 'potassium' | 'drainage';
|
|
||||||
type SoilRequirementMap = Record<SoilFactorKey, RangeRequirement>;
|
|
||||||
|
|
||||||
type ClimateRequirement = {
|
|
||||||
temperature: RangeRequirement;
|
|
||||||
rainfall: RangeRequirement;
|
|
||||||
sunlight: RangeRequirement;
|
|
||||||
};
|
|
||||||
|
|
||||||
type YieldRange = {
|
|
||||||
high: [number, number];
|
|
||||||
medium: [number, number];
|
|
||||||
low: [number, number];
|
|
||||||
};
|
|
||||||
|
|
||||||
type RiskSeverity = 'low' | 'medium' | 'high';
|
|
||||||
|
|
||||||
interface CropRiskFactor {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
condition: string;
|
|
||||||
severity: RiskSeverity;
|
|
||||||
suggestion: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CropKnowledgeEntry {
|
|
||||||
id: string;
|
|
||||||
cropName: string;
|
|
||||||
category: string;
|
|
||||||
description: string;
|
|
||||||
growthCycle: { days: number; seasons: string[] };
|
|
||||||
soilRequirements: SoilRequirementMap;
|
|
||||||
climateRequirements: ClimateRequirement;
|
|
||||||
expectedYield: YieldRange;
|
|
||||||
riskFactors: CropRiskFactor[];
|
|
||||||
}
|
|
||||||
|
|
||||||
type MatchStatus = '??' | '??' | '??';
|
|
||||||
|
|
||||||
interface MatchDetail {
|
|
||||||
factor: string;
|
|
||||||
value: number;
|
|
||||||
score: number;
|
|
||||||
status: MatchStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RecommendationResult {
|
|
||||||
crop: CropKnowledgeEntry;
|
|
||||||
matchScore: number;
|
|
||||||
suitabilityLevel: '????' | '??' | '????' | '??';
|
|
||||||
matchDetails: MatchDetail[];
|
|
||||||
applicableRisks: CropRiskFactor[];
|
|
||||||
expectedYield: [number, number];
|
|
||||||
}
|
|
||||||
|
|
||||||
type FieldFactors = Record<SoilFactorKey | 'temperature' | 'rainfall', number>;
|
|
||||||
|
|
||||||
// 模拟作物知识库数据
|
|
||||||
const cropKnowledgeBase: CropKnowledgeEntry[] = [
|
|
||||||
{
|
|
||||||
id: 'wheat',
|
|
||||||
cropName: '小麦',
|
|
||||||
category: '粮食作物',
|
|
||||||
description: '适应性强的主粮作物,对土壤要求较宽泛,耐寒性好,适合北方地区种植。',
|
|
||||||
growthCycle: {
|
|
||||||
days: 220,
|
|
||||||
seasons: ['春季', '秋季']
|
|
||||||
},
|
|
||||||
soilRequirements: {
|
|
||||||
ph: { optimal: [6.5, 7.5], acceptable: [6.0, 8.0] },
|
|
||||||
organicMatter: { optimal: [25, 35], acceptable: [20, 40] },
|
|
||||||
soilDepth: { optimal: [60, 100], acceptable: [40, 120] },
|
|
||||||
nitrogen: { optimal: [1.5, 2.5], acceptable: [1.0, 3.0] },
|
|
||||||
phosphorus: { optimal: [1.0, 2.0], acceptable: [0.6, 2.5] },
|
|
||||||
potassium: { optimal: [15, 25], acceptable: [10, 30] },
|
|
||||||
drainage: { optimal: [3, 5], acceptable: [2, 5] }
|
|
||||||
},
|
|
||||||
climateRequirements: {
|
|
||||||
temperature: { optimal: [15, 22], acceptable: [10, 25] },
|
|
||||||
rainfall: { optimal: [400, 600], acceptable: [300, 800] },
|
|
||||||
sunlight: { optimal: [6, 8], acceptable: [5, 10] }
|
|
||||||
},
|
|
||||||
expectedYield: {
|
|
||||||
high: [400, 500],
|
|
||||||
medium: [300, 400],
|
|
||||||
low: [200, 300]
|
|
||||||
},
|
|
||||||
riskFactors: [
|
|
||||||
{
|
|
||||||
id: 'wheat-rust',
|
|
||||||
name: '锈病风险',
|
|
||||||
condition: '湿度过高、温度适宜',
|
|
||||||
severity: 'medium',
|
|
||||||
suggestion: '选择抗病品种,合理密植,及时防治'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'wheat-drought',
|
|
||||||
name: '干旱风险',
|
|
||||||
condition: '降雨量不足400mm',
|
|
||||||
severity: 'high',
|
|
||||||
suggestion: '加强灌溉设施建设,选择抗旱品种'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'corn',
|
|
||||||
cropName: '玉米',
|
|
||||||
category: '粮食作物',
|
|
||||||
description: '高产作物,对温度要求较高,需水量大,适合水热条件良好的地区。',
|
|
||||||
growthCycle: {
|
|
||||||
days: 120,
|
|
||||||
seasons: ['春季', '夏季']
|
|
||||||
},
|
|
||||||
soilRequirements: {
|
|
||||||
ph: { optimal: [6.0, 7.0], acceptable: [5.5, 7.5] },
|
|
||||||
organicMatter: { optimal: [30, 40], acceptable: [25, 45] },
|
|
||||||
soilDepth: { optimal: [80, 120], acceptable: [60, 150] },
|
|
||||||
nitrogen: { optimal: [2.0, 3.0], acceptable: [1.5, 3.5] },
|
|
||||||
phosphorus: { optimal: [1.2, 2.5], acceptable: [0.8, 3.0] },
|
|
||||||
potassium: { optimal: [20, 30], acceptable: [15, 35] },
|
|
||||||
drainage: { optimal: [3, 5], acceptable: [2, 5] }
|
|
||||||
},
|
|
||||||
climateRequirements: {
|
|
||||||
temperature: { optimal: [20, 28], acceptable: [15, 32] },
|
|
||||||
rainfall: { optimal: [500, 800], acceptable: [400, 1000] },
|
|
||||||
sunlight: { optimal: [7, 9], acceptable: [6, 10] }
|
|
||||||
},
|
|
||||||
expectedYield: {
|
|
||||||
high: [600, 800],
|
|
||||||
medium: [400, 600],
|
|
||||||
low: [250, 400]
|
|
||||||
},
|
|
||||||
riskFactors: [
|
|
||||||
{
|
|
||||||
id: 'corn-borer',
|
|
||||||
name: '玉米螟',
|
|
||||||
condition: '温度适宜、湿度适中',
|
|
||||||
severity: 'medium',
|
|
||||||
suggestion: '生物防治与化学防治结合,适时播种'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'corn-drought',
|
|
||||||
name: '花期干旱',
|
|
||||||
condition: '开花期降雨不足',
|
|
||||||
severity: 'high',
|
|
||||||
suggestion: '保证花期灌溉,选择耐旱品种'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'soybean',
|
|
||||||
cropName: '大豆',
|
|
||||||
category: '经济作物',
|
|
||||||
description: '豆科作物,具有固氮能力,对土壤肥力要求较低,适合轮作种植。',
|
|
||||||
growthCycle: {
|
|
||||||
days: 100,
|
|
||||||
seasons: ['春季', '夏季']
|
|
||||||
},
|
|
||||||
soilRequirements: {
|
|
||||||
ph: { optimal: [6.0, 7.0], acceptable: [5.5, 7.5] },
|
|
||||||
organicMatter: { optimal: [25, 35], acceptable: [20, 40] },
|
|
||||||
soilDepth: { optimal: [50, 80], acceptable: [40, 100] },
|
|
||||||
nitrogen: { optimal: [1.0, 2.0], acceptable: [0.5, 2.5] },
|
|
||||||
phosphorus: { optimal: [0.8, 1.8], acceptable: [0.5, 2.5] },
|
|
||||||
potassium: { optimal: [15, 25], acceptable: [10, 30] },
|
|
||||||
drainage: { optimal: [3, 5], acceptable: [2, 5] }
|
|
||||||
},
|
|
||||||
climateRequirements: {
|
|
||||||
temperature: { optimal: [18, 25], acceptable: [15, 28] },
|
|
||||||
rainfall: { optimal: [450, 700], acceptable: [350, 900] },
|
|
||||||
sunlight: { optimal: [6, 8], acceptable: [5, 9] }
|
|
||||||
},
|
|
||||||
expectedYield: {
|
|
||||||
high: [250, 350],
|
|
||||||
medium: [180, 250],
|
|
||||||
low: [120, 180]
|
|
||||||
},
|
|
||||||
riskFactors: [
|
|
||||||
{
|
|
||||||
id: 'soybean-disease',
|
|
||||||
name: '病害风险',
|
|
||||||
condition: '高温高湿环境',
|
|
||||||
severity: 'medium',
|
|
||||||
suggestion: '选择抗病品种,合理轮作,加强田间管理'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
interface CropRecommendationsProps {
|
|
||||||
currentResult: SuitabilityResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CropRecommendations({ currentResult }: CropRecommendationsProps) {
|
|
||||||
// 匹配作物推荐
|
|
||||||
const matchCropsForField = (fieldFactors: FieldFactors): RecommendationResult[] => {
|
|
||||||
const factorLabelMap: Record<SoilFactorKey, string> = {
|
|
||||||
ph: 'pH?',
|
|
||||||
organicMatter: '??',
|
|
||||||
soilDepth: '????',
|
|
||||||
nitrogen: '??',
|
|
||||||
phosphorus: '??',
|
|
||||||
potassium: '??',
|
|
||||||
drainage: '??'
|
|
||||||
};
|
|
||||||
|
|
||||||
return cropKnowledgeBase.map((crop) => {
|
|
||||||
let totalScore = 0;
|
|
||||||
let factorCount = 0;
|
|
||||||
const matchDetails: MatchDetail[] = [];
|
|
||||||
|
|
||||||
(Object.entries(crop.soilRequirements) as Array<[SoilFactorKey, RangeRequirement]>).forEach(([factor, requirements]) => {
|
|
||||||
const value = fieldFactors[factor];
|
|
||||||
if (typeof value !== 'number') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { optimal, acceptable } = requirements;
|
|
||||||
let score = 0;
|
|
||||||
let status: MatchStatus = '??';
|
|
||||||
|
|
||||||
if (value >= optimal[0] && value <= optimal[1]) {
|
|
||||||
score = 100;
|
|
||||||
status = '??';
|
|
||||||
} else if (value >= acceptable[0] && value <= acceptable[1]) {
|
|
||||||
const deviation = Math.min(
|
|
||||||
Math.abs(value - optimal[0]),
|
|
||||||
Math.abs(value - optimal[1])
|
|
||||||
);
|
|
||||||
const range = optimal[1] - optimal[0];
|
|
||||||
score = Math.max(60, 100 - (deviation / range) * 40);
|
|
||||||
status = '??';
|
|
||||||
} else {
|
|
||||||
score = Math.max(
|
|
||||||
0,
|
|
||||||
60 -
|
|
||||||
Math.min(
|
|
||||||
Math.abs(value - acceptable[0]),
|
|
||||||
Math.abs(value - acceptable[1])
|
|
||||||
) *
|
|
||||||
2
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
totalScore += score;
|
|
||||||
factorCount += 1;
|
|
||||||
|
|
||||||
matchDetails.push({
|
|
||||||
factor: factorLabelMap[factor],
|
|
||||||
value,
|
|
||||||
score,
|
|
||||||
status
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (typeof fieldFactors.temperature === 'number') {
|
|
||||||
const tempScore =
|
|
||||||
fieldFactors.temperature >= 18 && fieldFactors.temperature <= 25 ? 90 : 70;
|
|
||||||
totalScore += tempScore;
|
|
||||||
factorCount += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof fieldFactors.rainfall === 'number') {
|
|
||||||
const rainScore =
|
|
||||||
fieldFactors.rainfall >= 500 && fieldFactors.rainfall <= 800 ? 90 : 70;
|
|
||||||
totalScore += rainScore;
|
|
||||||
factorCount += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const matchScore = factorCount > 0 ? Math.round(totalScore / factorCount) : 0;
|
|
||||||
|
|
||||||
let suitabilityLevel: RecommendationResult['suitabilityLevel'] = '??';
|
|
||||||
if (matchScore >= 85) suitabilityLevel = '????';
|
|
||||||
else if (matchScore >= 70) suitabilityLevel = '??';
|
|
||||||
else if (matchScore >= 50) suitabilityLevel = '????';
|
|
||||||
|
|
||||||
let expectedYield: [number, number] = crop.expectedYield.low;
|
|
||||||
if (suitabilityLevel === '????') expectedYield = crop.expectedYield.high;
|
|
||||||
else if (suitabilityLevel === '??') expectedYield = crop.expectedYield.medium;
|
|
||||||
|
|
||||||
const applicableRisks = crop.riskFactors.filter((risk) => {
|
|
||||||
if (risk.id.includes('drought') && fieldFactors.rainfall < 400) return true;
|
|
||||||
if (
|
|
||||||
risk.id.includes('rust') &&
|
|
||||||
fieldFactors.temperature >= 15 &&
|
|
||||||
fieldFactors.temperature <= 22
|
|
||||||
)
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
crop,
|
|
||||||
matchScore,
|
|
||||||
suitabilityLevel,
|
|
||||||
matchDetails,
|
|
||||||
applicableRisks,
|
|
||||||
expectedYield
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getFactorValue = (factorId: string) =>
|
|
||||||
currentResult.factors.find((factor) => factor.id === factorId)?.value ?? 0;
|
|
||||||
|
|
||||||
const fieldFactors: FieldFactors = {
|
|
||||||
ph: getFactorValue('ph'),
|
|
||||||
organicMatter: getFactorValue('organic'),
|
|
||||||
soilDepth: getFactorValue('depth'),
|
|
||||||
nitrogen: getFactorValue('nitrogen'),
|
|
||||||
phosphorus: getFactorValue('phosphorus'),
|
|
||||||
potassium: getFactorValue('potassium'),
|
|
||||||
drainage: getFactorValue('drainage'),
|
|
||||||
temperature: 22, // ??????
|
|
||||||
rainfall: 800 // ???????
|
|
||||||
};
|
|
||||||
|
|
||||||
// // 模拟年均降雨量
|
|
||||||
};
|
|
||||||
|
|
||||||
// 匹配推荐作物
|
|
||||||
const recommendations = matchCropsForField(fieldFactors);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card className="p-6">
|
|
||||||
<div className="flex items-center justify-between mb-4">
|
|
||||||
<h3 className="flex items-center gap-2">
|
|
||||||
<Leaf className="w-5 h-5 text-green-600" />
|
|
||||||
智能作物推荐清单
|
|
||||||
</h3>
|
|
||||||
<Badge variant="outline" className="text-xs">
|
|
||||||
基于{cropKnowledgeBase.length}种作物知识库匹配
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
{recommendations.map((recommendation, index) => {
|
|
||||||
const { crop, matchScore, suitabilityLevel, matchDetails, applicableRisks, expectedYield } = recommendation;
|
|
||||||
|
|
||||||
// 根据适宜性等级设置颜色
|
|
||||||
const levelColor =
|
|
||||||
suitabilityLevel === '高度推荐' ? { bg: 'bg-green-500', border: '#22c55e', text: 'text-green-600 dark:text-green-400' } :
|
|
||||||
suitabilityLevel === '推荐' ? { bg: 'bg-blue-500', border: '#3b82f6', text: 'text-blue-600 dark:text-blue-400' } :
|
|
||||||
suitabilityLevel === '谨慎种植' ? { bg: 'bg-yellow-500', border: '#eab308', text: 'text-yellow-600 dark:text-yellow-400' } :
|
|
||||||
{ bg: 'bg-gray-500', border: '#6b7280', text: 'text-gray-600 dark:text-gray-400' };
|
|
||||||
|
|
||||||
// 只显示高度推荐和推荐的作物
|
|
||||||
if (suitabilityLevel === '不推荐') return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card key={index} className="p-5 border-l-4 hover:shadow-lg transition-shadow" style={{ borderLeftColor: levelColor.border }}>
|
|
||||||
{/* 标题栏 */}
|
|
||||||
<div className="flex items-start justify-between mb-4">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className={`p-2 rounded-lg bg-gradient-to-br from-green-50 to-green-100 dark:from-green-950 dark:to-green-900`}>
|
|
||||||
<Leaf className="w-6 h-6 text-green-600 dark:text-green-400" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<h4 className="mb-1">{crop.cropName}</h4>
|
|
||||||
<Badge variant="outline" className="text-xs">{crop.category}</Badge>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Badge className={`${levelColor.bg} text-white`}>
|
|
||||||
{suitabilityLevel}
|
|
||||||
</Badge>
|
|
||||||
<span className="text-xs text-muted-foreground">匹配度: {matchScore}分</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-right">
|
|
||||||
<p className="text-xs text-muted-foreground mb-1">预期产量区间</p>
|
|
||||||
<p className={`text-lg font-medium ${levelColor.text}`}>
|
|
||||||
{expectedYield[0]}-{expectedYield[1]} kg/亩
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-muted-foreground mt-1">生长周期: {crop.growthCycle.days}天</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 作物描述 */}
|
|
||||||
<p className="text-sm text-muted-foreground mb-3 p-2 bg-gray-50 dark:bg-gray-900 rounded">
|
|
||||||
{crop.description}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{/* 土壤因子匹配详情 */}
|
|
||||||
<div className="mb-3">
|
|
||||||
<p className="text-xs text-muted-foreground mb-2">土壤因子匹配情况:</p>
|
|
||||||
<div className="grid grid-cols-7 gap-2">
|
|
||||||
{matchDetails.map((detail, i) => (
|
|
||||||
<div key={i} className="p-2 bg-gray-50 dark:bg-gray-900 rounded text-center">
|
|
||||||
<p className="text-xs text-muted-foreground mb-1">{detail.factor}</p>
|
|
||||||
<p className="text-xs font-medium mb-1">{detail.value.toFixed(1)}</p>
|
|
||||||
{detail.status === '最佳' ? (
|
|
||||||
<Badge className="bg-green-500 text-white" style={{ fontSize: '9px', padding: '1px 4px' }}>
|
|
||||||
最佳
|
|
||||||
</Badge>
|
|
||||||
) : detail.status === '可接受' ? (
|
|
||||||
<Badge className="bg-blue-500 text-white" style={{ fontSize: '9px', padding: '1px 4px' }}>
|
|
||||||
可接受
|
|
||||||
</Badge>
|
|
||||||
) : (
|
|
||||||
<Badge variant="outline" className="text-red-500" style={{ fontSize: '9px', padding: '1px 4px' }}>
|
|
||||||
偏离
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 气候要求 */}
|
|
||||||
<div className="grid grid-cols-3 gap-3 mb-3">
|
|
||||||
<div className="p-2 bg-blue-50 dark:bg-blue-950 rounded-lg">
|
|
||||||
<p className="text-xs text-blue-600 dark:text-blue-400 mb-1 flex items-center gap-1">
|
|
||||||
<ThermometerSun className="w-3 h-3" />
|
|
||||||
温度要求
|
|
||||||
</p>
|
|
||||||
<p className="text-sm text-blue-900 dark:text-blue-100">
|
|
||||||
{crop.climateRequirements.temperature.optimal[0]}-{crop.climateRequirements.temperature.optimal[1]}°C
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="p-2 bg-cyan-50 dark:bg-cyan-950 rounded-lg">
|
|
||||||
<p className="text-xs text-cyan-600 dark:text-cyan-400 mb-1 flex items-center gap-1">
|
|
||||||
<Cloud className="w-3 h-3" />
|
|
||||||
降雨要求
|
|
||||||
</p>
|
|
||||||
<p className="text-sm text-cyan-900 dark:text-cyan-100">
|
|
||||||
{crop.climateRequirements.rainfall.optimal[0]}-{crop.climateRequirements.rainfall.optimal[1]}mm/年
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="p-2 bg-amber-50 dark:bg-amber-950 rounded-lg">
|
|
||||||
<p className="text-xs text-amber-600 dark:text-amber-400 mb-1 flex items-center gap-1">
|
|
||||||
<Sun className="w-3 h-3" />
|
|
||||||
光照要求
|
|
||||||
</p>
|
|
||||||
<p className="text-sm text-amber-900 dark:text-amber-100">
|
|
||||||
{crop.climateRequirements.sunlight.optimal[0]}-{crop.climateRequirements.sunlight.optimal[1]}小时/天
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 风险提示 */}
|
|
||||||
{applicableRisks.length > 0 && (
|
|
||||||
<div className={`p-3 rounded-lg border ${
|
|
||||||
applicableRisks.some(r => r.severity === 'high')
|
|
||||||
? 'bg-red-50 dark:bg-red-950 border-red-200 dark:border-red-800'
|
|
||||||
: 'bg-orange-50 dark:bg-orange-950 border-orange-200 dark:border-orange-800'
|
|
||||||
}`}>
|
|
||||||
<p className="text-xs font-medium mb-2 flex items-center gap-1">
|
|
||||||
<AlertTriangle className={`w-4 h-4 ${
|
|
||||||
applicableRisks.some(r => r.severity === 'high')
|
|
||||||
? 'text-red-600 dark:text-red-400'
|
|
||||||
: 'text-orange-600 dark:text-orange-400'
|
|
||||||
}`} />
|
|
||||||
<span className={applicableRisks.some(r => r.severity === 'high') ? 'text-red-900 dark:text-red-100' : 'text-orange-900 dark:text-orange-100'}>
|
|
||||||
风险提示与应对建议
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<div className="space-y-2">
|
|
||||||
{applicableRisks.map((risk, i) => (
|
|
||||||
<div key={i} className="text-xs">
|
|
||||||
<div className="flex items-start gap-2">
|
|
||||||
<Badge
|
|
||||||
className={
|
|
||||||
risk.severity === 'high' ? 'bg-red-500 text-white' :
|
|
||||||
risk.severity === 'medium' ? 'bg-orange-500 text-white' :
|
|
||||||
'bg-yellow-500 text-white'
|
|
||||||
}
|
|
||||||
style={{ fontSize: '9px', padding: '2px 6px', marginTop: '2px' }}
|
|
||||||
>
|
|
||||||
{risk.severity === 'high' ? '高风险' : risk.severity === 'medium' ? '中风险' : '低风险'}
|
|
||||||
</Badge>
|
|
||||||
<div className="flex-1">
|
|
||||||
<p className={`font-medium mb-0.5 ${
|
|
||||||
applicableRisks.some(r => r.severity === 'high')
|
|
||||||
? 'text-red-800 dark:text-red-200'
|
|
||||||
: 'text-orange-800 dark:text-orange-200'
|
|
||||||
}`}>
|
|
||||||
{risk.name}
|
|
||||||
</p>
|
|
||||||
<p className="text-muted-foreground mb-1">触发条件: {risk.condition}</p>
|
|
||||||
<p className={applicableRisks.some(r => r.severity === 'high') ? 'text-red-700 dark:text-red-300' : 'text-orange-700 dark:text-orange-300'}>
|
|
||||||
💡 {risk.suggestion}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 适宜季节 */}
|
|
||||||
<div className="mt-3 flex items-center gap-2 text-xs text-muted-foreground">
|
|
||||||
<span>适宜种植季节:</span>
|
|
||||||
{crop.growthCycle.seasons.map((season, i) => (
|
|
||||||
<Badge key={i} variant="outline" className="text-xs">
|
|
||||||
{season}
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
initialState
|
initialState
|
||||||
} from './components/cropRecommendReducer';
|
} from './components/cropRecommendReducer';
|
||||||
import { FieldEnvironmentOverview } from './components/FieldEnvironmentOverview';
|
import { FieldEnvironmentOverview } from './components/FieldEnvironmentOverview';
|
||||||
import { CropRecommendations } from './components/CropRecommendations';
|
|
||||||
import { KnowledgeBaseDialog } from './components/KnowledgeBaseDialog';
|
import { KnowledgeBaseDialog } from './components/KnowledgeBaseDialog';
|
||||||
|
|
||||||
export default function CropPage() {
|
export default function CropPage() {
|
||||||
@@ -115,8 +114,6 @@ export default function CropPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 智能作物推荐 */}
|
|
||||||
<CropRecommendations currentResult={currentResult} />
|
|
||||||
|
|
||||||
{/* 知识库对话框 */}
|
{/* 知识库对话框 */}
|
||||||
<KnowledgeBaseDialog
|
<KnowledgeBaseDialog
|
||||||
|
|||||||
@@ -117,18 +117,8 @@ export function LoginForm({ onRegisterClick }: LoginFormProps) {
|
|||||||
if (response.data) {
|
if (response.data) {
|
||||||
// 登录成功,提取用户信息
|
// 登录成功,提取用户信息
|
||||||
const userData = {
|
const userData = {
|
||||||
id: response.data.user_id || '1',
|
|
||||||
username: state.passwordForm.username,
|
|
||||||
realName: response.data.real_name || state.passwordForm.username,
|
|
||||||
phone: response.data.phone || '',
|
|
||||||
email: response.data.email || '',
|
|
||||||
role: response.data.role || 'user',
|
|
||||||
permissions: response.data.permissions || [],
|
|
||||||
enterpriseId: response.data.enterprise_id || '',
|
|
||||||
enterpriseName: response.data.enterprise_name || '',
|
|
||||||
createdAt: response.data.created_at || new Date().toISOString(),
|
|
||||||
// 重要:存储token到用户对象中
|
// 重要:存储token到用户对象中
|
||||||
token: response.data.access_token || response.data.token || null,
|
token: response.data.access_token || null,
|
||||||
refreshToken:response.data.refresh_token || ''
|
refreshToken:response.data.refresh_token || ''
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -188,26 +178,6 @@ export function LoginForm({ onRegisterClick }: LoginFormProps) {
|
|||||||
try {
|
try {
|
||||||
// 模拟手机号登录
|
// 模拟手机号登录
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
if (state.phoneForm.code === '123456') {
|
|
||||||
login({
|
|
||||||
id: '2',
|
|
||||||
username: 'user_' + state.phoneForm.phone.slice(-4),
|
|
||||||
realName: '用户',
|
|
||||||
phone: state.phoneForm.phone,
|
|
||||||
email: '',
|
|
||||||
role: 'user',
|
|
||||||
permissions: [],
|
|
||||||
enterpriseId: '',
|
|
||||||
enterpriseName: '',
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
toast.success('登录成功!');
|
|
||||||
window.location.href = '/';
|
|
||||||
} else {
|
|
||||||
dispatch({ type: 'SET_ERROR', payload: '验证码错误' });
|
|
||||||
toast.error('验证码错误');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('???????:', error);
|
console.error('???????:', error);
|
||||||
dispatch({ type: 'SET_ERROR', payload: '??????????' });
|
dispatch({ type: 'SET_ERROR', payload: '??????????' });
|
||||||
|
|||||||
@@ -225,23 +225,6 @@ export default function RegisterPage() {
|
|||||||
if (form.code === '123456') {
|
if (form.code === '123456') {
|
||||||
setSuccess('注册成功!正在为您自动登录...');
|
setSuccess('注册成功!正在为您自动登录...');
|
||||||
toast.success('注册成功!');
|
toast.success('注册成功!');
|
||||||
|
|
||||||
// 自动登录
|
|
||||||
setTimeout(() => {
|
|
||||||
login({
|
|
||||||
id: '3',
|
|
||||||
username: form.username,
|
|
||||||
realName: form.realName,
|
|
||||||
email: form.email,
|
|
||||||
phone: form.phone,
|
|
||||||
role: 'user',
|
|
||||||
permissions: [],
|
|
||||||
enterpriseId: form.enterpriseId,
|
|
||||||
enterpriseName: enterprises.find(e => e.id === form.enterpriseId)?.name || '',
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
});
|
|
||||||
handleNavigateToHome();
|
|
||||||
}, 1500);
|
|
||||||
} else {
|
} else {
|
||||||
setError('短信验证码错误');
|
setError('短信验证码错误');
|
||||||
toast.error('短信验证码错误');
|
toast.error('短信验证码错误');
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
import React, { createContext, useContext, useState, ReactNode, useRef } from 'react';
|
import React, { createContext, useContext, useState, ReactNode, useRef } from 'react';
|
||||||
import { getCurrentUserInfoApiV1AuthMeGet, refreshTokenApiV1AuthRefreshPost, listAdminSettingsApiV1AdminSettingsGet } from '@/lib/api/sdk.gen';
|
import { getCurrentUserInfoApiV1AuthMeGet, refreshTokenApiV1AuthRefreshPost, listAdminSettingsApiV1AdminSettingsGet } from '@/lib/api/sdk.gen';
|
||||||
import { setAuthUser, getAuthUser, setSettings } from '@/stores/modules/auth';
|
import { setAuthUser, getAuthUser, setSettings } from '@/stores/modules/auth';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import {AuthUser,SettingsResponse} from "@/stores/modules/auth"
|
||||||
|
import { safeLocalStorage } from '@/utils/storage';
|
||||||
// Cookie 操作工具
|
// Cookie 操作工具
|
||||||
const setTokenCookie = (token: string) => {
|
const setTokenCookie = (token: string) => {
|
||||||
if (typeof document !== 'undefined') {
|
if (typeof document !== 'undefined') {
|
||||||
@@ -22,16 +24,22 @@ const removeTokenCookie = () => {
|
|||||||
interface User {
|
interface User {
|
||||||
id: string;
|
id: string;
|
||||||
username: string;
|
username: string;
|
||||||
realName: string;
|
realName?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
phone?: string;
|
phone?: string;
|
||||||
enterpriseId?: string;
|
enterpriseId?: string;
|
||||||
token?: string;
|
token?: string;
|
||||||
|
refreshToken?:string;
|
||||||
|
is_superuser?: boolean;
|
||||||
|
}
|
||||||
|
interface Token{
|
||||||
|
token: string
|
||||||
|
refreshToken:string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AuthContextType {
|
interface AuthContextType {
|
||||||
user: User | null;
|
user: User | null;
|
||||||
login: (user: User) => void;
|
login: (user: Token) => void;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@@ -46,17 +54,18 @@ interface AuthProviderProps {
|
|||||||
|
|
||||||
export function AuthProvider({ children }: AuthProviderProps) {
|
export function AuthProvider({ children }: AuthProviderProps) {
|
||||||
const [user, setUser] = useState<User | null>(null);
|
const [user, setUser] = useState<User | null>(null);
|
||||||
|
const [, setToken] = useState<Token | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const refreshTimerRef = useRef<NodeJS.Timeout | null>(null);
|
const refreshTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const router = useRouter();
|
||||||
const login = (userData: User) => {
|
const login = (tokenData: Token) => {
|
||||||
setUser(userData);
|
setToken(tokenData);
|
||||||
// 存储到 localStorage
|
// 存储到 localStorage
|
||||||
localStorage.setItem('user', JSON.stringify(userData));
|
safeLocalStorage.setItem('user', JSON.stringify(tokenData));
|
||||||
|
|
||||||
// 同时设置 cookie(供中间件使用)
|
// 同时设置 cookie(供中间件使用)
|
||||||
if (userData.token) {
|
if (tokenData.token) {
|
||||||
setTokenCookie(userData.token);
|
setTokenCookie(tokenData.token);
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -67,7 +76,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
setUser(null);
|
setUser(null);
|
||||||
localStorage.removeItem('user');
|
safeLocalStorage.removeItem('user');
|
||||||
removeTokenCookie(); // 清除 cookie
|
removeTokenCookie(); // 清除 cookie
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
||||||
@@ -86,7 +95,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|||||||
// 刷新 token 的函数
|
// 刷新 token 的函数
|
||||||
const refreshAccessToken = async () => {
|
const refreshAccessToken = async () => {
|
||||||
try {
|
try {
|
||||||
const storedUser = localStorage.getItem('user');
|
const storedUser = safeLocalStorage.getItem('user');
|
||||||
if (!storedUser) {
|
if (!storedUser) {
|
||||||
console.warn('⚠️ 未找到用户信息,无法刷新 token');
|
console.warn('⚠️ 未找到用户信息,无法刷新 token');
|
||||||
return;
|
return;
|
||||||
@@ -127,7 +136,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 更新 localStorage
|
// 更新 localStorage
|
||||||
localStorage.setItem('user', JSON.stringify(updatedUserData));
|
safeLocalStorage.setItem('user', JSON.stringify(updatedUserData));
|
||||||
|
|
||||||
// 更新状态
|
// 更新状态
|
||||||
setUser(updatedUserData);
|
setUser(updatedUserData);
|
||||||
@@ -195,7 +204,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|||||||
// 验证当前用户信息
|
// 验证当前用户信息
|
||||||
const validateUser = async () => {
|
const validateUser = async () => {
|
||||||
try {
|
try {
|
||||||
const storedUser = localStorage.getItem('user');
|
const storedUser = safeLocalStorage.getItem('user');
|
||||||
if (!storedUser) {
|
if (!storedUser) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
@@ -229,18 +238,18 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|||||||
// 更新用户信息(可能包含最新的权限、角色等)
|
// 更新用户信息(可能包含最新的权限、角色等)
|
||||||
const updatedUserData = {
|
const updatedUserData = {
|
||||||
...userData,
|
...userData,
|
||||||
...userResponse.data, // 合并最新的用户信息
|
...userResponse.data as any, // 合并最新的用户信息
|
||||||
};
|
};
|
||||||
setUser(updatedUserData);
|
setUser(updatedUserData);
|
||||||
|
|
||||||
// 存储到 Zustand store
|
// 存储到 Zustand store
|
||||||
setAuthUser(userResponse.data);
|
setAuthUser(userResponse.data as AuthUser);
|
||||||
console.log('✅ 用户验证成功,最新用户信息:', userResponse.data);
|
console.log('✅ 用户验证成功,最新用户信息:', userResponse.data);
|
||||||
console.log('📦 从 Zustand store 取出的用户数据:', getAuthUser());
|
console.log('📦 从 Zustand store 取出的用户数据:', getAuthUser());
|
||||||
|
|
||||||
// 存储设置数据到 Zustand store
|
// 存储设置数据到 Zustand store
|
||||||
if (settingsResponse && settingsResponse.data) {
|
if (settingsResponse && settingsResponse.data) {
|
||||||
setSettings(settingsResponse.data);
|
setSettings(settingsResponse.data as SettingsResponse);
|
||||||
console.log('✅ 设置数据获取成功:', settingsResponse.data);
|
console.log('✅ 设置数据获取成功:', settingsResponse.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,13 +258,16 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|||||||
} else {
|
} else {
|
||||||
// Token无效,清除用户信息
|
// Token无效,清除用户信息
|
||||||
console.warn('⚠️ Token验证失败,清除用户信息');
|
console.warn('⚠️ Token验证失败,清除用户信息');
|
||||||
localStorage.removeItem('user');
|
safeLocalStorage.removeItem('user');
|
||||||
|
const currentPath = window.location.pathname;
|
||||||
|
const loginUrl = `/login${currentPath !== '/' ? `?redirect=${encodeURIComponent(currentPath)}` : ''}`;
|
||||||
|
router.push(loginUrl);
|
||||||
setUser(null);
|
setUser(null);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('❌ 用户验证失败:', error);
|
console.error('❌ 用户验证失败:', error);
|
||||||
// 验证失败时也清除用户信息,避免不一致状态
|
// 验证失败时也清除用户信息,避免不一致状态
|
||||||
localStorage.removeItem('user');
|
safeLocalStorage.removeItem('user');
|
||||||
setUser(null);
|
setUser(null);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -277,7 +289,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否有存储的用户信息和 token,并设置 cookie
|
// 检查是否有存储的用户信息和 token,并设置 cookie
|
||||||
const storedUser = localStorage.getItem('user');
|
const storedUser = safeLocalStorage.getItem('user');
|
||||||
if (storedUser) {
|
if (storedUser) {
|
||||||
try {
|
try {
|
||||||
const userData = JSON.parse(storedUser);
|
const userData = JSON.parse(storedUser);
|
||||||
@@ -287,7 +299,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('解析存储用户信息失败:', error);
|
console.error('解析存储用户信息失败:', error);
|
||||||
localStorage.removeItem('user');
|
safeLocalStorage.removeItem('user');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback,useRef } from 'react';
|
||||||
import { RefreshCw } from 'lucide-react';
|
import { RefreshCw } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
@@ -12,121 +12,66 @@ interface CaptchaInputProps {
|
|||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
onCaptchaChange?: (captchaData: CaptchaResponse | null) => void;
|
onCaptchaChange?: (captchaData: CaptchaResponse | null) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
instanceId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CaptchaInput({ value, onChange, onCaptchaChange, className = '' }: CaptchaInputProps) {
|
export function CaptchaInput({ value, onChange, onCaptchaChange, className = '', instanceId = 'default' }: CaptchaInputProps) {
|
||||||
const [captchaData, setCaptchaData] = useState<CaptchaResponse | null>(null);
|
const [captchaData, setCaptchaData] = useState<CaptchaResponse | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
const isInitialized = useRef(false);
|
||||||
|
|
||||||
const fetchCaptcha = useCallback(async () => {
|
|
||||||
|
// 初始化验证码
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isInitialized.current) {
|
||||||
|
isInitialized.current = true;
|
||||||
|
const initialFetch = async () => {
|
||||||
|
console.log(`[CaptchaInput-${instanceId}] 初始化获取验证码...`);
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError('');
|
setError('');
|
||||||
onChange(''); // 清空验证码输入
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await getCaptchaApiV1AuthCaptchaGet();
|
const response = await getCaptchaApiV1AuthCaptchaGet();
|
||||||
console.log('API验证码获取成功:', response);
|
console.log(`[CaptchaInput-${instanceId}] API验证码获取成功:`, response);
|
||||||
setCaptchaData(response.data);
|
setCaptchaData(response.data);
|
||||||
if (onCaptchaChange) {
|
if (onCaptchaChange) {
|
||||||
onCaptchaChange(response.data);
|
onCaptchaChange(response.data);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('验证码获取失败:', err);
|
console.error(`[CaptchaInput-${instanceId}] 验证码获取失败:`, err);
|
||||||
|
setError('获取验证码失败,请重试');
|
||||||
// 如果API失败,使用备用验证码
|
|
||||||
const fallbackCaptcha = generateFallbackCaptcha();
|
|
||||||
console.log('生成备用验证码:', fallbackCaptcha);
|
|
||||||
setCaptchaData(fallbackCaptcha);
|
|
||||||
if (onCaptchaChange) {
|
|
||||||
onCaptchaChange(fallbackCaptcha);
|
|
||||||
}
|
|
||||||
setError(''); // 清除错误状态,因为备用验证码已生成
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateFallbackCaptcha = (): CaptchaResponse => {
|
initialFetch();
|
||||||
// 备用验证码生成(使用Canvas)
|
|
||||||
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
|
|
||||||
let text = '';
|
|
||||||
for (let i = 0; i < 4; i++) {
|
|
||||||
text += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
||||||
}
|
}
|
||||||
|
}, [instanceId, onCaptchaChange]);
|
||||||
|
|
||||||
const canvas = document.createElement('canvas');
|
const fetchCaptcha = useCallback(async () => {
|
||||||
canvas.width = 120;
|
console.log(`[CaptchaInput-${instanceId}] 刷新验证码...`);
|
||||||
canvas.height = 40;
|
setLoading(true);
|
||||||
const ctx = canvas.getContext('2d');
|
setError('');
|
||||||
|
|
||||||
if (!ctx) {
|
try {
|
||||||
return {
|
const response = await getCaptchaApiV1AuthCaptchaGet();
|
||||||
captcha_id: 'fallback-' + Date.now(),
|
console.log(`[CaptchaInput-${instanceId}] API验证码获取成功:`, response);
|
||||||
image: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=='
|
setCaptchaData(response.data);
|
||||||
};
|
if (onCaptchaChange) {
|
||||||
|
onCaptchaChange(response.data);
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
// 背景
|
console.error(`[CaptchaInput-${instanceId}] 验证码获取失败:`, err);
|
||||||
ctx.fillStyle = '#f0f0f0';
|
setError('获取验证码失败,请重试');
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
// 干扰线
|
|
||||||
for (let i = 0; i < 3; i++) {
|
|
||||||
ctx.strokeStyle = `rgba(${Math.random() * 100}, ${Math.random() * 100}, ${Math.random() * 100}, 0.3)`;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(Math.random() * canvas.width, Math.random() * canvas.height);
|
|
||||||
ctx.lineTo(Math.random() * canvas.width, Math.random() * canvas.height);
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
}
|
||||||
|
}, [instanceId, onCaptchaChange]);
|
||||||
// 干扰点
|
|
||||||
for (let i = 0; i < 30; i++) {
|
|
||||||
ctx.fillStyle = `rgba(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255}, 0.3)`;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(
|
|
||||||
Math.random() * canvas.width,
|
|
||||||
Math.random() * canvas.height,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
2 * Math.PI
|
|
||||||
);
|
|
||||||
ctx.fill();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证码文字
|
|
||||||
ctx.font = 'bold 24px Arial';
|
|
||||||
ctx.textBaseline = 'middle';
|
|
||||||
|
|
||||||
for (let i = 0; i < text.length; i++) {
|
|
||||||
const char = text[i];
|
|
||||||
const x = 20 + i * 25;
|
|
||||||
const y = 20 + (Math.random() - 0.5) * 6;
|
|
||||||
const angle = (Math.random() - 0.5) * 0.4;
|
|
||||||
|
|
||||||
ctx.save();
|
|
||||||
ctx.translate(x, y);
|
|
||||||
ctx.rotate(angle);
|
|
||||||
|
|
||||||
// 随机颜色
|
|
||||||
const colors = ['#16a34a', '#2563eb', '#dc2626', '#ea580c', '#8b5cf6'];
|
|
||||||
ctx.fillStyle = colors[Math.floor(Math.random() * colors.length)];
|
|
||||||
|
|
||||||
ctx.fillText(char, 0, 0);
|
|
||||||
ctx.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
captcha_id: 'fallback-' + Date.now(),
|
|
||||||
image: canvas.toDataURL()
|
|
||||||
};
|
|
||||||
}, [onCaptchaChange, onChange]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchCaptcha();
|
|
||||||
}, [fetchCaptcha]);
|
|
||||||
|
|
||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
|
onChange(''); // 清空验证码输入
|
||||||
fetchCaptcha();
|
fetchCaptcha();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function ClientAuthInterceptor({ children }: ClientAuthInterceptorProps)
|
|||||||
const currentPath = window.location.pathname;
|
const currentPath = window.location.pathname;
|
||||||
|
|
||||||
// 如果已经在认证页面(包括 /login 开头的所有路径、/register 和根路径),不需要重定向
|
// 如果已经在认证页面(包括 /login 开头的所有路径、/register 和根路径),不需要重定向
|
||||||
if (currentPath.startsWith('/login') || currentPath === '/register' || currentPath === '/') {
|
if (currentPath.startsWith('/login') || currentPath === '/register') {
|
||||||
console.log(`📄 已在认证页面,跳过拦截: ${currentPath}`);
|
console.log(`📄 已在认证页面,跳过拦截: ${currentPath}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -494,11 +494,10 @@ export function SearchFormPagination<T = any>({
|
|||||||
// 更新内部状态
|
// 更新内部状态
|
||||||
setInternalPagination(newPagination);
|
setInternalPagination(newPagination);
|
||||||
|
|
||||||
// 通知父组件分页变化
|
|
||||||
onPageChange?.(page);
|
|
||||||
|
|
||||||
// 同步到URL(标记为用户操作)
|
// 同步到URL(标记为用户操作)
|
||||||
updateUrl(filters, newPagination, 'user');
|
updateUrl(filters, newPagination, 'user');
|
||||||
|
// 通知父组件分页变化
|
||||||
|
onPageChange?.(page);
|
||||||
}, [internalPagination, filters, onPageChange, updateUrl]);
|
}, [internalPagination, filters, onPageChange, updateUrl]);
|
||||||
|
|
||||||
const handleSizeChange = useCallback((size: number) => {
|
const handleSizeChange = useCallback((size: number) => {
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Navbar1} from '@/components/layouts/Navbar.tsx'
|
import {Navbar1} from '@/components/layouts/Navbar.tsx'
|
||||||
import Page from './SideBar/SideBar'
|
import Page from './SideBar/SideBar'
|
||||||
import './index.css'
|
|
||||||
function Main() {
|
function Main() {
|
||||||
return (
|
return (
|
||||||
<div className = "parent-flex">
|
<div className = "flex flex-col gap-4 w-full">
|
||||||
<Navbar1></Navbar1>
|
<Navbar1></Navbar1>
|
||||||
<div>
|
<div>
|
||||||
<Page ></Page>
|
<Page ></Page>
|
||||||
|
|||||||
@@ -1,30 +1,17 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Book, Menu, Sunset, Trees, Zap } from "lucide-react";
|
import { Menu } from "lucide-react";
|
||||||
import { Sprout, Map, Clipboard, Package, Brain, Droplets, Settings } from 'lucide-react';
|
import { Sprout } from 'lucide-react';
|
||||||
import { MessageBell } from './components/MessageBell';
|
import { MessageBell } from './components/MessageBell';
|
||||||
import { UserProfile } from './components/UserProfile';
|
import { UserProfile } from './components/UserProfile';
|
||||||
import { ThemeToggle } from './ThemeToggle';
|
import { ThemeToggle } from './ThemeToggle';
|
||||||
import { useElementHeight } from '@/hooks/useElementHeight';
|
|
||||||
import { useViewHeight } from '@/hooks/useViewHeight';
|
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import { useRef, useEffect, useState } from 'react';
|
|
||||||
// 注释掉 Accordion 相关导入,因为不再需要二级菜单
|
|
||||||
// import {
|
|
||||||
// Accordion,
|
|
||||||
// AccordionContent,
|
|
||||||
// AccordionItem,
|
|
||||||
// AccordionTrigger,
|
|
||||||
// } from "@/components/ui/accordion";
|
|
||||||
import { useLayoutStore } from '@/stores/useLayoutStore';
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
NavigationMenu,
|
NavigationMenu,
|
||||||
NavigationMenuContent,
|
|
||||||
NavigationMenuItem,
|
NavigationMenuItem,
|
||||||
NavigationMenuLink,
|
NavigationMenuLink,
|
||||||
NavigationMenuList,
|
NavigationMenuList,
|
||||||
NavigationMenuTrigger,
|
|
||||||
} from "@/components/ui/navigation-menu";
|
} from "@/components/ui/navigation-menu";
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
@@ -82,19 +69,6 @@ const Navbar1 = ({ navbarData }: Navbar1Props) => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用自定义 Hook 计算高度
|
|
||||||
const { elementRef, updateHeight } = useElementHeight({
|
|
||||||
immediate: true, // 立即计算高度
|
|
||||||
onUpdate: (height: number) => {
|
|
||||||
// 更新 Zustand store 中的状态
|
|
||||||
const { setNavigatorHeight } = useLayoutStore.getState();
|
|
||||||
setNavigatorHeight(height);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// 监听页面高度变化
|
|
||||||
useViewHeight();
|
|
||||||
|
|
||||||
const handleMessageClick = () => {
|
const handleMessageClick = () => {
|
||||||
// 处理消息点击事件,可以跳转到消息中心页面
|
// 处理消息点击事件,可以跳转到消息中心页面
|
||||||
@@ -107,7 +81,7 @@ const Navbar1 = ({ navbarData }: Navbar1Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="py-4" ref={elementRef}>
|
<section className="py-4">
|
||||||
<div className="container" style = {containerStyle}>
|
<div className="container" style = {containerStyle}>
|
||||||
{/* Desktop Menu */}
|
{/* Desktop Menu */}
|
||||||
<nav className="hidden justify-between lg:flex">
|
<nav className="hidden justify-between lg:flex">
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { cn } from '@/lib/utils';
|
|||||||
interface NavItem {
|
interface NavItem {
|
||||||
title: string;
|
title: string;
|
||||||
url: string;
|
url: string;
|
||||||
icon: string;
|
icon: React.ReactNode;
|
||||||
items?: {
|
items?: {
|
||||||
title: string;
|
title: string;
|
||||||
url: string;
|
url: string;
|
||||||
@@ -35,7 +35,7 @@ const defaultSideBarData: SideBarData = {
|
|||||||
{
|
{
|
||||||
title: "租户管理",
|
title: "租户管理",
|
||||||
url: "/central-config/tenant",
|
url: "/central-config/tenant",
|
||||||
icon: "🏢",
|
icon: "🏢" as React.ReactNode,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: "企业审核",
|
title: "企业审核",
|
||||||
@@ -62,7 +62,7 @@ const defaultSideBarData: SideBarData = {
|
|||||||
{
|
{
|
||||||
title: "用户管理",
|
title: "用户管理",
|
||||||
url: "/central-config/user",
|
url: "/central-config/user",
|
||||||
icon: "👥",
|
icon: "👥" as React.ReactNode,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: "员工管理",
|
title: "员工管理",
|
||||||
@@ -89,7 +89,7 @@ const defaultSideBarData: SideBarData = {
|
|||||||
{
|
{
|
||||||
title: "系统参数",
|
title: "系统参数",
|
||||||
url: "/central-config/system",
|
url: "/central-config/system",
|
||||||
icon: "🔧",
|
icon: "🔧" as React.ReactNode,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: "系统设置",
|
title: "系统设置",
|
||||||
@@ -111,7 +111,7 @@ const defaultSideBarData: SideBarData = {
|
|||||||
{
|
{
|
||||||
title: "系统监控",
|
title: "系统监控",
|
||||||
url: "/central-config/monitor",
|
url: "/central-config/monitor",
|
||||||
icon: "📈",
|
icon: "📈" as React.ReactNode,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: "登录日志",
|
title: "登录日志",
|
||||||
@@ -138,7 +138,7 @@ const defaultSideBarData: SideBarData = {
|
|||||||
{
|
{
|
||||||
title: "消息中心",
|
title: "消息中心",
|
||||||
url: "/central-config/message",
|
url: "/central-config/message",
|
||||||
icon: "📨",
|
icon: "📨" as React.ReactNode,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: "消息发送",
|
title: "消息发送",
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export function UserProfile({ onProfileClick }: UserProfileProps) {
|
|||||||
<span className="text-muted-foreground">手机号:</span>
|
<span className="text-muted-foreground">手机号:</span>
|
||||||
<span>{user?.phone}</span>
|
<span>{user?.phone}</span>
|
||||||
</div>
|
</div>
|
||||||
{user?.enterpriseName && (
|
{/* {user?.enterpriseName && (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-muted-foreground">所属企业:</span>
|
<span className="text-muted-foreground">所属企业:</span>
|
||||||
<span className="truncate max-w-[140px]" title={user?.enterpriseName}>
|
<span className="truncate max-w-[140px]" title={user?.enterpriseName}>
|
||||||
@@ -76,7 +76,7 @@ export function UserProfile({ onProfileClick }: UserProfileProps) {
|
|||||||
<span className="text-muted-foreground">上次登录:</span>
|
<span className="text-muted-foreground">上次登录:</span>
|
||||||
<span className="text-muted-foreground">{user?.lastLoginTime}</span>
|
<span className="text-muted-foreground">{user?.lastLoginTime}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
<div className="border-t pt-2 mt-2">
|
<div className="border-t pt-2 mt-2">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
.parent-flex {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem; /* 控制子元素间距 */
|
|
||||||
width: 100%; /* 默认宽度 */
|
|
||||||
}
|
|
||||||
@@ -76,7 +76,6 @@ export const createClient = (config: Config = {}): Client => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const request: Client['request'] = async (options) => {
|
const request: Client['request'] = async (options) => {
|
||||||
// @ts-expect-error
|
|
||||||
const { opts, url } = await beforeRequest(options);
|
const { opts, url } = await beforeRequest(options);
|
||||||
const requestInit: ReqInit = {
|
const requestInit: ReqInit = {
|
||||||
redirect: 'follow',
|
redirect: 'follow',
|
||||||
|
|||||||
@@ -1,396 +1,52 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand';
|
||||||
import { AIModel, ModelTraining, PredictionResult, Recommendation } from '@api/modules/ai-model'
|
|
||||||
import { aiModelApi } from '@api'
|
|
||||||
import { QueryRequest } from '@api/types'
|
|
||||||
|
|
||||||
interface AIModelState {
|
// AI Model interface definition
|
||||||
// 模型数据
|
export interface AIModel {
|
||||||
modelList: AIModel[]
|
id: string;
|
||||||
selectedModel: AIModel | null
|
name: string;
|
||||||
modelLoading: boolean
|
description?: string | null;
|
||||||
modelPagination: {
|
model_type: string;
|
||||||
page: number
|
version: string;
|
||||||
pageSize: number
|
status: 'active' | 'inactive' | 'training' | 'error';
|
||||||
total: number
|
created_at: string;
|
||||||
}
|
updated_at: string;
|
||||||
|
parameters?: Record<string, any> | null;
|
||||||
// 训练数据
|
|
||||||
trainingList: ModelTraining[]
|
|
||||||
selectedTraining: ModelTraining | null
|
|
||||||
trainingLoading: boolean
|
|
||||||
trainingPagination: {
|
|
||||||
page: number
|
|
||||||
pageSize: number
|
|
||||||
total: number
|
|
||||||
}
|
|
||||||
|
|
||||||
// 预测数据
|
|
||||||
predictionList: PredictionResult[]
|
|
||||||
selectedPrediction: PredictionResult | null
|
|
||||||
predictionLoading: boolean
|
|
||||||
predictionPagination: {
|
|
||||||
page: number
|
|
||||||
pageSize: number
|
|
||||||
total: number
|
|
||||||
}
|
|
||||||
|
|
||||||
// 推荐数据
|
|
||||||
recommendationList: Recommendation[]
|
|
||||||
selectedRecommendation: Recommendation | null
|
|
||||||
recommendationLoading: boolean
|
|
||||||
recommendationPagination: {
|
|
||||||
page: number
|
|
||||||
pageSize: number
|
|
||||||
total: number
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AIModelActions {
|
// AI Model state interface
|
||||||
// 模型操作
|
export interface AIModelState {
|
||||||
fetchModelList: (params?: QueryRequest) => Promise<void>
|
models: AIModel[];
|
||||||
fetchModelDetail: (id: string) => Promise<void>
|
currentModel: AIModel | null;
|
||||||
createModel: (data: Omit<AIModel, 'id' | 'createdAt' | 'updatedAt' | 'accuracy' | 'status' | 'trainingDate'>) => Promise<void>
|
|
||||||
updateModel: (id: string, data: Partial<AIModel>) => Promise<void>
|
|
||||||
deleteModel: (id: string) => Promise<void>
|
|
||||||
deployModel: (id: string) => Promise<void>
|
|
||||||
setSelectedModel: (model: AIModel | null) => void
|
|
||||||
|
|
||||||
// 训练操作
|
// Actions
|
||||||
startTraining: (data: Omit<ModelTraining, 'id' | 'status' | 'progress' | 'createdAt' | 'updatedAt'>) => Promise<void>
|
setModels: (models: AIModel[]) => void;
|
||||||
fetchTrainingDetail: (id: string) => Promise<void>
|
setCurrentModel: (model: AIModel | null) => void;
|
||||||
fetchTrainingList: (params?: QueryRequest) => Promise<void>
|
|
||||||
stopTraining: (id: string) => Promise<void>
|
|
||||||
setSelectedTraining: (training: ModelTraining | null) => void
|
|
||||||
|
|
||||||
// 预测操作
|
// Getters
|
||||||
makePrediction: (data: { modelId: string; inputData: Record<string, any>; options?: any }) => Promise<void>
|
getModels: () => AIModel[];
|
||||||
fetchPredictionHistory: (params?: QueryRequest) => Promise<void>
|
getCurrentModel: () => AIModel | null;
|
||||||
fetchPredictionDetail: (id: string) => Promise<void>
|
|
||||||
setSelectedPrediction: (prediction: PredictionResult | null) => void
|
|
||||||
|
|
||||||
// 推荐操作
|
|
||||||
fetchRecommendations: (params?: QueryRequest & { type?: string; landParcelId?: string }) => Promise<void>
|
|
||||||
applyRecommendation: (id: string) => Promise<void>
|
|
||||||
dismissRecommendation: (id: string, reason: string) => Promise<void>
|
|
||||||
setSelectedRecommendation: (recommendation: Recommendation | null) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAIModelStore = create<AIModelState & AIModelActions>((set, get) => ({
|
// Create AI Model store
|
||||||
// 初始状态
|
export const useAIModelStore = create<AIModelState>((set, get) => ({
|
||||||
modelList: [],
|
models: [],
|
||||||
selectedModel: null,
|
currentModel: null,
|
||||||
modelLoading: false,
|
|
||||||
modelPagination: {
|
setModels: (models: AIModel[]) => {
|
||||||
page: 1,
|
set({ models });
|
||||||
pageSize: 10,
|
|
||||||
total: 0
|
|
||||||
},
|
},
|
||||||
|
|
||||||
trainingList: [],
|
setCurrentModel: (model: AIModel | null) => {
|
||||||
selectedTraining: null,
|
set({ currentModel: model });
|
||||||
trainingLoading: false,
|
|
||||||
trainingPagination: {
|
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: 0
|
|
||||||
},
|
},
|
||||||
|
|
||||||
predictionList: [],
|
getModels: () => {
|
||||||
selectedPrediction: null,
|
return get().models;
|
||||||
predictionLoading: false,
|
|
||||||
predictionPagination: {
|
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: 0
|
|
||||||
},
|
},
|
||||||
|
|
||||||
recommendationList: [],
|
getCurrentModel: () => {
|
||||||
selectedRecommendation: null,
|
return get().currentModel;
|
||||||
recommendationLoading: false,
|
|
||||||
recommendationPagination: {
|
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: 0
|
|
||||||
},
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
// 模型操作
|
export default useAIModelStore;
|
||||||
fetchModelList: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ modelLoading: true })
|
|
||||||
const response = await aiModelApi.getModelList({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
modelList: response.data.items,
|
|
||||||
modelPagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
modelLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取模型列表失败:', error)
|
|
||||||
set({ modelLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchModelDetail: async (id) => {
|
|
||||||
try {
|
|
||||||
set({ modelLoading: true })
|
|
||||||
const response = await aiModelApi.getModelDetail(id)
|
|
||||||
set({
|
|
||||||
selectedModel: response.data,
|
|
||||||
modelLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取模型详情失败:', error)
|
|
||||||
set({ modelLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
createModel: async (data) => {
|
|
||||||
try {
|
|
||||||
const response = await aiModelApi.createModel(data)
|
|
||||||
const currentList = get().modelList
|
|
||||||
set({
|
|
||||||
modelList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建模型失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateModel: async (id, data) => {
|
|
||||||
try {
|
|
||||||
const response = await aiModelApi.updateModel(id, data)
|
|
||||||
const currentList = get().modelList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
modelList: updatedList,
|
|
||||||
selectedModel: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('更新模型失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteModel: async (id) => {
|
|
||||||
try {
|
|
||||||
await aiModelApi.deleteModel(id)
|
|
||||||
const currentList = get().modelList
|
|
||||||
const updatedList = currentList.filter(item => item.id !== id)
|
|
||||||
set({
|
|
||||||
modelList: updatedList,
|
|
||||||
selectedModel: null
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除模型失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
deployModel: async (id) => {
|
|
||||||
try {
|
|
||||||
const response = await aiModelApi.deployModel(id)
|
|
||||||
const currentList = get().modelList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
modelList: updatedList,
|
|
||||||
selectedModel: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('部署模型失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setSelectedModel: (model) => set({ selectedModel: model }),
|
|
||||||
|
|
||||||
// 训练操作
|
|
||||||
startTraining: async (data) => {
|
|
||||||
try {
|
|
||||||
const response = await aiModelApi.startTraining(data)
|
|
||||||
const currentList = get().trainingList
|
|
||||||
set({
|
|
||||||
trainingList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('开始训练失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchTrainingDetail: async (id) => {
|
|
||||||
try {
|
|
||||||
set({ trainingLoading: true })
|
|
||||||
const response = await aiModelApi.getTrainingDetail(id)
|
|
||||||
set({
|
|
||||||
selectedTraining: response.data,
|
|
||||||
trainingLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取训练详情失败:', error)
|
|
||||||
set({ trainingLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchTrainingList: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ trainingLoading: true })
|
|
||||||
const response = await aiModelApi.getTrainingList({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
trainingList: response.data.items,
|
|
||||||
trainingPagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
trainingLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取训练列表失败:', error)
|
|
||||||
set({ trainingLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
stopTraining: async (id) => {
|
|
||||||
try {
|
|
||||||
await aiModelApi.stopTraining(id)
|
|
||||||
const currentList = get().trainingList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? { ...item, status: 'completed' as const } : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
trainingList: updatedList,
|
|
||||||
selectedTraining: get().selectedTraining?.id === id
|
|
||||||
? { ...get().selectedTraining, status: 'completed' as const }
|
|
||||||
: get().selectedTraining
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('停止训练失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setSelectedTraining: (training) => set({ selectedTraining: training }),
|
|
||||||
|
|
||||||
// 预测操作
|
|
||||||
makePrediction: async (data) => {
|
|
||||||
try {
|
|
||||||
set({ predictionLoading: true })
|
|
||||||
const response = await aiModelApi.makePrediction(data)
|
|
||||||
const currentList = get().predictionList
|
|
||||||
set({
|
|
||||||
predictionList: [response.data, ...currentList],
|
|
||||||
predictionLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('预测失败:', error)
|
|
||||||
set({ predictionLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchPredictionHistory: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ predictionLoading: true })
|
|
||||||
const response = await aiModelApi.getPredictionHistory({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
predictionList: response.data.items,
|
|
||||||
predictionPagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
predictionLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取预测历史失败:', error)
|
|
||||||
set({ predictionLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchPredictionDetail: async (id) => {
|
|
||||||
try {
|
|
||||||
set({ predictionLoading: true })
|
|
||||||
const response = await aiModelApi.getPredictionDetail(id)
|
|
||||||
set({
|
|
||||||
selectedPrediction: response.data,
|
|
||||||
predictionLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取预测详情失败:', error)
|
|
||||||
set({ predictionLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setSelectedPrediction: (prediction) => set({ selectedPrediction: prediction }),
|
|
||||||
|
|
||||||
// 推荐操作
|
|
||||||
fetchRecommendations: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ recommendationLoading: true })
|
|
||||||
const response = await aiModelApi.getRecommendations({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
recommendationList: response.data.items,
|
|
||||||
recommendationPagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
recommendationLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取推荐列表失败:', error)
|
|
||||||
set({ recommendationLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
applyRecommendation: async (id) => {
|
|
||||||
try {
|
|
||||||
const response = await aiModelApi.applyRecommendation(id)
|
|
||||||
const currentList = get().recommendationList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
recommendationList: updatedList,
|
|
||||||
selectedRecommendation: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('应用推荐失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
dismissRecommendation: async (id, reason) => {
|
|
||||||
try {
|
|
||||||
const response = await aiModelApi.dismissRecommendation(id, reason)
|
|
||||||
const currentList = get().recommendationList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
recommendationList: updatedList,
|
|
||||||
selectedRecommendation: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('忽略推荐失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setSelectedRecommendation: (recommendation) => set({ selectedRecommendation: recommendation })
|
|
||||||
}))
|
|
||||||
@@ -1,370 +1,142 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand';
|
||||||
import { AgriculturalAsset, MaintenanceRecord, AssetInventory } from '@api/modules/asset'
|
|
||||||
import { assetApi } from '@api'
|
|
||||||
import { QueryRequest } from '@api/types'
|
|
||||||
|
|
||||||
interface AssetState {
|
// 农业资产接口
|
||||||
// 资产数据
|
export interface AgriculturalAsset {
|
||||||
assetList: AgriculturalAsset[]
|
id: string;
|
||||||
selectedAsset: AgriculturalAsset | null
|
name: string;
|
||||||
assetLoading: boolean
|
type: string;
|
||||||
assetError: string | null
|
category: string;
|
||||||
assetPagination: {
|
brand?: string | null;
|
||||||
page: number
|
model?: string | null;
|
||||||
pageSize: number
|
serial_number?: string | null;
|
||||||
total: number
|
purchase_date?: string | null;
|
||||||
}
|
purchase_price?: number | null;
|
||||||
|
current_value?: number | null;
|
||||||
// 维护记录数据
|
depreciation_rate?: number | null;
|
||||||
maintenanceList: MaintenanceRecord[]
|
status: 'active' | 'maintenance' | 'retired' | 'lost';
|
||||||
selectedMaintenance: MaintenanceRecord | null
|
location?: string | null;
|
||||||
maintenanceLoading: boolean
|
assigned_to?: string | null;
|
||||||
maintenancePagination: {
|
warranty_expiry?: string | null;
|
||||||
page: number
|
created_at: string;
|
||||||
pageSize: number
|
updated_at: string;
|
||||||
total: number
|
|
||||||
}
|
|
||||||
|
|
||||||
// 库存数据
|
|
||||||
inventoryList: AssetInventory[]
|
|
||||||
selectedInventory: AssetInventory | null
|
|
||||||
inventoryLoading: boolean
|
|
||||||
inventoryPagination: {
|
|
||||||
page: number
|
|
||||||
pageSize: number
|
|
||||||
total: number
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AssetActions {
|
// 维护记录接口
|
||||||
// 资产操作
|
export interface MaintenanceRecord {
|
||||||
fetchAssetList: (params?: QueryRequest) => Promise<void>
|
id: string;
|
||||||
fetchAssetDetail: (id: string) => Promise<void>
|
asset_id: string;
|
||||||
createAsset: (data: Omit<AgriculturalAsset, 'id' | 'createdAt' | 'updatedAt' | 'currentValue'>) => Promise<void>
|
maintenance_type: string;
|
||||||
updateAsset: (id: string, data: Partial<AgriculturalAsset>) => Promise<void>
|
description?: string | null;
|
||||||
deleteAsset: (id: string) => Promise<void>
|
cost?: number | null;
|
||||||
calculateDepreciation: (id: string, date: string) => Promise<void>
|
performed_by?: string | null;
|
||||||
setSelectedAsset: (asset: AgriculturalAsset | null) => void
|
performed_date?: string | null;
|
||||||
clearAssetError: () => void
|
next_maintenance_date?: string | null;
|
||||||
|
status: 'scheduled' | 'in_progress' | 'completed' | 'cancelled';
|
||||||
// 维护记录操作
|
notes?: string | null;
|
||||||
fetchMaintenanceRecords: (params?: QueryRequest) => Promise<void>
|
created_at: string;
|
||||||
fetchMaintenanceDetail: (id: string) => Promise<void>
|
updated_at: string;
|
||||||
createMaintenanceRecord: (data: Omit<MaintenanceRecord, 'id' | 'createdAt' | 'updatedAt'>) => Promise<void>
|
|
||||||
updateMaintenanceRecord: (id: string, data: Partial<MaintenanceRecord>) => Promise<void>
|
|
||||||
deleteMaintenanceRecord: (id: string) => Promise<void>
|
|
||||||
|
|
||||||
// 库存操作
|
|
||||||
fetchInventoryList: (params?: QueryRequest) => Promise<void>
|
|
||||||
fetchInventoryDetail: (id: string) => Promise<void>
|
|
||||||
createInventoryRecord: (data: Omit<AssetInventory, 'id' | 'createdAt' | 'updatedAt' | 'variance'>) => Promise<void>
|
|
||||||
updateInventoryRecord: (id: string, data: Partial<AssetInventory>) => Promise<void>
|
|
||||||
verifyInventory: (id: string, verifiedBy: string) => Promise<void>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAssetStore = create<AssetState & AssetActions>((set, get) => ({
|
// 资产库存接口
|
||||||
// 初始状态
|
export interface AssetInventory {
|
||||||
assetList: [],
|
id: string;
|
||||||
selectedAsset: null,
|
asset_type: string;
|
||||||
assetLoading: false,
|
name: string;
|
||||||
assetError: null,
|
description?: string | null;
|
||||||
assetPagination: {
|
quantity: number;
|
||||||
page: 1,
|
unit: string;
|
||||||
pageSize: 10,
|
unit_cost?: number | null;
|
||||||
total: 0
|
total_value?: number | null;
|
||||||
|
reorder_level?: number | null;
|
||||||
|
location?: string | null;
|
||||||
|
supplier?: string | null;
|
||||||
|
last_updated?: string | null;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asset state interface
|
||||||
|
export interface AssetState {
|
||||||
|
assets: AgriculturalAsset[];
|
||||||
|
currentAsset: AgriculturalAsset | null;
|
||||||
|
maintenanceRecords: MaintenanceRecord[];
|
||||||
|
currentMaintenance: MaintenanceRecord | null;
|
||||||
|
assetInventory: AssetInventory[];
|
||||||
|
currentInventory: AssetInventory | null;
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
setAssets: (assets: AgriculturalAsset[]) => void;
|
||||||
|
setCurrentAsset: (asset: AgriculturalAsset | null) => void;
|
||||||
|
setMaintenanceRecords: (records: MaintenanceRecord[]) => void;
|
||||||
|
setCurrentMaintenance: (record: MaintenanceRecord | null) => void;
|
||||||
|
setAssetInventory: (inventory: AssetInventory[]) => void;
|
||||||
|
setCurrentInventory: (inventory: AssetInventory | null) => void;
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
getAssets: () => AgriculturalAsset[];
|
||||||
|
getCurrentAsset: () => AgriculturalAsset | null;
|
||||||
|
getMaintenanceRecords: () => MaintenanceRecord[];
|
||||||
|
getCurrentMaintenance: () => MaintenanceRecord | null;
|
||||||
|
getAssetInventory: () => AssetInventory[];
|
||||||
|
getCurrentInventory: () => AssetInventory | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Asset store
|
||||||
|
export const useAssetStore = create<AssetState>((set, get) => ({
|
||||||
|
assets: [],
|
||||||
|
currentAsset: null,
|
||||||
|
maintenanceRecords: [],
|
||||||
|
currentMaintenance: null,
|
||||||
|
assetInventory: [],
|
||||||
|
currentInventory: null,
|
||||||
|
|
||||||
|
setAssets: (assets: AgriculturalAsset[]) => {
|
||||||
|
set({ assets });
|
||||||
},
|
},
|
||||||
|
|
||||||
maintenanceList: [],
|
setCurrentAsset: (asset: AgriculturalAsset | null) => {
|
||||||
selectedMaintenance: null,
|
set({ currentAsset: asset });
|
||||||
maintenanceLoading: false,
|
|
||||||
maintenancePagination: {
|
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: 0
|
|
||||||
},
|
},
|
||||||
|
|
||||||
inventoryList: [],
|
setMaintenanceRecords: (records: MaintenanceRecord[]) => {
|
||||||
selectedInventory: null,
|
set({ maintenanceRecords: records });
|
||||||
inventoryLoading: false,
|
|
||||||
inventoryPagination: {
|
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: 0
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 资产操作
|
setCurrentMaintenance: (record: MaintenanceRecord | null) => {
|
||||||
fetchAssetList: async (params) => {
|
set({ currentMaintenance: record });
|
||||||
try {
|
|
||||||
set({ assetLoading: true, assetError: null })
|
|
||||||
const response = await assetApi.getAssetList({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
|
|
||||||
set({
|
|
||||||
assetList: response.data.items,
|
|
||||||
assetPagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
assetLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
assetError: error instanceof Error ? error.message : '获取资产列表失败',
|
|
||||||
assetLoading: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchAssetDetail: async (id) => {
|
setAssetInventory: (inventory: AssetInventory[]) => {
|
||||||
try {
|
set({ assetInventory: inventory });
|
||||||
set({ assetLoading: true, assetError: null })
|
|
||||||
const response = await assetApi.getAssetDetail(id)
|
|
||||||
set({
|
|
||||||
selectedAsset: response.data,
|
|
||||||
assetLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
assetError: error instanceof Error ? error.message : '获取资产详情失败',
|
|
||||||
assetLoading: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createAsset: async (data) => {
|
setCurrentInventory: (inventory: AssetInventory | null) => {
|
||||||
try {
|
set({ currentInventory: inventory });
|
||||||
const response = await assetApi.createAsset(data)
|
|
||||||
const currentList = get().assetList
|
|
||||||
set({
|
|
||||||
assetList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
assetError: error instanceof Error ? error.message : '创建资产失败'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateAsset: async (id, data) => {
|
getAssets: () => {
|
||||||
try {
|
return get().assets;
|
||||||
const response = await assetApi.updateAsset(id, data)
|
|
||||||
const currentList = get().assetList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
assetList: updatedList,
|
|
||||||
selectedAsset: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
assetError: error instanceof Error ? error.message : '更新资产失败'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteAsset: async (id) => {
|
getCurrentAsset: () => {
|
||||||
try {
|
return get().currentAsset;
|
||||||
await assetApi.deleteAsset(id)
|
|
||||||
const currentList = get().assetList
|
|
||||||
const updatedList = currentList.filter(item => item.id !== id)
|
|
||||||
set({
|
|
||||||
assetList: updatedList,
|
|
||||||
selectedAsset: null
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
assetError: error instanceof Error ? error.message : '删除资产失败'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
calculateDepreciation: async (id, date) => {
|
getMaintenanceRecords: () => {
|
||||||
try {
|
return get().maintenanceRecords;
|
||||||
const response = await assetApi.calculateDepreciation(id, date)
|
|
||||||
const currentList = get().assetList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? { ...item, currentValue: response.data.currentValue } : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
assetList: updatedList,
|
|
||||||
selectedAsset: get().selectedAsset?.id === id
|
|
||||||
? { ...get().selectedAsset, currentValue: response.data.currentValue }
|
|
||||||
: get().selectedAsset
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
assetError: error instanceof Error ? error.message : '计算折旧失败'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setSelectedAsset: (asset) => set({ selectedAsset: asset }),
|
getCurrentMaintenance: () => {
|
||||||
clearAssetError: () => set({ assetError: null }),
|
return get().currentMaintenance;
|
||||||
|
|
||||||
// 维护记录操作
|
|
||||||
fetchMaintenanceRecords: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ maintenanceLoading: true })
|
|
||||||
const response = await assetApi.getMaintenanceRecords({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
maintenanceList: response.data.items,
|
|
||||||
maintenancePagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
maintenanceLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取维护记录失败:', error)
|
|
||||||
set({ maintenanceLoading: false })
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchMaintenanceDetail: async (id) => {
|
getAssetInventory: () => {
|
||||||
try {
|
return get().assetInventory;
|
||||||
set({ maintenanceLoading: true })
|
|
||||||
const response = await assetApi.getMaintenanceDetail(id)
|
|
||||||
set({
|
|
||||||
selectedMaintenance: response.data,
|
|
||||||
maintenanceLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取维护记录详情失败:', error)
|
|
||||||
set({ maintenanceLoading: false })
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createMaintenanceRecord: async (data) => {
|
getCurrentInventory: () => {
|
||||||
try {
|
return get().currentInventory;
|
||||||
const response = await assetApi.createMaintenanceRecord(data)
|
|
||||||
const currentList = get().maintenanceList
|
|
||||||
set({
|
|
||||||
maintenanceList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建维护记录失败:', error)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
updateMaintenanceRecord: async (id, data) => {
|
export default useAssetStore;
|
||||||
try {
|
|
||||||
const response = await assetApi.updateMaintenanceRecord(id, data)
|
|
||||||
const currentList = get().maintenanceList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
maintenanceList: updatedList,
|
|
||||||
selectedMaintenance: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('更新维护记录失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteMaintenanceRecord: async (id) => {
|
|
||||||
try {
|
|
||||||
await assetApi.deleteMaintenanceRecord(id)
|
|
||||||
const currentList = get().maintenanceList
|
|
||||||
const updatedList = currentList.filter(item => item.id !== id)
|
|
||||||
set({
|
|
||||||
maintenanceList: updatedList,
|
|
||||||
selectedMaintenance: null
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除维护记录失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 库存操作
|
|
||||||
fetchInventoryList: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ inventoryLoading: true })
|
|
||||||
const response = await assetApi.getInventoryList({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
inventoryList: response.data.items,
|
|
||||||
inventoryPagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
inventoryLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取库存列表失败:', error)
|
|
||||||
set({ inventoryLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchInventoryDetail: async (id) => {
|
|
||||||
try {
|
|
||||||
set({ inventoryLoading: true })
|
|
||||||
const response = await assetApi.getInventoryDetail(id)
|
|
||||||
set({
|
|
||||||
selectedInventory: response.data,
|
|
||||||
inventoryLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取库存详情失败:', error)
|
|
||||||
set({ inventoryLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
createInventoryRecord: async (data) => {
|
|
||||||
try {
|
|
||||||
const response = await assetApi.createInventoryRecord(data)
|
|
||||||
const currentList = get().inventoryList
|
|
||||||
set({
|
|
||||||
inventoryList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建库存记录失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateInventoryRecord: async (id, data) => {
|
|
||||||
try {
|
|
||||||
const response = await assetApi.updateInventoryRecord(id, data)
|
|
||||||
const currentList = get().inventoryList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
inventoryList: updatedList,
|
|
||||||
selectedInventory: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('更新库存记录失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
verifyInventory: async (id, verifiedBy) => {
|
|
||||||
try {
|
|
||||||
const response = await assetApi.verifyInventory(id, verifiedBy)
|
|
||||||
const currentList = get().inventoryList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
inventoryList: updatedList,
|
|
||||||
selectedInventory: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('验证库存失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
@@ -18,7 +18,7 @@ export interface AuthUser {
|
|||||||
bio: string | null;
|
bio: string | null;
|
||||||
display_name: string;
|
display_name: string;
|
||||||
department_id: string | null;
|
department_id: string | null;
|
||||||
department_name: string | null;
|
department_name?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings item interface
|
// Settings item interface
|
||||||
|
|||||||
@@ -1,675 +1,129 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand';
|
||||||
import {
|
|
||||||
Tenant,
|
|
||||||
User,
|
|
||||||
SystemParameter,
|
|
||||||
SystemLog,
|
|
||||||
Message
|
|
||||||
} from '@api/modules/config'
|
|
||||||
import { configApi } from '@api'
|
|
||||||
import { QueryRequest } from '@api/types'
|
|
||||||
|
|
||||||
interface ConfigState {
|
// 租户接口
|
||||||
// 租户数据
|
export interface Tenant {
|
||||||
tenantList: Tenant[]
|
id: string;
|
||||||
selectedTenant: Tenant | null
|
name: string;
|
||||||
tenantLoading: boolean
|
code: string;
|
||||||
tenantPagination: {
|
description?: string | null;
|
||||||
page: number
|
status: 'active' | 'inactive' | 'suspended';
|
||||||
pageSize: number
|
contact_email?: string | null;
|
||||||
total: number
|
contact_phone?: string | null;
|
||||||
}
|
max_users?: number | null;
|
||||||
|
created_at: string;
|
||||||
// 用户数据
|
updated_at: string;
|
||||||
userList: User[]
|
|
||||||
selectedUser: User | null
|
|
||||||
userLoading: boolean
|
|
||||||
userPagination: {
|
|
||||||
page: number
|
|
||||||
pageSize: number
|
|
||||||
total: number
|
|
||||||
}
|
|
||||||
|
|
||||||
// 系统参数数据
|
|
||||||
parameterList: SystemParameter[]
|
|
||||||
selectedParameter: SystemParameter | null
|
|
||||||
parameterLoading: boolean
|
|
||||||
parameterPagination: {
|
|
||||||
page: number
|
|
||||||
pageSize: number
|
|
||||||
total: number
|
|
||||||
}
|
|
||||||
|
|
||||||
// 系统日志数据
|
|
||||||
logList: SystemLog[]
|
|
||||||
logLoading: boolean
|
|
||||||
logPagination: {
|
|
||||||
page: number
|
|
||||||
pageSize: number
|
|
||||||
total: number
|
|
||||||
}
|
|
||||||
|
|
||||||
// 消息数据
|
|
||||||
messageList: Message[]
|
|
||||||
selectedMessage: Message | null
|
|
||||||
messageLoading: boolean
|
|
||||||
messagePagination: {
|
|
||||||
page: number
|
|
||||||
pageSize: number
|
|
||||||
total: number
|
|
||||||
}
|
|
||||||
unreadCount: number
|
|
||||||
|
|
||||||
// 系统指标数据
|
|
||||||
systemMetrics: {
|
|
||||||
totalUsers: number
|
|
||||||
activeUsers: number
|
|
||||||
totalTenants: number
|
|
||||||
activeTenants: number
|
|
||||||
systemUptime: number
|
|
||||||
cpuUsage: number
|
|
||||||
memoryUsage: number
|
|
||||||
diskUsage: number
|
|
||||||
databaseConnections: number
|
|
||||||
} | null
|
|
||||||
|
|
||||||
systemHealth: {
|
|
||||||
status: 'healthy' | 'warning' | 'critical'
|
|
||||||
services: Array<{
|
|
||||||
name: string
|
|
||||||
status: 'up' | 'down'
|
|
||||||
responseTime: number
|
|
||||||
lastCheck: string
|
|
||||||
}>
|
|
||||||
issues: Array<{
|
|
||||||
type: 'error' | 'warning'
|
|
||||||
message: string
|
|
||||||
timestamp: string
|
|
||||||
}>
|
|
||||||
} | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConfigActions {
|
// 用户接口
|
||||||
// 租户操作
|
export interface User {
|
||||||
fetchTenantList: (params?: QueryRequest) => Promise<void>
|
id: string;
|
||||||
fetchTenantDetail: (id: string) => Promise<void>
|
username: string;
|
||||||
createTenant: (data: Omit<Tenant, 'id' | 'createdAt' | 'updatedAt' | 'currentUsers'>) => Promise<void>
|
email: string;
|
||||||
updateTenant: (id: string, data: Partial<Tenant>) => Promise<void>
|
full_name?: string | null;
|
||||||
deleteTenant: (id: string) => Promise<void>
|
phone?: string | null;
|
||||||
suspendTenant: (id: string) => Promise<void>
|
role: string;
|
||||||
activateTenant: (id: string) => Promise<void>
|
status: 'active' | 'inactive' | 'suspended';
|
||||||
setSelectedTenant: (tenant: Tenant | null) => void
|
tenant_id: string;
|
||||||
|
last_login_at?: string | null;
|
||||||
// 用户操作
|
created_at: string;
|
||||||
fetchUserList: (params?: QueryRequest) => Promise<void>
|
updated_at: string;
|
||||||
fetchUserDetail: (id: string) => Promise<void>
|
|
||||||
createUser: (data: Omit<User, 'id' | 'createdAt' | 'updatedAt' | 'lastLoginAt'>) => Promise<void>
|
|
||||||
updateUser: (id: string, data: Partial<User>) => Promise<void>
|
|
||||||
deleteUser: (id: string) => Promise<void>
|
|
||||||
resetPassword: (id: string, newPassword: string) => Promise<void>
|
|
||||||
lockUser: (id: string) => Promise<void>
|
|
||||||
unlockUser: (id: string) => Promise<void>
|
|
||||||
setSelectedUser: (user: User | null) => void
|
|
||||||
|
|
||||||
// 系统参数操作
|
|
||||||
fetchParameterList: (params?: QueryRequest) => Promise<void>
|
|
||||||
fetchParameterDetail: (id: string) => Promise<void>
|
|
||||||
createParameter: (data: Omit<SystemParameter, 'id' | 'createdAt' | 'updatedAt'>) => Promise<void>
|
|
||||||
updateParameter: (id: string, data: Partial<SystemParameter>) => Promise<void>
|
|
||||||
deleteParameter: (id: string) => Promise<void>
|
|
||||||
getParametersByCategory: (category: string) => Promise<void>
|
|
||||||
setSelectedParameter: (parameter: SystemParameter | null) => void
|
|
||||||
|
|
||||||
// 系统监控操作
|
|
||||||
fetchSystemLogs: (params?: QueryRequest & { level?: string; category?: string }) => Promise<void>
|
|
||||||
fetchSystemMetrics: () => Promise<void>
|
|
||||||
fetchSystemHealth: () => Promise<void>
|
|
||||||
|
|
||||||
// 消息操作
|
|
||||||
fetchMessageList: (params?: QueryRequest) => Promise<void>
|
|
||||||
fetchMessageDetail: (id: string) => Promise<void>
|
|
||||||
createMessage: (data: Omit<Message, 'id' | 'createdAt' | 'readAt' | 'status'>) => Promise<void>
|
|
||||||
markMessageAsRead: (id: string) => Promise<void>
|
|
||||||
archiveMessage: (id: string) => Promise<void>
|
|
||||||
deleteMessage: (id: string) => Promise<void>
|
|
||||||
getUnreadCount: () => Promise<void>
|
|
||||||
setSelectedMessage: (message: Message | null) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useConfigStore = create<ConfigState & ConfigActions>((set, get) => ({
|
// 系统参数接口
|
||||||
// 初始状态
|
export interface SystemParameter {
|
||||||
tenantList: [],
|
id: string;
|
||||||
selectedTenant: null,
|
key: string;
|
||||||
tenantLoading: false,
|
value: string;
|
||||||
tenantPagination: {
|
description?: string | null;
|
||||||
page: 1,
|
category: string;
|
||||||
pageSize: 10,
|
data_type: 'string' | 'number' | 'boolean' | 'json';
|
||||||
total: 0
|
is_system: boolean;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config state interface
|
||||||
|
export interface ConfigState {
|
||||||
|
tenants: Tenant[];
|
||||||
|
currentTenant: Tenant | null;
|
||||||
|
users: User[];
|
||||||
|
currentUser: User | null;
|
||||||
|
systemParameters: SystemParameter[];
|
||||||
|
currentParameter: SystemParameter | null;
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
setTenants: (tenants: Tenant[]) => void;
|
||||||
|
setCurrentTenant: (tenant: Tenant | null) => void;
|
||||||
|
setUsers: (users: User[]) => void;
|
||||||
|
setCurrentUser: (user: User | null) => void;
|
||||||
|
setSystemParameters: (parameters: SystemParameter[]) => void;
|
||||||
|
setCurrentParameter: (parameter: SystemParameter | null) => void;
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
getTenants: () => Tenant[];
|
||||||
|
getCurrentTenant: () => Tenant | null;
|
||||||
|
getUsers: () => User[];
|
||||||
|
getCurrentUser: () => User | null;
|
||||||
|
getSystemParameters: () => SystemParameter[];
|
||||||
|
getCurrentParameter: () => SystemParameter | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Config store
|
||||||
|
export const useConfigStore = create<ConfigState>((set, get) => ({
|
||||||
|
tenants: [],
|
||||||
|
currentTenant: null,
|
||||||
|
users: [],
|
||||||
|
currentUser: null,
|
||||||
|
systemParameters: [],
|
||||||
|
currentParameter: null,
|
||||||
|
|
||||||
|
setTenants: (tenants: Tenant[]) => {
|
||||||
|
set({ tenants });
|
||||||
},
|
},
|
||||||
|
|
||||||
userList: [],
|
setCurrentTenant: (tenant: Tenant | null) => {
|
||||||
selectedUser: null,
|
set({ currentTenant: tenant });
|
||||||
userLoading: false,
|
|
||||||
userPagination: {
|
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: 0
|
|
||||||
},
|
},
|
||||||
|
|
||||||
parameterList: [],
|
setUsers: (users: User[]) => {
|
||||||
selectedParameter: null,
|
set({ users });
|
||||||
parameterLoading: false,
|
|
||||||
parameterPagination: {
|
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: 0
|
|
||||||
},
|
},
|
||||||
|
|
||||||
logList: [],
|
setCurrentUser: (user: User | null) => {
|
||||||
logLoading: false,
|
set({ currentUser: user });
|
||||||
logPagination: {
|
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: 0
|
|
||||||
},
|
},
|
||||||
|
|
||||||
messageList: [],
|
setSystemParameters: (parameters: SystemParameter[]) => {
|
||||||
selectedMessage: null,
|
set({ systemParameters: parameters });
|
||||||
messageLoading: false,
|
|
||||||
messagePagination: {
|
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: 0
|
|
||||||
},
|
|
||||||
unreadCount: 0,
|
|
||||||
|
|
||||||
systemMetrics: null,
|
|
||||||
systemHealth: null,
|
|
||||||
|
|
||||||
// 租户操作
|
|
||||||
fetchTenantList: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ tenantLoading: true })
|
|
||||||
const response = await configApi.getTenantList({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
tenantList: response.data.items,
|
|
||||||
tenantPagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
tenantLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取租户列表失败:', error)
|
|
||||||
set({ tenantLoading: false })
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchTenantDetail: async (id) => {
|
setCurrentParameter: (parameter: SystemParameter | null) => {
|
||||||
try {
|
set({ currentParameter: parameter });
|
||||||
set({ tenantLoading: true })
|
|
||||||
const response = await configApi.getTenantDetail(id)
|
|
||||||
set({
|
|
||||||
selectedTenant: response.data,
|
|
||||||
tenantLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取租户详情失败:', error)
|
|
||||||
set({ tenantLoading: false })
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createTenant: async (data) => {
|
getTenants: () => {
|
||||||
try {
|
return get().tenants;
|
||||||
const response = await configApi.createTenant(data)
|
|
||||||
const currentList = get().tenantList
|
|
||||||
set({
|
|
||||||
tenantList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建租户失败:', error)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateTenant: async (id, data) => {
|
getCurrentTenant: () => {
|
||||||
try {
|
return get().currentTenant;
|
||||||
const response = await configApi.updateTenant(id, data)
|
|
||||||
const currentList = get().tenantList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
tenantList: updatedList,
|
|
||||||
selectedTenant: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('更新租户失败:', error)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteTenant: async (id) => {
|
getUsers: () => {
|
||||||
try {
|
return get().users;
|
||||||
await configApi.deleteTenant(id)
|
|
||||||
const currentList = get().tenantList
|
|
||||||
const updatedList = currentList.filter(item => item.id !== id)
|
|
||||||
set({
|
|
||||||
tenantList: updatedList,
|
|
||||||
selectedTenant: null
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除租户失败:', error)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
suspendTenant: async (id) => {
|
getCurrentUser: () => {
|
||||||
try {
|
return get().currentUser;
|
||||||
const response = await configApi.suspendTenant(id)
|
|
||||||
const currentList = get().tenantList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
tenantList: updatedList,
|
|
||||||
selectedTenant: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('暂停租户失败:', error)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
activateTenant: async (id) => {
|
getSystemParameters: () => {
|
||||||
try {
|
return get().systemParameters;
|
||||||
const response = await configApi.activateTenant(id)
|
|
||||||
const currentList = get().tenantList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
tenantList: updatedList,
|
|
||||||
selectedTenant: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('激活租户失败:', error)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setSelectedTenant: (tenant) => set({ selectedTenant: tenant }),
|
getCurrentParameter: () => {
|
||||||
|
return get().currentParameter;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
// 用户操作
|
export default useConfigStore;
|
||||||
fetchUserList: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ userLoading: true })
|
|
||||||
const response = await configApi.getUserList({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
userList: response.data.items,
|
|
||||||
userPagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
userLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取用户列表失败:', error)
|
|
||||||
set({ userLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchUserDetail: async (id) => {
|
|
||||||
try {
|
|
||||||
set({ userLoading: true })
|
|
||||||
const response = await configApi.getUserDetail(id)
|
|
||||||
set({
|
|
||||||
selectedUser: response.data,
|
|
||||||
userLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取用户详情失败:', error)
|
|
||||||
set({ userLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
createUser: async (data) => {
|
|
||||||
try {
|
|
||||||
const response = await configApi.createUser(data)
|
|
||||||
const currentList = get().userList
|
|
||||||
set({
|
|
||||||
userList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建用户失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateUser: async (id, data) => {
|
|
||||||
try {
|
|
||||||
const response = await configApi.updateUser(id, data)
|
|
||||||
const currentList = get().userList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
userList: updatedList,
|
|
||||||
selectedUser: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('更新用户失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteUser: async (id) => {
|
|
||||||
try {
|
|
||||||
await configApi.deleteUser(id)
|
|
||||||
const currentList = get().userList
|
|
||||||
const updatedList = currentList.filter(item => item.id !== id)
|
|
||||||
set({
|
|
||||||
userList: updatedList,
|
|
||||||
selectedUser: null
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除用户失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
resetPassword: async (id, newPassword) => {
|
|
||||||
try {
|
|
||||||
await configApi.resetPassword(id, newPassword)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('重置密码失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
lockUser: async (id) => {
|
|
||||||
try {
|
|
||||||
const response = await configApi.lockUser(id)
|
|
||||||
const currentList = get().userList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
userList: updatedList,
|
|
||||||
selectedUser: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('锁定用户失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
unlockUser: async (id) => {
|
|
||||||
try {
|
|
||||||
const response = await configApi.unlockUser(id)
|
|
||||||
const currentList = get().userList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
userList: updatedList,
|
|
||||||
selectedUser: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('解锁用户失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setSelectedUser: (user) => set({ selectedUser: user }),
|
|
||||||
|
|
||||||
// 系统参数操作
|
|
||||||
fetchParameterList: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ parameterLoading: true })
|
|
||||||
const response = await configApi.getParameterList({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
parameterList: response.data.items,
|
|
||||||
parameterPagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
parameterLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取系统参数列表失败:', error)
|
|
||||||
set({ parameterLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchParameterDetail: async (id) => {
|
|
||||||
try {
|
|
||||||
set({ parameterLoading: true })
|
|
||||||
const response = await configApi.getParameterDetail(id)
|
|
||||||
set({
|
|
||||||
selectedParameter: response.data,
|
|
||||||
parameterLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取系统参数详情失败:', error)
|
|
||||||
set({ parameterLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
createParameter: async (data) => {
|
|
||||||
try {
|
|
||||||
const response = await configApi.createParameter(data)
|
|
||||||
const currentList = get().parameterList
|
|
||||||
set({
|
|
||||||
parameterList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建系统参数失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateParameter: async (id, data) => {
|
|
||||||
try {
|
|
||||||
const response = await configApi.updateParameter(id, data)
|
|
||||||
const currentList = get().parameterList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
parameterList: updatedList,
|
|
||||||
selectedParameter: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('更新系统参数失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteParameter: async (id) => {
|
|
||||||
try {
|
|
||||||
await configApi.deleteParameter(id)
|
|
||||||
const currentList = get().parameterList
|
|
||||||
const updatedList = currentList.filter(item => item.id !== id)
|
|
||||||
set({
|
|
||||||
parameterList: updatedList,
|
|
||||||
selectedParameter: null
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除系统参数失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getParametersByCategory: async (category) => {
|
|
||||||
try {
|
|
||||||
set({ parameterLoading: true })
|
|
||||||
const response = await configApi.getParametersByCategory(category)
|
|
||||||
set({
|
|
||||||
parameterList: response.data,
|
|
||||||
parameterLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取分类参数失败:', error)
|
|
||||||
set({ parameterLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setSelectedParameter: (parameter) => set({ selectedParameter: parameter }),
|
|
||||||
|
|
||||||
// 系统监控操作
|
|
||||||
fetchSystemLogs: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ logLoading: true })
|
|
||||||
const response = await configApi.getSystemLogs({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
logList: response.data.items,
|
|
||||||
logPagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
logLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取系统日志失败:', error)
|
|
||||||
set({ logLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchSystemMetrics: async () => {
|
|
||||||
try {
|
|
||||||
const response = await configApi.getSystemMetrics()
|
|
||||||
set({
|
|
||||||
systemMetrics: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取系统指标失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchSystemHealth: async () => {
|
|
||||||
try {
|
|
||||||
const response = await configApi.getSystemHealth()
|
|
||||||
set({
|
|
||||||
systemHealth: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取系统健康状态失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 消息操作
|
|
||||||
fetchMessageList: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ messageLoading: true })
|
|
||||||
const response = await configApi.getMessageList({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
messageList: response.data.items,
|
|
||||||
messagePagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
messageLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取消息列表失败:', error)
|
|
||||||
set({ messageLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchMessageDetail: async (id) => {
|
|
||||||
try {
|
|
||||||
set({ messageLoading: true })
|
|
||||||
const response = await configApi.getMessageDetail(id)
|
|
||||||
set({
|
|
||||||
selectedMessage: response.data,
|
|
||||||
messageLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取消息详情失败:', error)
|
|
||||||
set({ messageLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
createMessage: async (data) => {
|
|
||||||
try {
|
|
||||||
const response = await configApi.createMessage(data)
|
|
||||||
const currentList = get().messageList
|
|
||||||
set({
|
|
||||||
messageList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建消息失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
markMessageAsRead: async (id) => {
|
|
||||||
try {
|
|
||||||
const response = await configApi.markMessageAsRead(id)
|
|
||||||
const currentList = get().messageList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
messageList: updatedList,
|
|
||||||
selectedMessage: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('标记消息已读失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
archiveMessage: async (id) => {
|
|
||||||
try {
|
|
||||||
const response = await configApi.archiveMessage(id)
|
|
||||||
const currentList = get().messageList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
messageList: updatedList,
|
|
||||||
selectedMessage: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('归档消息失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteMessage: async (id) => {
|
|
||||||
try {
|
|
||||||
await configApi.deleteMessage(id)
|
|
||||||
const currentList = get().messageList
|
|
||||||
const updatedList = currentList.filter(item => item.id !== id)
|
|
||||||
set({
|
|
||||||
messageList: updatedList,
|
|
||||||
selectedMessage: null
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除消息失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getUnreadCount: async () => {
|
|
||||||
try {
|
|
||||||
const response = await configApi.getUnreadCount()
|
|
||||||
set({
|
|
||||||
unreadCount: response.data.count
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取未读消息数量失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setSelectedMessage: (message) => set({ selectedMessage: message })
|
|
||||||
}))
|
|
||||||
@@ -1,76 +1,64 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand';
|
||||||
import { persist } from 'zustand/middleware'
|
import { persist } from 'zustand/middleware';
|
||||||
|
|
||||||
interface GlobalState {
|
// 租户信息接口
|
||||||
// 主题设置
|
export interface Tenant {
|
||||||
theme: 'light' | 'dark' | 'auto'
|
id: string;
|
||||||
|
name: string;
|
||||||
// 语言设置
|
code: string;
|
||||||
language: 'zh-CN' | 'en-US'
|
|
||||||
|
|
||||||
// 侧边栏状态
|
|
||||||
sidebarCollapsed: boolean
|
|
||||||
|
|
||||||
// 全局加载状态
|
|
||||||
globalLoading: boolean
|
|
||||||
|
|
||||||
// 面包屑导航
|
|
||||||
breadcrumbs: Array<{
|
|
||||||
title: string
|
|
||||||
path?: string
|
|
||||||
}>
|
|
||||||
|
|
||||||
// 通知消息
|
|
||||||
notifications: Array<{
|
|
||||||
id: string
|
|
||||||
type: 'success' | 'error' | 'warning' | 'info'
|
|
||||||
title: string
|
|
||||||
message: string
|
|
||||||
duration?: number
|
|
||||||
timestamp: number
|
|
||||||
}>
|
|
||||||
|
|
||||||
// 当前选中的租户(如果是多租户系统)
|
|
||||||
currentTenant: {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
code: string
|
|
||||||
} | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GlobalActions {
|
// 面包屑接口
|
||||||
// 主题操作
|
export interface Breadcrumb {
|
||||||
setTheme: (theme: 'light' | 'dark' | 'auto') => void
|
title: string;
|
||||||
|
path?: string;
|
||||||
// 语言操作
|
|
||||||
setLanguage: (language: 'zh-CN' | 'en-US') => void
|
|
||||||
|
|
||||||
// 侧边栏操作
|
|
||||||
toggleSidebar: () => void
|
|
||||||
setSidebarCollapsed: (collapsed: boolean) => void
|
|
||||||
|
|
||||||
// 全局加载状态
|
|
||||||
setGlobalLoading: (loading: boolean) => void
|
|
||||||
|
|
||||||
// 面包屑操作
|
|
||||||
setBreadcrumbs: (breadcrumbs: Array<{ title: string; path?: string }>) => void
|
|
||||||
addBreadcrumb: (breadcrumb: { title: string; path?: string }) => void
|
|
||||||
|
|
||||||
// 通知操作
|
|
||||||
addNotification: (notification: {
|
|
||||||
type: 'success' | 'error' | 'warning' | 'info'
|
|
||||||
title: string
|
|
||||||
message: string
|
|
||||||
duration?: number
|
|
||||||
}) => void
|
|
||||||
removeNotification: (id: string) => void
|
|
||||||
clearNotifications: () => void
|
|
||||||
|
|
||||||
// 租户操作
|
|
||||||
setCurrentTenant: (tenant: { id: string; name: string; code: string } | null) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useGlobalStore = create<GlobalState & GlobalActions>()(
|
// 通知接口
|
||||||
|
export interface Notification {
|
||||||
|
id: string;
|
||||||
|
type: 'success' | 'error' | 'warning' | 'info';
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
duration?: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global state interface
|
||||||
|
export interface GlobalState {
|
||||||
|
theme: 'light' | 'dark' | 'auto';
|
||||||
|
language: 'zh-CN' | 'en-US';
|
||||||
|
sidebarCollapsed: boolean;
|
||||||
|
globalLoading: boolean;
|
||||||
|
breadcrumbs: Breadcrumb[];
|
||||||
|
notifications: Notification[];
|
||||||
|
currentTenant: Tenant | null;
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
setTheme: (theme: 'light' | 'dark' | 'auto') => void;
|
||||||
|
setLanguage: (language: 'zh-CN' | 'en-US') => void;
|
||||||
|
toggleSidebar: () => void;
|
||||||
|
setSidebarCollapsed: (collapsed: boolean) => void;
|
||||||
|
setGlobalLoading: (loading: boolean) => void;
|
||||||
|
setBreadcrumbs: (breadcrumbs: Breadcrumb[]) => void;
|
||||||
|
addBreadcrumb: (breadcrumb: Breadcrumb) => void;
|
||||||
|
addNotification: (notification: Omit<Notification, 'id' | 'timestamp'>) => void;
|
||||||
|
removeNotification: (id: string) => void;
|
||||||
|
clearNotifications: () => void;
|
||||||
|
setCurrentTenant: (tenant: Tenant | null) => void;
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
getTheme: () => 'light' | 'dark' | 'auto';
|
||||||
|
getLanguage: () => 'zh-CN' | 'en-US';
|
||||||
|
getSidebarCollapsed: () => boolean;
|
||||||
|
getGlobalLoading: () => boolean;
|
||||||
|
getBreadcrumbs: () => Breadcrumb[];
|
||||||
|
getNotifications: () => Notification[];
|
||||||
|
getCurrentTenant: () => Tenant | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Global store
|
||||||
|
export const useGlobalStore = create<GlobalState>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
// 初始状态
|
// 初始状态
|
||||||
@@ -82,55 +70,50 @@ export const useGlobalStore = create<GlobalState & GlobalActions>()(
|
|||||||
notifications: [],
|
notifications: [],
|
||||||
currentTenant: null,
|
currentTenant: null,
|
||||||
|
|
||||||
// 主题操作
|
// Actions
|
||||||
setTheme: (theme) => set({ theme }),
|
setTheme: (theme) => set({ theme }),
|
||||||
|
|
||||||
// 语言操作
|
|
||||||
setLanguage: (language) => set({ language }),
|
setLanguage: (language) => set({ language }),
|
||||||
|
|
||||||
// 侧边栏操作
|
|
||||||
toggleSidebar: () => set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed })),
|
toggleSidebar: () => set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed })),
|
||||||
setSidebarCollapsed: (collapsed) => set({ sidebarCollapsed: collapsed }),
|
setSidebarCollapsed: (collapsed) => set({ sidebarCollapsed: collapsed }),
|
||||||
|
|
||||||
// 全局加载状态
|
|
||||||
setGlobalLoading: (loading) => set({ globalLoading: loading }),
|
setGlobalLoading: (loading) => set({ globalLoading: loading }),
|
||||||
|
|
||||||
// 面包屑操作
|
|
||||||
setBreadcrumbs: (breadcrumbs) => set({ breadcrumbs }),
|
setBreadcrumbs: (breadcrumbs) => set({ breadcrumbs }),
|
||||||
addBreadcrumb: (breadcrumb) => set((state) => ({
|
addBreadcrumb: (breadcrumb) => set((state) => ({
|
||||||
breadcrumbs: [...state.breadcrumbs, breadcrumb]
|
breadcrumbs: [...state.breadcrumbs, breadcrumb]
|
||||||
})),
|
})),
|
||||||
|
|
||||||
// 通知操作
|
|
||||||
addNotification: (notification) => {
|
addNotification: (notification) => {
|
||||||
const id = Date.now().toString()
|
const id = Date.now().toString();
|
||||||
const newNotification = {
|
const newNotification = {
|
||||||
...notification,
|
...notification,
|
||||||
id,
|
id,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
duration: notification.duration || 4500
|
duration: notification.duration || 4500
|
||||||
}
|
};
|
||||||
|
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
notifications: [...state.notifications, newNotification]
|
notifications: [...state.notifications, newNotification]
|
||||||
}))
|
}));
|
||||||
|
|
||||||
// 自动移除通知
|
// 自动移除通知
|
||||||
if (newNotification.duration && newNotification.duration > 0) {
|
if (newNotification.duration && newNotification.duration > 0) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
get().removeNotification(id)
|
get().removeNotification(id);
|
||||||
}, newNotification.duration)
|
}, newNotification.duration);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
removeNotification: (id) => set((state) => ({
|
removeNotification: (id) => set((state) => ({
|
||||||
notifications: state.notifications.filter(n => n.id !== id)
|
notifications: state.notifications.filter(n => n.id !== id)
|
||||||
})),
|
})),
|
||||||
|
|
||||||
clearNotifications: () => set({ notifications: [] }),
|
clearNotifications: () => set({ notifications: [] }),
|
||||||
|
setCurrentTenant: (tenant) => set({ currentTenant: tenant }),
|
||||||
|
|
||||||
// 租户操作
|
// Getters
|
||||||
setCurrentTenant: (tenant) => set({ currentTenant: tenant })
|
getTheme: () => get().theme,
|
||||||
|
getLanguage: () => get().language,
|
||||||
|
getSidebarCollapsed: () => get().sidebarCollapsed,
|
||||||
|
getGlobalLoading: () => get().globalLoading,
|
||||||
|
getBreadcrumbs: () => get().breadcrumbs,
|
||||||
|
getNotifications: () => get().notifications,
|
||||||
|
getCurrentTenant: () => get().currentTenant,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'global-storage',
|
name: 'global-storage',
|
||||||
@@ -142,4 +125,6 @@ export const useGlobalStore = create<GlobalState & GlobalActions>()(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
|
|
||||||
|
export default useGlobalStore;
|
||||||
@@ -1,458 +1,130 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand';
|
||||||
import {
|
|
||||||
IrrigationSystem,
|
|
||||||
IrrigationZone,
|
|
||||||
IrrigationSchedule,
|
|
||||||
MonitoringData,
|
|
||||||
ControlCommand
|
|
||||||
} from '@api/modules/irrigation'
|
|
||||||
import { irrigationApi } from '@api'
|
|
||||||
import { QueryRequest } from '@api/types'
|
|
||||||
|
|
||||||
interface IrrigationState {
|
// 灌溉系统接口
|
||||||
// 系统数据
|
export interface IrrigationSystem {
|
||||||
systemList: IrrigationSystem[]
|
id: string;
|
||||||
selectedSystem: IrrigationSystem | null
|
name: string;
|
||||||
systemLoading: boolean
|
type: string;
|
||||||
systemPagination: {
|
status: 'active' | 'inactive' | 'maintenance';
|
||||||
page: number
|
location: string;
|
||||||
pageSize: number
|
capacity: number;
|
||||||
total: number
|
flow_rate: number;
|
||||||
}
|
installation_date?: string | null;
|
||||||
|
last_maintenance_date?: string | null;
|
||||||
// 分区数据
|
created_at: string;
|
||||||
zoneList: IrrigationZone[]
|
updated_at: string;
|
||||||
selectedZone: IrrigationZone | null
|
|
||||||
zoneLoading: boolean
|
|
||||||
|
|
||||||
// 调度数据
|
|
||||||
scheduleList: IrrigationSchedule[]
|
|
||||||
selectedSchedule: IrrigationSchedule | null
|
|
||||||
scheduleLoading: boolean
|
|
||||||
schedulePagination: {
|
|
||||||
page: number
|
|
||||||
pageSize: number
|
|
||||||
total: number
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监控数据
|
|
||||||
monitoringData: MonitoringData[]
|
|
||||||
realTimeData: MonitoringData[]
|
|
||||||
monitoringLoading: boolean
|
|
||||||
|
|
||||||
// 控制命令
|
|
||||||
controlCommands: ControlCommand[]
|
|
||||||
controlLoading: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IrrigationActions {
|
// 灌溉区域接口
|
||||||
// 系统操作
|
export interface IrrigationZone {
|
||||||
fetchSystemList: (params?: QueryRequest) => Promise<void>
|
id: string;
|
||||||
fetchSystemDetail: (id: string) => Promise<void>
|
name: string;
|
||||||
createSystem: (data: Omit<IrrigationSystem, 'id' | 'createdAt' | 'updatedAt'>) => Promise<void>
|
system_id: string;
|
||||||
updateSystem: (id: string, data: Partial<IrrigationSystem>) => Promise<void>
|
area: number;
|
||||||
deleteSystem: (id: string) => Promise<void>
|
crop_type: string;
|
||||||
startIrrigation: (systemId: string, zoneId?: string, duration?: number) => Promise<void>
|
soil_moisture_threshold: number;
|
||||||
stopIrrigation: (systemId: string, zoneId?: string) => Promise<void>
|
irrigation_duration: number;
|
||||||
setSelectedSystem: (system: IrrigationSystem | null) => void
|
status: 'active' | 'inactive';
|
||||||
|
coordinates?: string | null;
|
||||||
// 分区操作
|
created_at: string;
|
||||||
fetchZoneList: (systemId: string, params?: QueryRequest) => Promise<void>
|
updated_at: string;
|
||||||
fetchZoneDetail: (id: string) => Promise<void>
|
|
||||||
createZone: (data: Omit<IrrigationZone, 'id' | 'createdAt' | 'updatedAt'>) => Promise<void>
|
|
||||||
updateZone: (id: string, data: Partial<IrrigationZone>) => Promise<void>
|
|
||||||
deleteZone: (id: string) => Promise<void>
|
|
||||||
setSelectedZone: (zone: IrrigationZone | null) => void
|
|
||||||
|
|
||||||
// 调度操作
|
|
||||||
fetchScheduleList: (params?: QueryRequest) => Promise<void>
|
|
||||||
fetchScheduleDetail: (id: string) => Promise<void>
|
|
||||||
createSchedule: (data: Omit<IrrigationSchedule, 'id' | 'createdAt' | 'updatedAt'>) => Promise<void>
|
|
||||||
updateSchedule: (id: string, data: Partial<IrrigationSchedule>) => Promise<void>
|
|
||||||
deleteSchedule: (id: string) => Promise<void>
|
|
||||||
pauseSchedule: (id: string) => Promise<void>
|
|
||||||
resumeSchedule: (id: string) => Promise<void>
|
|
||||||
setSelectedSchedule: (schedule: IrrigationSchedule | null) => void
|
|
||||||
|
|
||||||
// 监控操作
|
|
||||||
fetchMonitoringData: (zoneId: string, params?: QueryRequest & { sensorType?: string }) => Promise<void>
|
|
||||||
fetchRealTimeData: (zoneId: string) => Promise<void>
|
|
||||||
|
|
||||||
// 清理操作
|
|
||||||
clearData: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useIrrigationStore = create<IrrigationState & IrrigationActions>((set, get) => ({
|
// 灌溉调度接口
|
||||||
// 初始状态
|
export interface IrrigationSchedule {
|
||||||
systemList: [],
|
id: string;
|
||||||
selectedSystem: null,
|
zone_id: string;
|
||||||
systemLoading: false,
|
start_time: string;
|
||||||
systemPagination: {
|
duration: number;
|
||||||
page: 1,
|
frequency: string;
|
||||||
pageSize: 10,
|
water_amount: number;
|
||||||
total: 0
|
status: 'pending' | 'active' | 'completed' | 'cancelled';
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Irrigation state interface
|
||||||
|
export interface IrrigationState {
|
||||||
|
irrigationSystems: IrrigationSystem[];
|
||||||
|
currentSystem: IrrigationSystem | null;
|
||||||
|
irrigationZones: IrrigationZone[];
|
||||||
|
currentZone: IrrigationZone | null;
|
||||||
|
irrigationSchedules: IrrigationSchedule[];
|
||||||
|
currentSchedule: IrrigationSchedule | null;
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
setIrrigationSystems: (systems: IrrigationSystem[]) => void;
|
||||||
|
setCurrentSystem: (system: IrrigationSystem | null) => void;
|
||||||
|
setIrrigationZones: (zones: IrrigationZone[]) => void;
|
||||||
|
setCurrentZone: (zone: IrrigationZone | null) => void;
|
||||||
|
setIrrigationSchedules: (schedules: IrrigationSchedule[]) => void;
|
||||||
|
setCurrentSchedule: (schedule: IrrigationSchedule | null) => void;
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
getIrrigationSystems: () => IrrigationSystem[];
|
||||||
|
getCurrentSystem: () => IrrigationSystem | null;
|
||||||
|
getIrrigationZones: () => IrrigationZone[];
|
||||||
|
getCurrentZone: () => IrrigationZone | null;
|
||||||
|
getIrrigationSchedules: () => IrrigationSchedule[];
|
||||||
|
getCurrentSchedule: () => IrrigationSchedule | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Irrigation store
|
||||||
|
export const useIrrigationStore = create<IrrigationState>((set, get) => ({
|
||||||
|
irrigationSystems: [],
|
||||||
|
currentSystem: null,
|
||||||
|
irrigationZones: [],
|
||||||
|
currentZone: null,
|
||||||
|
irrigationSchedules: [],
|
||||||
|
currentSchedule: null,
|
||||||
|
|
||||||
|
setIrrigationSystems: (systems: IrrigationSystem[]) => {
|
||||||
|
set({ irrigationSystems: systems });
|
||||||
},
|
},
|
||||||
|
|
||||||
zoneList: [],
|
setCurrentSystem: (system: IrrigationSystem | null) => {
|
||||||
selectedZone: null,
|
set({ currentSystem: system });
|
||||||
zoneLoading: false,
|
|
||||||
|
|
||||||
scheduleList: [],
|
|
||||||
selectedSchedule: null,
|
|
||||||
scheduleLoading: false,
|
|
||||||
schedulePagination: {
|
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: 0
|
|
||||||
},
|
},
|
||||||
|
|
||||||
monitoringData: [],
|
setIrrigationZones: (zones: IrrigationZone[]) => {
|
||||||
realTimeData: [],
|
set({ irrigationZones: zones });
|
||||||
monitoringLoading: false,
|
|
||||||
|
|
||||||
controlCommands: [],
|
|
||||||
controlLoading: false,
|
|
||||||
|
|
||||||
// 系统操作
|
|
||||||
fetchSystemList: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ systemLoading: true })
|
|
||||||
const response = await irrigationApi.getSystemList({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
systemList: response.data.items,
|
|
||||||
systemPagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
systemLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取灌溉系统列表失败:', error)
|
|
||||||
set({ systemLoading: false })
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchSystemDetail: async (id) => {
|
setCurrentZone: (zone: IrrigationZone | null) => {
|
||||||
try {
|
set({ currentZone: zone });
|
||||||
set({ systemLoading: true })
|
|
||||||
const response = await irrigationApi.getSystemDetail(id)
|
|
||||||
set({
|
|
||||||
selectedSystem: response.data,
|
|
||||||
systemLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取灌溉系统详情失败:', error)
|
|
||||||
set({ systemLoading: false })
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createSystem: async (data) => {
|
setIrrigationSchedules: (schedules: IrrigationSchedule[]) => {
|
||||||
try {
|
set({ irrigationSchedules: schedules });
|
||||||
const response = await irrigationApi.createSystem(data)
|
|
||||||
const currentList = get().systemList
|
|
||||||
set({
|
|
||||||
systemList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建灌溉系统失败:', error)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateSystem: async (id, data) => {
|
setCurrentSchedule: (schedule: IrrigationSchedule | null) => {
|
||||||
try {
|
set({ currentSchedule: schedule });
|
||||||
const response = await irrigationApi.updateSystem(id, data)
|
|
||||||
const currentList = get().systemList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
systemList: updatedList,
|
|
||||||
selectedSystem: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('更新灌溉系统失败:', error)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteSystem: async (id) => {
|
getIrrigationSystems: () => {
|
||||||
try {
|
return get().irrigationSystems;
|
||||||
await irrigationApi.deleteSystem(id)
|
|
||||||
const currentList = get().systemList
|
|
||||||
const updatedList = currentList.filter(item => item.id !== id)
|
|
||||||
set({
|
|
||||||
systemList: updatedList,
|
|
||||||
selectedSystem: null
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除灌溉系统失败:', error)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
startIrrigation: async (systemId, zoneId, duration) => {
|
getCurrentSystem: () => {
|
||||||
try {
|
return get().currentSystem;
|
||||||
set({ controlLoading: true })
|
|
||||||
const response = await irrigationApi.startIrrigation(systemId, zoneId, duration)
|
|
||||||
const currentCommands = get().controlCommands
|
|
||||||
set({
|
|
||||||
controlCommands: [response.data, ...currentCommands],
|
|
||||||
controlLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('启动灌溉失败:', error)
|
|
||||||
set({ controlLoading: false })
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
stopIrrigation: async (systemId, zoneId) => {
|
getIrrigationZones: () => {
|
||||||
try {
|
return get().irrigationZones;
|
||||||
set({ controlLoading: true })
|
|
||||||
const response = await irrigationApi.stopIrrigation(systemId, zoneId)
|
|
||||||
const currentCommands = get().controlCommands
|
|
||||||
set({
|
|
||||||
controlCommands: [response.data, ...currentCommands],
|
|
||||||
controlLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('停止灌溉失败:', error)
|
|
||||||
set({ controlLoading: false })
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setSelectedSystem: (system) => set({ selectedSystem: system }),
|
getCurrentZone: () => {
|
||||||
|
return get().currentZone;
|
||||||
// 分区操作
|
|
||||||
fetchZoneList: async (systemId, params) => {
|
|
||||||
try {
|
|
||||||
set({ zoneLoading: true })
|
|
||||||
const response = await irrigationApi.getZoneList(systemId, {
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 20,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
zoneList: response.data.items,
|
|
||||||
zoneLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取灌溉分区列表失败:', error)
|
|
||||||
set({ zoneLoading: false })
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchZoneDetail: async (id) => {
|
getIrrigationSchedules: () => {
|
||||||
try {
|
return get().irrigationSchedules;
|
||||||
set({ zoneLoading: true })
|
|
||||||
const response = await irrigationApi.getZoneDetail(id)
|
|
||||||
set({
|
|
||||||
selectedZone: response.data,
|
|
||||||
zoneLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取灌溉分区详情失败:', error)
|
|
||||||
set({ zoneLoading: false })
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createZone: async (data) => {
|
getCurrentSchedule: () => {
|
||||||
try {
|
return get().currentSchedule;
|
||||||
const response = await irrigationApi.createZone(data)
|
|
||||||
const currentList = get().zoneList
|
|
||||||
set({
|
|
||||||
zoneList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建灌溉分区失败:', error)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
updateZone: async (id, data) => {
|
export default useIrrigationStore;
|
||||||
try {
|
|
||||||
const response = await irrigationApi.updateZone(id, data)
|
|
||||||
const currentList = get().zoneList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
zoneList: updatedList,
|
|
||||||
selectedZone: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('更新灌溉分区失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteZone: async (id) => {
|
|
||||||
try {
|
|
||||||
await irrigationApi.deleteZone(id)
|
|
||||||
const currentList = get().zoneList
|
|
||||||
const updatedList = currentList.filter(item => item.id !== id)
|
|
||||||
set({
|
|
||||||
zoneList: updatedList,
|
|
||||||
selectedZone: null
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除灌溉分区失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setSelectedZone: (zone) => set({ selectedZone: zone }),
|
|
||||||
|
|
||||||
// 调度操作
|
|
||||||
fetchScheduleList: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ scheduleLoading: true })
|
|
||||||
const response = await irrigationApi.getScheduleList({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
scheduleList: response.data.items,
|
|
||||||
schedulePagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
scheduleLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取灌溉调度列表失败:', error)
|
|
||||||
set({ scheduleLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchScheduleDetail: async (id) => {
|
|
||||||
try {
|
|
||||||
set({ scheduleLoading: true })
|
|
||||||
const response = await irrigationApi.getScheduleDetail(id)
|
|
||||||
set({
|
|
||||||
selectedSchedule: response.data,
|
|
||||||
scheduleLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取灌溉调度详情失败:', error)
|
|
||||||
set({ scheduleLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
createSchedule: async (data) => {
|
|
||||||
try {
|
|
||||||
const response = await irrigationApi.createSchedule(data)
|
|
||||||
const currentList = get().scheduleList
|
|
||||||
set({
|
|
||||||
scheduleList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建灌溉调度失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateSchedule: async (id, data) => {
|
|
||||||
try {
|
|
||||||
const response = await irrigationApi.updateSchedule(id, data)
|
|
||||||
const currentList = get().scheduleList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
scheduleList: updatedList,
|
|
||||||
selectedSchedule: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('更新灌溉调度失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteSchedule: async (id) => {
|
|
||||||
try {
|
|
||||||
await irrigationApi.deleteSchedule(id)
|
|
||||||
const currentList = get().scheduleList
|
|
||||||
const updatedList = currentList.filter(item => item.id !== id)
|
|
||||||
set({
|
|
||||||
scheduleList: updatedList,
|
|
||||||
selectedSchedule: null
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除灌溉调度失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
pauseSchedule: async (id) => {
|
|
||||||
try {
|
|
||||||
const response = await irrigationApi.pauseSchedule(id)
|
|
||||||
const currentList = get().scheduleList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
scheduleList: updatedList,
|
|
||||||
selectedSchedule: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('暂停灌溉调度失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
resumeSchedule: async (id) => {
|
|
||||||
try {
|
|
||||||
const response = await irrigationApi.resumeSchedule(id)
|
|
||||||
const currentList = get().scheduleList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
scheduleList: updatedList,
|
|
||||||
selectedSchedule: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('恢复灌溉调度失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setSelectedSchedule: (schedule) => set({ selectedSchedule: schedule }),
|
|
||||||
|
|
||||||
// 监控操作
|
|
||||||
fetchMonitoringData: async (zoneId, params) => {
|
|
||||||
try {
|
|
||||||
set({ monitoringLoading: true })
|
|
||||||
const response = await irrigationApi.getMonitoringData(zoneId, {
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 50,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
monitoringData: response.data.items,
|
|
||||||
monitoringLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取监控数据失败:', error)
|
|
||||||
set({ monitoringLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchRealTimeData: async (zoneId) => {
|
|
||||||
try {
|
|
||||||
const response = await irrigationApi.getRealTimeData(zoneId)
|
|
||||||
set({
|
|
||||||
realTimeData: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取实时数据失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
clearData: () => set({
|
|
||||||
zoneList: [],
|
|
||||||
selectedZone: null,
|
|
||||||
scheduleList: [],
|
|
||||||
selectedSchedule: null,
|
|
||||||
monitoringData: [],
|
|
||||||
realTimeData: [],
|
|
||||||
controlCommands: []
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
@@ -1,344 +1,91 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand';
|
||||||
import { LandParcel, LandClassification, MapLayer, SpatialAnalysis } from '@api/modules/land'
|
|
||||||
import { landApi } from '@api'
|
|
||||||
import { QueryRequest } from '@api/types'
|
|
||||||
|
|
||||||
interface LandState {
|
// 地块接口
|
||||||
// 地块数据
|
export interface LandParcel {
|
||||||
landParcelList: LandParcel[]
|
id: string;
|
||||||
selectedLandParcel: LandParcel | null
|
name: string;
|
||||||
landLoading: boolean
|
area: number;
|
||||||
landError: string | null
|
location: string;
|
||||||
landPagination: {
|
soil_type: string;
|
||||||
page: number
|
land_use_type: string;
|
||||||
pageSize: number
|
status: 'active' | 'inactive' | 'reserved';
|
||||||
total: number
|
coordinates?: string | null;
|
||||||
}
|
owner_id?: string | null;
|
||||||
|
created_at: string;
|
||||||
// 分类数据
|
updated_at: string;
|
||||||
classificationList: LandClassification[]
|
|
||||||
selectedClassification: LandClassification | null
|
|
||||||
classificationLoading: boolean
|
|
||||||
|
|
||||||
// 地图数据
|
|
||||||
mapLayers: MapLayer[]
|
|
||||||
mapLoading: boolean
|
|
||||||
|
|
||||||
// 分析数据
|
|
||||||
analysisList: SpatialAnalysis[]
|
|
||||||
selectedAnalysis: SpatialAnalysis | null
|
|
||||||
analysisLoading: boolean
|
|
||||||
analysisPagination: {
|
|
||||||
page: number
|
|
||||||
pageSize: number
|
|
||||||
total: number
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LandActions {
|
// 土壤类型接口
|
||||||
// 地块操作
|
export interface SoilType {
|
||||||
fetchLandParcelList: (params?: QueryRequest) => Promise<void>
|
id: string;
|
||||||
fetchLandParcelDetail: (id: string) => Promise<void>
|
name: string;
|
||||||
createLandParcel: (data: Omit<LandParcel, 'id' | 'createdAt' | 'updatedAt'>) => Promise<void>
|
description?: string | null;
|
||||||
updateLandParcel: (id: string, data: Partial<LandParcel>) => Promise<void>
|
ph_level?: number | null;
|
||||||
deleteLandParcel: (id: string) => Promise<void>
|
organic_matter?: number | null;
|
||||||
setSelectedLandParcel: (landParcel: LandParcel | null) => void
|
nitrogen_content?: number | null;
|
||||||
clearLandError: () => void
|
phosphorus_content?: number | null;
|
||||||
|
potassium_content?: number | null;
|
||||||
// 分类操作
|
created_at: string;
|
||||||
fetchClassificationList: (params?: QueryRequest) => Promise<void>
|
|
||||||
createClassification: (data: Omit<LandClassification, 'id' | 'createdAt' | 'updatedAt'>) => Promise<void>
|
|
||||||
updateClassification: (id: string, data: Partial<LandClassification>) => Promise<void>
|
|
||||||
deleteClassification: (id: string) => Promise<void>
|
|
||||||
|
|
||||||
// 地图操作
|
|
||||||
fetchMapLayers: () => Promise<void>
|
|
||||||
createMapLayer: (data: Omit<MapLayer, 'id'>) => Promise<void>
|
|
||||||
updateMapLayer: (id: string, data: Partial<MapLayer>) => Promise<void>
|
|
||||||
deleteMapLayer: (id: string) => Promise<void>
|
|
||||||
|
|
||||||
// 分析操作
|
|
||||||
performSpatialAnalysis: (data: Omit<SpatialAnalysis, 'id' | 'createdAt' | 'result'>) => Promise<void>
|
|
||||||
fetchAnalysisHistory: (params?: QueryRequest) => Promise<void>
|
|
||||||
setSelectedAnalysis: (analysis: SpatialAnalysis | null) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useLandStore = create<LandState & LandActions>((set, get) => ({
|
// Land state interface
|
||||||
// 初始状态
|
export interface LandState {
|
||||||
landParcelList: [],
|
landParcels: LandParcel[];
|
||||||
selectedLandParcel: null,
|
currentLandParcel: LandParcel | null;
|
||||||
landLoading: false,
|
soilTypes: SoilType[];
|
||||||
landError: null,
|
currentSoilType: SoilType | null;
|
||||||
landPagination: {
|
|
||||||
page: 1,
|
// Actions
|
||||||
pageSize: 10,
|
setLandParcels: (landParcels: LandParcel[]) => void;
|
||||||
total: 0
|
setCurrentLandParcel: (landParcel: LandParcel | null) => void;
|
||||||
|
setSoilTypes: (soilTypes: SoilType[]) => void;
|
||||||
|
setCurrentSoilType: (soilType: SoilType | null) => void;
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
getLandParcels: () => LandParcel[];
|
||||||
|
getCurrentLandParcel: () => LandParcel | null;
|
||||||
|
getSoilTypes: () => SoilType[];
|
||||||
|
getCurrentSoilType: () => SoilType | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Land store
|
||||||
|
export const useLandStore = create<LandState>((set, get) => ({
|
||||||
|
landParcels: [],
|
||||||
|
currentLandParcel: null,
|
||||||
|
soilTypes: [],
|
||||||
|
currentSoilType: null,
|
||||||
|
|
||||||
|
setLandParcels: (landParcels: LandParcel[]) => {
|
||||||
|
set({ landParcels });
|
||||||
},
|
},
|
||||||
|
|
||||||
classificationList: [],
|
setCurrentLandParcel: (landParcel: LandParcel | null) => {
|
||||||
selectedClassification: null,
|
set({ currentLandParcel: landParcel });
|
||||||
classificationLoading: false,
|
|
||||||
|
|
||||||
mapLayers: [],
|
|
||||||
mapLoading: false,
|
|
||||||
|
|
||||||
analysisList: [],
|
|
||||||
selectedAnalysis: null,
|
|
||||||
analysisLoading: false,
|
|
||||||
analysisPagination: {
|
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: 0
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 地块操作
|
setSoilTypes: (soilTypes: SoilType[]) => {
|
||||||
fetchLandParcelList: async (params) => {
|
set({ soilTypes });
|
||||||
try {
|
|
||||||
set({ landLoading: true, landError: null })
|
|
||||||
const response = await landApi.getLandParcelList({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
|
|
||||||
set({
|
|
||||||
landParcelList: response.data.items,
|
|
||||||
landPagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
landLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
landError: error instanceof Error ? error.message : '获取地块列表失败',
|
|
||||||
landLoading: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchLandParcelDetail: async (id) => {
|
setCurrentSoilType: (soilType: SoilType | null) => {
|
||||||
try {
|
set({ currentSoilType: soilType });
|
||||||
set({ landLoading: true, landError: null })
|
|
||||||
const response = await landApi.getLandParcelDetail(id)
|
|
||||||
set({
|
|
||||||
selectedLandParcel: response.data,
|
|
||||||
landLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
landError: error instanceof Error ? error.message : '获取地块详情失败',
|
|
||||||
landLoading: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createLandParcel: async (data) => {
|
getLandParcels: () => {
|
||||||
try {
|
return get().landParcels;
|
||||||
const response = await landApi.createLandParcel(data)
|
|
||||||
const currentList = get().landParcelList
|
|
||||||
set({
|
|
||||||
landParcelList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
landError: error instanceof Error ? error.message : '创建地块失败'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateLandParcel: async (id, data) => {
|
getCurrentLandParcel: () => {
|
||||||
try {
|
return get().currentLandParcel;
|
||||||
const response = await landApi.updateLandParcel(id, data)
|
|
||||||
const currentList = get().landParcelList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
landParcelList: updatedList,
|
|
||||||
selectedLandParcel: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
landError: error instanceof Error ? error.message : '更新地块失败'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteLandParcel: async (id) => {
|
getSoilTypes: () => {
|
||||||
try {
|
return get().soilTypes;
|
||||||
await landApi.deleteLandParcel(id)
|
|
||||||
const currentList = get().landParcelList
|
|
||||||
const updatedList = currentList.filter(item => item.id !== id)
|
|
||||||
set({
|
|
||||||
landParcelList: updatedList,
|
|
||||||
selectedLandParcel: null
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
landError: error instanceof Error ? error.message : '删除地块失败'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setSelectedLandParcel: (landParcel) => set({ selectedLandParcel: landParcel }),
|
getCurrentSoilType: () => {
|
||||||
clearLandError: () => set({ landError: null }),
|
return get().currentSoilType;
|
||||||
|
|
||||||
// 分类操作
|
|
||||||
fetchClassificationList: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ classificationLoading: true })
|
|
||||||
const response = await landApi.getClassificationList({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 50,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
classificationList: response.data.items,
|
|
||||||
classificationLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取分类列表失败:', error)
|
|
||||||
set({ classificationLoading: false })
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
createClassification: async (data) => {
|
export default useLandStore;
|
||||||
try {
|
|
||||||
const response = await landApi.createClassification(data)
|
|
||||||
const currentList = get().classificationList
|
|
||||||
set({
|
|
||||||
classificationList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建分类失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateClassification: async (id, data) => {
|
|
||||||
try {
|
|
||||||
const response = await landApi.updateClassification(id, data)
|
|
||||||
const currentList = get().classificationList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
classificationList: updatedList,
|
|
||||||
selectedClassification: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('更新分类失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteClassification: async (id) => {
|
|
||||||
try {
|
|
||||||
await landApi.deleteClassification(id)
|
|
||||||
const currentList = get().classificationList
|
|
||||||
const updatedList = currentList.filter(item => item.id !== id)
|
|
||||||
set({
|
|
||||||
classificationList: updatedList,
|
|
||||||
selectedClassification: null
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除分类失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 地图操作
|
|
||||||
fetchMapLayers: async () => {
|
|
||||||
try {
|
|
||||||
set({ mapLoading: true })
|
|
||||||
const response = await landApi.getMapLayers()
|
|
||||||
set({
|
|
||||||
mapLayers: response.data,
|
|
||||||
mapLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取地图图层失败:', error)
|
|
||||||
set({ mapLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
createMapLayer: async (data) => {
|
|
||||||
try {
|
|
||||||
const response = await landApi.createMapLayer(data)
|
|
||||||
const currentList = get().mapLayers
|
|
||||||
set({
|
|
||||||
mapLayers: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建地图图层失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateMapLayer: async (id, data) => {
|
|
||||||
try {
|
|
||||||
const response = await landApi.updateMapLayer(id, data)
|
|
||||||
const currentList = get().mapLayers
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
mapLayers: updatedList
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('更新地图图层失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteMapLayer: async (id) => {
|
|
||||||
try {
|
|
||||||
await landApi.deleteMapLayer(id)
|
|
||||||
const currentList = get().mapLayers
|
|
||||||
const updatedList = currentList.filter(item => item.id !== id)
|
|
||||||
set({
|
|
||||||
mapLayers: updatedList
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除地图图层失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 分析操作
|
|
||||||
performSpatialAnalysis: async (data) => {
|
|
||||||
try {
|
|
||||||
set({ analysisLoading: true })
|
|
||||||
const response = await landApi.performSpatialAnalysis(data)
|
|
||||||
const currentList = get().analysisList
|
|
||||||
set({
|
|
||||||
analysisList: [response.data, ...currentList],
|
|
||||||
analysisLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('执行空间分析失败:', error)
|
|
||||||
set({ analysisLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchAnalysisHistory: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ analysisLoading: true })
|
|
||||||
const response = await landApi.getAnalysisHistory({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
analysisList: response.data.items,
|
|
||||||
analysisPagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
analysisLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取分析历史失败:', error)
|
|
||||||
set({ analysisLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setSelectedAnalysis: (analysis) => set({ selectedAnalysis: analysis })
|
|
||||||
}))
|
|
||||||
@@ -1,305 +1,94 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand';
|
||||||
import { Machinery, Driver, MonitoringData } from '@api/modules/machinery'
|
|
||||||
import { machineryApi } from '@api'
|
|
||||||
import { QueryRequest } from '@api/types'
|
|
||||||
|
|
||||||
interface MachineryState {
|
// 农机设备接口
|
||||||
// 农机数据
|
export interface Machinery {
|
||||||
machineryList: Machinery[]
|
id: string;
|
||||||
selectedMachinery: Machinery | null
|
name: string;
|
||||||
machineryLoading: boolean
|
type: string;
|
||||||
machineryError: string | null
|
model: string;
|
||||||
machineryPagination: {
|
status: 'active' | 'maintenance' | 'inactive' | 'repair';
|
||||||
page: number
|
license_plate?: string | null;
|
||||||
pageSize: number
|
purchase_date?: string | null;
|
||||||
total: number
|
last_maintenance_date?: string | null;
|
||||||
}
|
next_maintenance_date?: string | null;
|
||||||
|
operator_id?: string | null;
|
||||||
// 驾驶员数据
|
location?: string | null;
|
||||||
driverList: Driver[]
|
created_at: string;
|
||||||
selectedDriver: Driver | null
|
updated_at: string;
|
||||||
driverLoading: boolean
|
|
||||||
driverError: string | null
|
|
||||||
driverPagination: {
|
|
||||||
page: number
|
|
||||||
pageSize: number
|
|
||||||
total: number
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监控数据
|
|
||||||
monitoringData: MonitoringData[]
|
|
||||||
realTimeData: MonitoringData | null
|
|
||||||
monitoringLoading: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MachineryActions {
|
// 驾驶员接口
|
||||||
// 农机操作
|
export interface Driver {
|
||||||
fetchMachineryList: (params?: QueryRequest) => Promise<void>
|
id: string;
|
||||||
fetchMachineryDetail: (id: string) => Promise<void>
|
name: string;
|
||||||
createMachinery: (data: Omit<Machinery, 'id' | 'createdAt' | 'updatedAt'>) => Promise<void>
|
phone: string;
|
||||||
updateMachinery: (id: string, data: Partial<Machinery>) => Promise<void>
|
license_number: string;
|
||||||
deleteMachinery: (id: string) => Promise<void>
|
license_type: string;
|
||||||
setSelectedMachinery: (machinery: Machinery | null) => void
|
experience_years?: number | null;
|
||||||
clearMachineryError: () => void
|
status: 'active' | 'inactive' | 'on_leave';
|
||||||
|
hire_date?: string | null;
|
||||||
// 驾驶员操作
|
created_at: string;
|
||||||
fetchDriverList: (params?: QueryRequest) => Promise<void>
|
updated_at: string;
|
||||||
fetchDriverDetail: (id: string) => Promise<void>
|
|
||||||
createDriver: (data: Omit<Driver, 'id' | 'createdAt' | 'updatedAt'>) => Promise<void>
|
|
||||||
updateDriver: (id: string, data: Partial<Driver>) => Promise<void>
|
|
||||||
deleteDriver: (id: string) => Promise<void>
|
|
||||||
setSelectedDriver: (driver: Driver | null) => void
|
|
||||||
clearDriverError: () => void
|
|
||||||
|
|
||||||
// 监控操作
|
|
||||||
fetchRealTimeData: (machineryId: string) => Promise<void>
|
|
||||||
fetchMonitoringHistory: (machineryId: string, params?: QueryRequest) => Promise<void>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useMachineryStore = create<MachineryState & MachineryActions>((set, get) => ({
|
// Machinery state interface
|
||||||
// 初始状态
|
export interface MachineryState {
|
||||||
machineryList: [],
|
machineries: Machinery[];
|
||||||
selectedMachinery: null,
|
currentMachinery: Machinery | null;
|
||||||
machineryLoading: false,
|
drivers: Driver[];
|
||||||
machineryError: null,
|
currentDriver: Driver | null;
|
||||||
machineryPagination: {
|
|
||||||
page: 1,
|
// Actions
|
||||||
pageSize: 10,
|
setMachineries: (machineries: Machinery[]) => void;
|
||||||
total: 0
|
setCurrentMachinery: (machinery: Machinery | null) => void;
|
||||||
|
setDrivers: (drivers: Driver[]) => void;
|
||||||
|
setCurrentDriver: (driver: Driver | null) => void;
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
getMachineries: () => Machinery[];
|
||||||
|
getCurrentMachinery: () => Machinery | null;
|
||||||
|
getDrivers: () => Driver[];
|
||||||
|
getCurrentDriver: () => Driver | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Machinery store
|
||||||
|
export const useMachineryStore = create<MachineryState>((set, get) => ({
|
||||||
|
machineries: [],
|
||||||
|
currentMachinery: null,
|
||||||
|
drivers: [],
|
||||||
|
currentDriver: null,
|
||||||
|
|
||||||
|
setMachineries: (machineries: Machinery[]) => {
|
||||||
|
set({ machineries });
|
||||||
},
|
},
|
||||||
|
|
||||||
driverList: [],
|
setCurrentMachinery: (machinery: Machinery | null) => {
|
||||||
selectedDriver: null,
|
set({ currentMachinery: machinery });
|
||||||
driverLoading: false,
|
|
||||||
driverError: null,
|
|
||||||
driverPagination: {
|
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: 0
|
|
||||||
},
|
},
|
||||||
|
|
||||||
monitoringData: [],
|
setDrivers: (drivers: Driver[]) => {
|
||||||
realTimeData: null,
|
set({ drivers });
|
||||||
monitoringLoading: false,
|
|
||||||
|
|
||||||
// 农机操作
|
|
||||||
fetchMachineryList: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ machineryLoading: true, machineryError: null })
|
|
||||||
const response = await machineryApi.getMachineryList({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
|
|
||||||
set({
|
|
||||||
machineryList: response.data.items,
|
|
||||||
machineryPagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
machineryLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
machineryError: error instanceof Error ? error.message : '获取农机列表失败',
|
|
||||||
machineryLoading: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchMachineryDetail: async (id) => {
|
setCurrentDriver: (driver: Driver | null) => {
|
||||||
try {
|
set({ currentDriver: driver });
|
||||||
set({ machineryLoading: true, machineryError: null })
|
|
||||||
const response = await machineryApi.getMachineryDetail(id)
|
|
||||||
set({
|
|
||||||
selectedMachinery: response.data,
|
|
||||||
machineryLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
machineryError: error instanceof Error ? error.message : '获取农机详情失败',
|
|
||||||
machineryLoading: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createMachinery: async (data) => {
|
getMachineries: () => {
|
||||||
try {
|
return get().machineries;
|
||||||
const response = await machineryApi.createMachinery(data)
|
|
||||||
const currentList = get().machineryList
|
|
||||||
set({
|
|
||||||
machineryList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
machineryError: error instanceof Error ? error.message : '创建农机失败'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateMachinery: async (id, data) => {
|
getCurrentMachinery: () => {
|
||||||
try {
|
return get().currentMachinery;
|
||||||
const response = await machineryApi.updateMachinery(id, data)
|
|
||||||
const currentList = get().machineryList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
machineryList: updatedList,
|
|
||||||
selectedMachinery: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
machineryError: error instanceof Error ? error.message : '更新农机失败'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteMachinery: async (id) => {
|
getDrivers: () => {
|
||||||
try {
|
return get().drivers;
|
||||||
await machineryApi.deleteMachinery(id)
|
|
||||||
const currentList = get().machineryList
|
|
||||||
const updatedList = currentList.filter(item => item.id !== id)
|
|
||||||
set({
|
|
||||||
machineryList: updatedList,
|
|
||||||
selectedMachinery: null
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
machineryError: error instanceof Error ? error.message : '删除农机失败'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setSelectedMachinery: (machinery) => set({ selectedMachinery: machinery }),
|
getCurrentDriver: () => {
|
||||||
clearMachineryError: () => set({ machineryError: null }),
|
return get().currentDriver;
|
||||||
|
|
||||||
// 驾驶员操作
|
|
||||||
fetchDriverList: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ driverLoading: true, driverError: null })
|
|
||||||
const response = await machineryApi.getDriverList({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
|
|
||||||
set({
|
|
||||||
driverList: response.data.items,
|
|
||||||
driverPagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
driverLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
driverError: error instanceof Error ? error.message : '获取驾驶员列表失败',
|
|
||||||
driverLoading: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
fetchDriverDetail: async (id) => {
|
export default useMachineryStore;
|
||||||
try {
|
|
||||||
set({ driverLoading: true, driverError: null })
|
|
||||||
const response = await machineryApi.getDriverDetail(id)
|
|
||||||
set({
|
|
||||||
selectedDriver: response.data,
|
|
||||||
driverLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
driverError: error instanceof Error ? error.message : '获取驾驶员详情失败',
|
|
||||||
driverLoading: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
createDriver: async (data) => {
|
|
||||||
try {
|
|
||||||
const response = await machineryApi.createDriver(data)
|
|
||||||
const currentList = get().driverList
|
|
||||||
set({
|
|
||||||
driverList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
driverError: error instanceof Error ? error.message : '创建驾驶员失败'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateDriver: async (id, data) => {
|
|
||||||
try {
|
|
||||||
const response = await machineryApi.updateDriver(id, data)
|
|
||||||
const currentList = get().driverList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
driverList: updatedList,
|
|
||||||
selectedDriver: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
driverError: error instanceof Error ? error.message : '更新驾驶员失败'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteDriver: async (id) => {
|
|
||||||
try {
|
|
||||||
await machineryApi.deleteDriver(id)
|
|
||||||
const currentList = get().driverList
|
|
||||||
const updatedList = currentList.filter(item => item.id !== id)
|
|
||||||
set({
|
|
||||||
driverList: updatedList,
|
|
||||||
selectedDriver: null
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
driverError: error instanceof Error ? error.message : '删除驾驶员失败'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setSelectedDriver: (driver) => set({ selectedDriver: driver }),
|
|
||||||
clearDriverError: () => set({ driverError: null }),
|
|
||||||
|
|
||||||
// 监控操作
|
|
||||||
fetchRealTimeData: async (machineryId) => {
|
|
||||||
try {
|
|
||||||
set({ monitoringLoading: true })
|
|
||||||
const response = await machineryApi.getRealTimeData(machineryId)
|
|
||||||
set({
|
|
||||||
realTimeData: response.data,
|
|
||||||
monitoringLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取实时数据失败:', error)
|
|
||||||
set({ monitoringLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchMonitoringHistory: async (machineryId, params) => {
|
|
||||||
try {
|
|
||||||
set({ monitoringLoading: true })
|
|
||||||
const response = await machineryApi.getMonitoringHistory(machineryId, {
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 50,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
monitoringData: response.data.items,
|
|
||||||
monitoringLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取监控历史失败:', error)
|
|
||||||
set({ monitoringLoading: false })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
@@ -1,400 +1,129 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand';
|
||||||
import { FarmingTask, TaskTemplate, ResourceAllocation, Workflow } from '@api/modules/operation'
|
|
||||||
import { operationApi } from '@api'
|
|
||||||
import { QueryRequest } from '@api/types'
|
|
||||||
|
|
||||||
interface OperationState {
|
// 农事任务接口
|
||||||
// 任务数据
|
export interface FarmingTask {
|
||||||
taskList: FarmingTask[]
|
id: string;
|
||||||
selectedTask: FarmingTask | null
|
title: string;
|
||||||
taskLoading: boolean
|
description?: string | null;
|
||||||
taskError: string | null
|
status: 'pending' | 'in_progress' | 'completed' | 'cancelled';
|
||||||
taskPagination: {
|
priority: 'low' | 'medium' | 'high';
|
||||||
page: number
|
assigned_to?: string | null;
|
||||||
pageSize: number
|
due_date?: string | null;
|
||||||
total: number
|
created_at: string;
|
||||||
}
|
updated_at: string;
|
||||||
|
|
||||||
// 模板数据
|
|
||||||
templateList: TaskTemplate[]
|
|
||||||
selectedTemplate: TaskTemplate | null
|
|
||||||
templateLoading: boolean
|
|
||||||
|
|
||||||
// 资源分配数据
|
|
||||||
resourceAllocationList: ResourceAllocation[]
|
|
||||||
selectedResourceAllocation: ResourceAllocation | null
|
|
||||||
resourceLoading: boolean
|
|
||||||
|
|
||||||
// 工作流数据
|
|
||||||
workflowList: Workflow[]
|
|
||||||
selectedWorkflow: Workflow | null
|
|
||||||
workflowLoading: boolean
|
|
||||||
workflowPagination: {
|
|
||||||
page: number
|
|
||||||
pageSize: number
|
|
||||||
total: number
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OperationActions {
|
// 任务模板接口
|
||||||
// 任务操作
|
export interface TaskTemplate {
|
||||||
fetchTaskList: (params?: QueryRequest) => Promise<void>
|
id: string;
|
||||||
fetchTaskDetail: (id: string) => Promise<void>
|
name: string;
|
||||||
createTask: (data: Omit<FarmingTask, 'id' | 'createdAt' | 'updatedAt'>) => Promise<void>
|
description?: string | null;
|
||||||
updateTask: (id: string, data: Partial<FarmingTask>) => Promise<void>
|
task_type: string;
|
||||||
deleteTask: (id: string) => Promise<void>
|
estimated_duration?: number | null;
|
||||||
updateTaskStatus: (id: string, status: FarmingTask['status']) => Promise<void>
|
created_at: string;
|
||||||
setSelectedTask: (task: FarmingTask | null) => void
|
|
||||||
clearTaskError: () => void
|
|
||||||
|
|
||||||
// 模板操作
|
|
||||||
fetchTemplateList: (params?: QueryRequest) => Promise<void>
|
|
||||||
createTemplate: (data: Omit<TaskTemplate, 'id'>) => Promise<void>
|
|
||||||
updateTemplate: (id: string, data: Partial<TaskTemplate>) => Promise<void>
|
|
||||||
deleteTemplate: (id: string) => Promise<void>
|
|
||||||
|
|
||||||
// 资源分配操作
|
|
||||||
fetchResourceAllocations: (params?: QueryRequest) => Promise<void>
|
|
||||||
allocateResource: (data: Omit<ResourceAllocation, 'id' | 'allocatedDate'>) => Promise<void>
|
|
||||||
returnResource: (id: string) => Promise<void>
|
|
||||||
|
|
||||||
// 工作流操作
|
|
||||||
fetchWorkflowList: (params?: QueryRequest) => Promise<void>
|
|
||||||
fetchWorkflowDetail: (id: string) => Promise<void>
|
|
||||||
createWorkflow: (data: Omit<Workflow, 'id' | 'createdAt' | 'updatedAt'>) => Promise<void>
|
|
||||||
updateWorkflow: (id: string, data: Partial<Workflow>) => Promise<void>
|
|
||||||
deleteWorkflow: (id: string) => Promise<void>
|
|
||||||
setSelectedWorkflow: (workflow: Workflow | null) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useOperationStore = create<OperationState & OperationActions>((set, get) => ({
|
// 工作流接口
|
||||||
// 初始状态
|
export interface Workflow {
|
||||||
taskList: [],
|
id: string;
|
||||||
selectedTask: null,
|
name: string;
|
||||||
taskLoading: false,
|
description?: string | null;
|
||||||
taskError: null,
|
steps: WorkflowStep[];
|
||||||
taskPagination: {
|
status: 'active' | 'inactive' | 'draft';
|
||||||
page: 1,
|
created_at: string;
|
||||||
pageSize: 10,
|
updated_at: string;
|
||||||
total: 0
|
}
|
||||||
|
|
||||||
|
export interface WorkflowStep {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description?: string | null;
|
||||||
|
order: number;
|
||||||
|
estimated_duration?: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operation state interface
|
||||||
|
export interface OperationState {
|
||||||
|
tasks: FarmingTask[];
|
||||||
|
currentTask: FarmingTask | null;
|
||||||
|
taskTemplates: TaskTemplate[];
|
||||||
|
currentTemplate: TaskTemplate | null;
|
||||||
|
workflows: Workflow[];
|
||||||
|
currentWorkflow: Workflow | null;
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
setTasks: (tasks: FarmingTask[]) => void;
|
||||||
|
setCurrentTask: (task: FarmingTask | null) => void;
|
||||||
|
setTaskTemplates: (templates: TaskTemplate[]) => void;
|
||||||
|
setCurrentTemplate: (template: TaskTemplate | null) => void;
|
||||||
|
setWorkflows: (workflows: Workflow[]) => void;
|
||||||
|
setCurrentWorkflow: (workflow: Workflow | null) => void;
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
getTasks: () => FarmingTask[];
|
||||||
|
getCurrentTask: () => FarmingTask | null;
|
||||||
|
getTaskTemplates: () => TaskTemplate[];
|
||||||
|
getCurrentTemplate: () => TaskTemplate | null;
|
||||||
|
getWorkflows: () => Workflow[];
|
||||||
|
getCurrentWorkflow: () => Workflow | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Operation store
|
||||||
|
export const useOperationStore = create<OperationState>((set, get) => ({
|
||||||
|
tasks: [],
|
||||||
|
currentTask: null,
|
||||||
|
taskTemplates: [],
|
||||||
|
currentTemplate: null,
|
||||||
|
workflows: [],
|
||||||
|
currentWorkflow: null,
|
||||||
|
|
||||||
|
setTasks: (tasks: FarmingTask[]) => {
|
||||||
|
set({ tasks });
|
||||||
},
|
},
|
||||||
|
|
||||||
templateList: [],
|
setCurrentTask: (task: FarmingTask | null) => {
|
||||||
selectedTemplate: null,
|
set({ currentTask: task });
|
||||||
templateLoading: false,
|
|
||||||
|
|
||||||
resourceAllocationList: [],
|
|
||||||
selectedResourceAllocation: null,
|
|
||||||
resourceLoading: false,
|
|
||||||
|
|
||||||
workflowList: [],
|
|
||||||
selectedWorkflow: null,
|
|
||||||
workflowLoading: false,
|
|
||||||
workflowPagination: {
|
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
total: 0
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 任务操作
|
setTaskTemplates: (templates: TaskTemplate[]) => {
|
||||||
fetchTaskList: async (params) => {
|
set({ taskTemplates: templates });
|
||||||
try {
|
|
||||||
set({ taskLoading: true, taskError: null })
|
|
||||||
const response = await operationApi.getTaskList({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
|
|
||||||
set({
|
|
||||||
taskList: response.data.items,
|
|
||||||
taskPagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
taskLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
taskError: error instanceof Error ? error.message : '获取任务列表失败',
|
|
||||||
taskLoading: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchTaskDetail: async (id) => {
|
setCurrentTemplate: (template: TaskTemplate | null) => {
|
||||||
try {
|
set({ currentTemplate: template });
|
||||||
set({ taskLoading: true, taskError: null })
|
|
||||||
const response = await operationApi.getTaskDetail(id)
|
|
||||||
set({
|
|
||||||
selectedTask: response.data,
|
|
||||||
taskLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
taskError: error instanceof Error ? error.message : '获取任务详情失败',
|
|
||||||
taskLoading: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createTask: async (data) => {
|
setWorkflows: (workflows: Workflow[]) => {
|
||||||
try {
|
set({ workflows });
|
||||||
const response = await operationApi.createTask(data)
|
|
||||||
const currentList = get().taskList
|
|
||||||
set({
|
|
||||||
taskList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
taskError: error instanceof Error ? error.message : '创建任务失败'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateTask: async (id, data) => {
|
setCurrentWorkflow: (workflow: Workflow | null) => {
|
||||||
try {
|
set({ currentWorkflow: workflow });
|
||||||
const response = await operationApi.updateTask(id, data)
|
|
||||||
const currentList = get().taskList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
taskList: updatedList,
|
|
||||||
selectedTask: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
taskError: error instanceof Error ? error.message : '更新任务失败'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteTask: async (id) => {
|
getTasks: () => {
|
||||||
try {
|
return get().tasks;
|
||||||
await operationApi.deleteTask(id)
|
|
||||||
const currentList = get().taskList
|
|
||||||
const updatedList = currentList.filter(item => item.id !== id)
|
|
||||||
set({
|
|
||||||
taskList: updatedList,
|
|
||||||
selectedTask: null
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
taskError: error instanceof Error ? error.message : '删除任务失败'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateTaskStatus: async (id, status) => {
|
getCurrentTask: () => {
|
||||||
try {
|
return get().currentTask;
|
||||||
const response = await operationApi.updateTaskStatus(id, status)
|
|
||||||
const currentList = get().taskList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
taskList: updatedList,
|
|
||||||
selectedTask: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
set({
|
|
||||||
taskError: error instanceof Error ? error.message : '更新任务状态失败'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setSelectedTask: (task) => set({ selectedTask: task }),
|
getTaskTemplates: () => {
|
||||||
clearTaskError: () => set({ taskError: null }),
|
return get().taskTemplates;
|
||||||
|
|
||||||
// 模板操作
|
|
||||||
fetchTemplateList: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ templateLoading: true })
|
|
||||||
const response = await operationApi.getTaskTemplateList({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 50,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
templateList: response.data.items,
|
|
||||||
templateLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取模板列表失败:', error)
|
|
||||||
set({ templateLoading: false })
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createTemplate: async (data) => {
|
getCurrentTemplate: () => {
|
||||||
try {
|
return get().currentTemplate;
|
||||||
const response = await operationApi.createTaskTemplate(data)
|
|
||||||
const currentList = get().templateList
|
|
||||||
set({
|
|
||||||
templateList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建模板失败:', error)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateTemplate: async (id, data) => {
|
getWorkflows: () => {
|
||||||
try {
|
return get().workflows;
|
||||||
const response = await operationApi.updateTaskTemplate(id, data)
|
|
||||||
const currentList = get().templateList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
templateList: updatedList,
|
|
||||||
selectedTemplate: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('更新模板失败:', error)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteTemplate: async (id) => {
|
getCurrentWorkflow: () => {
|
||||||
try {
|
return get().currentWorkflow;
|
||||||
await operationApi.deleteTaskTemplate(id)
|
|
||||||
const currentList = get().templateList
|
|
||||||
const updatedList = currentList.filter(item => item.id !== id)
|
|
||||||
set({
|
|
||||||
templateList: updatedList,
|
|
||||||
selectedTemplate: null
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除模板失败:', error)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
// 资源分配操作
|
export default useOperationStore;
|
||||||
fetchResourceAllocations: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ resourceLoading: true })
|
|
||||||
const response = await operationApi.getResourceAllocations({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 20,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
resourceAllocationList: response.data.items,
|
|
||||||
resourceLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取资源分配列表失败:', error)
|
|
||||||
set({ resourceLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
allocateResource: async (data) => {
|
|
||||||
try {
|
|
||||||
const response = await operationApi.allocateResource(data)
|
|
||||||
const currentList = get().resourceAllocationList
|
|
||||||
set({
|
|
||||||
resourceAllocationList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('分配资源失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
returnResource: async (id) => {
|
|
||||||
try {
|
|
||||||
await operationApi.returnResource(id)
|
|
||||||
const currentList = get().resourceAllocationList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? { ...item, status: 'returned' as const } : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
resourceAllocationList: updatedList
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('归还资源失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 工作流操作
|
|
||||||
fetchWorkflowList: async (params) => {
|
|
||||||
try {
|
|
||||||
set({ workflowLoading: true })
|
|
||||||
const response = await operationApi.getWorkflowList({
|
|
||||||
page: params?.page || 1,
|
|
||||||
pageSize: params?.pageSize || 10,
|
|
||||||
...params
|
|
||||||
})
|
|
||||||
|
|
||||||
set({
|
|
||||||
workflowList: response.data.items,
|
|
||||||
workflowPagination: {
|
|
||||||
page: response.data.page,
|
|
||||||
pageSize: response.data.pageSize,
|
|
||||||
total: response.data.total
|
|
||||||
},
|
|
||||||
workflowLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取工作流列表失败:', error)
|
|
||||||
set({ workflowLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchWorkflowDetail: async (id) => {
|
|
||||||
try {
|
|
||||||
set({ workflowLoading: true })
|
|
||||||
const response = await operationApi.getWorkflowDetail(id)
|
|
||||||
set({
|
|
||||||
selectedWorkflow: response.data,
|
|
||||||
workflowLoading: false
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取工作流详情失败:', error)
|
|
||||||
set({ workflowLoading: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
createWorkflow: async (data) => {
|
|
||||||
try {
|
|
||||||
const response = await operationApi.createWorkflow(data)
|
|
||||||
const currentList = get().workflowList
|
|
||||||
set({
|
|
||||||
workflowList: [response.data, ...currentList]
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建工作流失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateWorkflow: async (id, data) => {
|
|
||||||
try {
|
|
||||||
const response = await operationApi.updateWorkflow(id, data)
|
|
||||||
const currentList = get().workflowList
|
|
||||||
const updatedList = currentList.map(item =>
|
|
||||||
item.id === id ? response.data : item
|
|
||||||
)
|
|
||||||
set({
|
|
||||||
workflowList: updatedList,
|
|
||||||
selectedWorkflow: response.data
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('更新工作流失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteWorkflow: async (id) => {
|
|
||||||
try {
|
|
||||||
await operationApi.deleteWorkflow(id)
|
|
||||||
const currentList = get().workflowList
|
|
||||||
const updatedList = currentList.filter(item => item.id !== id)
|
|
||||||
set({
|
|
||||||
workflowList: updatedList,
|
|
||||||
selectedWorkflow: null
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除工作流失败:', error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setSelectedWorkflow: (workflow) => set({ selectedWorkflow: workflow })
|
|
||||||
}))
|
|
||||||
@@ -25,6 +25,7 @@ export interface SystemSettings {
|
|||||||
dateFormat: string;
|
dateFormat: string;
|
||||||
timezone: string;
|
timezone: string;
|
||||||
language: string;
|
language: string;
|
||||||
|
defaultTheme?: 'light' | 'dark';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分类字典(树形结构)
|
// 分类字典(树形结构)
|
||||||
|
|||||||
110
src/utils/storage.ts
Normal file
110
src/utils/storage.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
* 安全的本地存储工具 - 解决SSR环境localStorage不存在的问题
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 安全的localStorage操作
|
||||||
|
export const safeLocalStorage = {
|
||||||
|
// 获取数据
|
||||||
|
getItem(key: string): string | null {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return localStorage.getItem(key);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to get item from localStorage:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 设置数据
|
||||||
|
setItem(key: string, value: string): void {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
localStorage.setItem(key, value);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to set item to localStorage:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除数据
|
||||||
|
removeItem(key: string): void {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to remove item from localStorage:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 清空所有数据
|
||||||
|
clear(): void {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
localStorage.clear();
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to clear localStorage:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 安全的sessionStorage操作
|
||||||
|
export const safeSessionStorage = {
|
||||||
|
// 获取数据
|
||||||
|
getItem(key: string): string | null {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return sessionStorage.getItem(key);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to get item from sessionStorage:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 设置数据
|
||||||
|
setItem(key: string, value: string): void {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
sessionStorage.setItem(key, value);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to set item to sessionStorage:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除数据
|
||||||
|
removeItem(key: string): void {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
sessionStorage.removeItem(key);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to remove item from sessionStorage:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 清空所有数据
|
||||||
|
clear(): void {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
sessionStorage.clear();
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to clear sessionStorage:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查是否在浏览器环境中
|
||||||
|
export const isBrowser = typeof window !== 'undefined';
|
||||||
271
src/utils/urlParams.ts
Normal file
271
src/utils/urlParams.ts
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
/**
|
||||||
|
* filekorolheader: URL参数处理工具函数 - 通用URL参数解析和优先级管理
|
||||||
|
* 功能:从浏览器URL读取参数、参数优先级处理、类型安全的参数转换
|
||||||
|
* 路径:/utils/urlParams
|
||||||
|
* 规范:遵循crop-x/docs/开发项目规范.md,TypeScript类型安全,泛型支持
|
||||||
|
*/
|
||||||
|
|
||||||
|
// URL参数解析配置接口
|
||||||
|
export interface UrlParamsConfig<T extends Record<string, any>> {
|
||||||
|
// 搜索字段配置,定义哪些字段需要从URL中读取
|
||||||
|
searchFields: {
|
||||||
|
[K in keyof T]?: {
|
||||||
|
type: 'string' | 'number' | 'boolean';
|
||||||
|
// URL参数名映射,如果不配置则使用字段名
|
||||||
|
urlKey?: string;
|
||||||
|
// 默认值
|
||||||
|
default?: T[K];
|
||||||
|
// 是否必需字段
|
||||||
|
required?: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
// 分页字段配置
|
||||||
|
paginationFields?: {
|
||||||
|
page?: {
|
||||||
|
urlKey?: string;
|
||||||
|
default?: number;
|
||||||
|
};
|
||||||
|
size?: {
|
||||||
|
urlKey?: string;
|
||||||
|
default?: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL参数解析结果接口
|
||||||
|
export interface UrlParamsResult<T extends Record<string, any>> {
|
||||||
|
// 搜索参数
|
||||||
|
searchParams: Partial<T>;
|
||||||
|
// 分页参数
|
||||||
|
paginationParams: {
|
||||||
|
page: number;
|
||||||
|
size: number;
|
||||||
|
};
|
||||||
|
// 是否包含任何URL参数
|
||||||
|
hasUrlParams: boolean;
|
||||||
|
// 原始URL参数字符串
|
||||||
|
rawUrlParams: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用URL参数解析函数
|
||||||
|
*
|
||||||
|
* @param config URL参数配置
|
||||||
|
* @returns 解析后的URL参数结果
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* interface MyParams {
|
||||||
|
* search: string;
|
||||||
|
* status: string;
|
||||||
|
* category: string;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* const config: UrlParamsConfig<MyParams> = {
|
||||||
|
* searchFields: {
|
||||||
|
* search: { type: 'string', urlKey: 'search', default: '' },
|
||||||
|
* status: { type: 'string', urlKey: 'audit_status', default: 'all' },
|
||||||
|
* category: { type: 'string', default: 'all' }
|
||||||
|
* },
|
||||||
|
* paginationFields: {
|
||||||
|
* page: { urlKey: 'page', default: 1 },
|
||||||
|
* size: { urlKey: 'size', default: 10 }
|
||||||
|
* }
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* const result = parseUrlParams(config);
|
||||||
|
* console.log(result.searchParams); // { search: 'keyword', status: 'pending', category: 'all' }
|
||||||
|
* console.log(result.paginationParams); // { page: 2, size: 20 }
|
||||||
|
*/
|
||||||
|
export function parseUrlParams<T extends Record<string, any>>(
|
||||||
|
config: UrlParamsConfig<T>
|
||||||
|
): UrlParamsResult<T> {
|
||||||
|
// 检查是否在浏览器环境
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return {
|
||||||
|
searchParams: {} as Partial<T>,
|
||||||
|
paginationParams: {
|
||||||
|
page: config.paginationFields?.page?.default ?? 1,
|
||||||
|
size: config.paginationFields?.size?.default ?? 10
|
||||||
|
},
|
||||||
|
hasUrlParams: false,
|
||||||
|
rawUrlParams: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const rawUrlParams = urlParams.toString();
|
||||||
|
|
||||||
|
const searchParams: Partial<T> = {};
|
||||||
|
let hasUrlParams = rawUrlParams !== '';
|
||||||
|
|
||||||
|
// 解析搜索字段
|
||||||
|
Object.entries(config.searchFields).forEach(([fieldKey, fieldConfig]) => {
|
||||||
|
if (!fieldConfig) return;
|
||||||
|
|
||||||
|
const urlKey = fieldConfig.urlKey || fieldKey;
|
||||||
|
const rawValue = urlParams.get(urlKey);
|
||||||
|
|
||||||
|
if (rawValue !== null) {
|
||||||
|
let parsedValue: any = rawValue;
|
||||||
|
|
||||||
|
// 类型转换
|
||||||
|
switch (fieldConfig.type) {
|
||||||
|
case 'number':
|
||||||
|
parsedValue = parseInt(rawValue, 10);
|
||||||
|
if (isNaN(parsedValue)) {
|
||||||
|
parsedValue = fieldConfig.default;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'boolean':
|
||||||
|
parsedValue = rawValue === 'true' || rawValue === '1';
|
||||||
|
break;
|
||||||
|
case 'string':
|
||||||
|
default:
|
||||||
|
parsedValue = rawValue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
(searchParams as any)[fieldKey] = parsedValue;
|
||||||
|
} else if (fieldConfig.default !== undefined) {
|
||||||
|
(searchParams as any)[fieldKey] = fieldConfig.default;
|
||||||
|
} else if (fieldConfig.required) {
|
||||||
|
console.warn(`Required URL parameter '${urlKey}' is missing`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 解析分页字段
|
||||||
|
const pageValue = urlParams.get(config.paginationFields?.page?.urlKey || 'page');
|
||||||
|
const sizeValue = urlParams.get(config.paginationFields?.size?.urlKey || 'size');
|
||||||
|
|
||||||
|
const page = pageValue ? Math.max(1, parseInt(pageValue, 10) || 1) : (config.paginationFields?.page?.default ?? 1);
|
||||||
|
const size = sizeValue ? Math.max(1, parseInt(sizeValue, 10) || 10) : (config.paginationFields?.size?.default ?? 10);
|
||||||
|
|
||||||
|
const paginationParams = { page, size };
|
||||||
|
|
||||||
|
return {
|
||||||
|
searchParams,
|
||||||
|
paginationParams,
|
||||||
|
hasUrlParams,
|
||||||
|
rawUrlParams
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse URL parameters:', error);
|
||||||
|
|
||||||
|
return {
|
||||||
|
searchParams: {} as Partial<T>,
|
||||||
|
paginationParams: {
|
||||||
|
page: config.paginationFields?.page?.default ?? 1,
|
||||||
|
size: config.paginationFields?.size?.default ?? 10
|
||||||
|
},
|
||||||
|
hasUrlParams: false,
|
||||||
|
rawUrlParams: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数优先级合并函数
|
||||||
|
* 优先级:URL参数 > 函数传入参数 > 默认状态
|
||||||
|
*
|
||||||
|
* @param urlParams 从URL解析的参数
|
||||||
|
* @param functionParams 函数传入的参数
|
||||||
|
* @param defaultState 默认状态
|
||||||
|
* @returns 合并后的参数
|
||||||
|
*/
|
||||||
|
export function mergeParamsWithPriority<T extends Record<string, any>>(
|
||||||
|
urlParams: Partial<T>,
|
||||||
|
functionParams: Partial<T> = {},
|
||||||
|
defaultState: T
|
||||||
|
): T {
|
||||||
|
const result = { ...defaultState };
|
||||||
|
|
||||||
|
// 先合并函数参数(第二优先级)
|
||||||
|
Object.keys(result).forEach(key => {
|
||||||
|
if (functionParams[key as keyof T] !== undefined) {
|
||||||
|
(result as any)[key] = functionParams[key as keyof T];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 再合并URL参数(第一优先级)
|
||||||
|
Object.keys(urlParams).forEach(key => {
|
||||||
|
if (urlParams[key as keyof T] !== undefined && urlParams[key as keyof T] !== '') {
|
||||||
|
(result as any)[key] = urlParams[key as keyof T];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页参数优先级合并函数
|
||||||
|
*
|
||||||
|
* @param urlPagination 从URL解析的分页参数
|
||||||
|
* @param functionPagination 函数传入的分页参数
|
||||||
|
* @param defaultPagination 默认分页状态
|
||||||
|
* @param resetPage 是否重置到第一页
|
||||||
|
* @returns 合并后的分页参数
|
||||||
|
*/
|
||||||
|
export function mergePaginationWithPriority(
|
||||||
|
urlPagination: { page: number; size: number },
|
||||||
|
functionPagination: { page?: number; size?: number } = {},
|
||||||
|
defaultPagination: { page: number; size: number },
|
||||||
|
resetPage: boolean = false
|
||||||
|
): { page: number; size: number } {
|
||||||
|
const page = resetPage
|
||||||
|
? 1
|
||||||
|
: (urlPagination.page || functionPagination.page || defaultPagination.page);
|
||||||
|
|
||||||
|
const size = urlPagination.size || functionPagination.size || defaultPagination.size;
|
||||||
|
|
||||||
|
return { page: Math.max(1, page), size: Math.max(1, size) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核历史页面专用的URL参数配置
|
||||||
|
*/
|
||||||
|
export const AUDIT_HISTORY_URL_CONFIG: UrlParamsConfig<{
|
||||||
|
search: string;
|
||||||
|
action: string;
|
||||||
|
audit_status: string;
|
||||||
|
date_range: string;
|
||||||
|
}> = {
|
||||||
|
searchFields: {
|
||||||
|
search: { type: 'string', default: '' },
|
||||||
|
action: { type: 'string', urlKey: 'action', default: 'all' },
|
||||||
|
audit_status: { type: 'string', urlKey: 'audit_status', default: 'all' },
|
||||||
|
date_range: { type: 'string', urlKey: 'date_range', default: 'all' }
|
||||||
|
},
|
||||||
|
paginationFields: {
|
||||||
|
page: { urlKey: 'page', default: 1 },
|
||||||
|
size: { urlKey: 'size', default: 10 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企业审核页面专用的URL参数配置
|
||||||
|
*/
|
||||||
|
export const ENTERPRISE_AUDIT_URL_CONFIG: UrlParamsConfig<{
|
||||||
|
search: string;
|
||||||
|
audit_status: string;
|
||||||
|
}> = {
|
||||||
|
searchFields: {
|
||||||
|
search: { type: 'string', default: '' },
|
||||||
|
audit_status: { type: 'string', urlKey: 'audit_status', default: 'all' }
|
||||||
|
},
|
||||||
|
paginationFields: {
|
||||||
|
page: { urlKey: 'page', default: 1 },
|
||||||
|
size: { urlKey: 'size', default: 10 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认URL参数配置(通用)
|
||||||
|
*/
|
||||||
|
export const DEFAULT_URL_CONFIG: UrlParamsConfig<Record<string, any>> = {
|
||||||
|
searchFields: {},
|
||||||
|
paginationFields: {
|
||||||
|
page: { urlKey: 'page', default: 1 },
|
||||||
|
size: { urlKey: 'size', default: 10 }
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -73,12 +73,13 @@
|
|||||||
".next/types/**/*.ts",
|
".next/types/**/*.ts",
|
||||||
".next/dev/types/**/*.ts"
|
".next/dev/types/**/*.ts"
|
||||||
],
|
],
|
||||||
"paths": {
|
|
||||||
"@/*": [
|
|
||||||
"./src/*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules",
|
||||||
|
"src/app/(app)/land-information/**",
|
||||||
|
"src/app/(app)/ai-crop-model/**",
|
||||||
|
"src/app/(app)/central-config/**",
|
||||||
|
"src/components/**",
|
||||||
|
"**/*.ts",
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user