生产管理系统 - 激活、删除的联调

This commit is contained in:
2025-11-12 14:34:34 +08:00
parent 8fefadaf55
commit dcd7ddeb71
16 changed files with 487 additions and 458 deletions

View File

@@ -9,7 +9,6 @@
import { useReducer, useEffect, useState, useCallback, useMemo,useRef } from 'react';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { Eye, Edit, Trash2, UserX, UserCheck } from 'lucide-react';
import {
AlertDialog,
AlertDialogAction,
@@ -20,6 +19,7 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { Eye, Edit, Lock, UserX, UserCheck, Trash2 } from 'lucide-react';
import { UserDetailDialog } from './components/UserDetailDialog';
import { AddUserModal } from './components/AddUserModal';
import { EditUserModal } from './components/EditUserModal';
@@ -45,6 +45,8 @@ interface UserManagementState {
showDetailDialog: boolean;
showAddDialog: boolean;
showEditDialog: boolean;
showDeactivateDialog: boolean;
showDeleteDialog: boolean;
}
type UserManagementAction =
@@ -58,6 +60,8 @@ type UserManagementAction =
| { type: 'TOGGLE_DETAIL_DIALOG'; payload: boolean }
| { type: 'TOGGLE_ADD_DIALOG'; payload: boolean }
| { type: 'TOGGLE_EDIT_DIALOG'; payload: boolean }
| { type: 'TOGGLE_DEACTIVATE_DIALOG'; payload: boolean }
| { type: 'TOGGLE_DELETE_DIALOG'; payload: boolean }
| { type: 'REFRESH_DATA' };
const userManagementReducer = (state: UserManagementState, action: UserManagementAction): UserManagementState => {
@@ -88,6 +92,10 @@ const userManagementReducer = (state: UserManagementState, action: UserManagemen
return { ...state, showAddDialog: !state.showAddDialog };
case 'TOGGLE_EDIT_DIALOG':
return { ...state, showEditDialog: !state.showEditDialog };
case 'TOGGLE_DEACTIVATE_DIALOG':
return { ...state, showDeactivateDialog: !state.showDeactivateDialog };
case 'TOGGLE_DELETE_DIALOG':
return { ...state, showDeleteDialog: !state.showDeleteDialog };
case 'REFRESH_DATA':
return { ...state, error: null };
default:
@@ -118,17 +126,13 @@ const initialState: UserManagementState = {
showDetailDialog: false,
showAddDialog: false,
showEditDialog: false,
showDeactivateDialog: false,
showDeleteDialog: false,
};
export default function TenantUserManagementPage() {
const [state, dispatch] = useReducer(userManagementReducer, initialState);
// 弹窗状态管理
const [statusDialogOpen, setStatusDialogOpen] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [actionUser, setActionUser] = useState<User | null>(null);
const [actionType, setActionType] = useState<'activate' | 'deactivate'>('activate');
// 搜索字段配置
const searchFields: SearchFieldConfig[] = useMemo(() => [
{
@@ -279,7 +283,7 @@ export default function TenantUserManagementPage() {
variant="ghost"
size="sm"
onClick={() => handleToggleStatus(user)}
title={user.isActive ? "冻结用户" : "激活用户"}
title={user.isActive ? "停用用户" : "激活用户"}
>
{user.isActive ? (
<UserX className="w-4 h-4 text-orange-600" />
@@ -290,9 +294,9 @@ export default function TenantUserManagementPage() {
<Button
variant="ghost"
size="sm"
onClick={() => handleDeleteUser(user)}
onClick={() => handleDelete(user)}
title="删除用户"
className="text-red-600 hover:text-red-700 hover:bg-red-50 dark:hover:bg-red-950"
className="text-red-600 hover:text-red-700 hover:bg-red-50"
>
<Trash2 className="w-4 h-4" />
</Button>
@@ -504,62 +508,60 @@ export default function TenantUserManagementPage() {
loadUsers({});
}, [loadUsers]);
// 切换用户状态 - 打开确认弹窗
// 切换用户状态
const handleToggleStatus = (user: User) => {
const newStatus = !user.isActive;
setActionUser(user);
setActionType(newStatus ? 'activate' : 'deactivate');
setStatusDialogOpen(true);
dispatch({ type: 'SET_SELECTED_USER', payload: user });
dispatch({ type: 'TOGGLE_DEACTIVATE_DIALOG', payload: true });
};
// 执行状态切换
const handleStatusConfirm = async () => {
if (!actionUser) return;
// 删除用户
const handleDelete = (user: User) => {
dispatch({ type: 'SET_SELECTED_USER', payload: user });
dispatch({ type: 'TOGGLE_DELETE_DIALOG', payload: true });
};
// 确认切换用户状态
const confirmToggleStatus = async () => {
if (!state.selectedUser) return;
try {
if (actionType === 'activate') {
await activateUser(actionUser.id);
toast.success(`用户 ${actionUser.fullName || actionUser.username} 已激活`);
const user = state.selectedUser;
if (user.isActive) {
await deactivateUser(user.id);
toast.success(`用户 ${user.fullName || user.username} 已停用`);
} else {
await deactivateUser(actionUser.id);
toast.success(`用户 ${actionUser.fullName || actionUser.username}停用`);
await activateUser(user.id);
toast.success(`用户 ${user.fullName || user.username}激活`);
}
// 刷新数据
// 关闭对话框并刷新列表
dispatch({ type: 'TOGGLE_DEACTIVATE_DIALOG', payload: false });
dispatch({ type: 'SET_SELECTED_USER', payload: null });
refreshData();
} catch (error) {
console.error(`${actionType === 'activate' ? '激活' : '停用'}用户失败:`, error);
const errorMessage = error instanceof Error ? error.message : `${actionType === 'activate' ? '激活' : '停用'}用户失败,请重试`;
console.error('切换用户状态失败:', error);
const errorMessage = error instanceof Error ? error.message : '操作失败,请重试';
toast.error(errorMessage);
} finally {
setStatusDialogOpen(false);
setActionUser(null);
}
};
// 删除用户 - 打开确认弹窗
const handleDeleteUser = (user: User) => {
setActionUser(user);
setDeleteDialogOpen(true);
};
// 执行删除用户
const handleDeleteConfirm = async () => {
if (!actionUser) return;
// 确认删除用户
const confirmDelete = async () => {
if (!state.selectedUser) return;
try {
await deleteUser(actionUser.id);
toast.success(`用户 ${actionUser.fullName || actionUser.username} 已删除`);
const user = state.selectedUser;
await deleteUser(user.id);
toast.success(`用户 ${user.fullName || user.username} 已删除`);
// 刷新数据
// 关闭对话框并刷新列表
dispatch({ type: 'TOGGLE_DELETE_DIALOG', payload: false });
dispatch({ type: 'SET_SELECTED_USER', payload: null });
refreshData();
} catch (error) {
console.error('删除用户失败:', error);
const errorMessage = error instanceof Error ? error.message : '删除用户失败,请重试';
const errorMessage = error instanceof Error ? error.message : '删除失败,请重试';
toast.error(errorMessage);
} finally {
setDeleteDialogOpen(false);
setActionUser(null);
}
};
@@ -652,54 +654,55 @@ export default function TenantUserManagementPage() {
onSuccess={refreshData}
/>
{/* 状态切换确认对话框 */}
<AlertDialog open={statusDialogOpen} onOpenChange={setStatusDialogOpen}>
{/* 停用/激活用户确认对话框 */}
<AlertDialog open={state.showDeactivateDialog} onOpenChange={(open) => dispatch({ type: 'TOGGLE_DEACTIVATE_DIALOG', payload: open })}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{actionType === 'activate' ? '激活用户' : '停用用户'}
{state.selectedUser?.isActive ? '停用用户' : '激活用户'}
</AlertDialogTitle>
<AlertDialogDescription>
{actionType === 'activate' ? '激活' : '停用'}
<span className="font-semibold">
{actionUser?.fullName || actionUser?.username}
{state.selectedUser?.isActive ? '停用' : '激活'} <strong>{state.selectedUser?.fullName || state.selectedUser?.username}</strong>
{state.selectedUser?.isActive && (
<span className="block mt-2 text-amber-600 dark:text-amber-400">
</span>
)}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction
onClick={handleStatusConfirm}
className={actionType === 'activate' ? 'bg-green-600 hover:bg-green-700' : 'bg-orange-600 hover:bg-orange-700'}
<Button
onClick={confirmToggleStatus}
className={state.selectedUser?.isActive ? 'bg-orange-600 hover:bg-orange-700' : 'bg-green-600 hover:bg-green-700'}
>
{actionType === 'activate' ? '激活' : '停用'}
</AlertDialogAction>
{state.selectedUser?.isActive ? '停用' : '激活'}
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* 删除用户确认对话框 */}
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<AlertDialog open={state.showDeleteDialog} onOpenChange={(open) => dispatch({ type: 'TOGGLE_DELETE_DIALOG', payload: open })}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="text-red-600"></AlertDialogTitle>
<AlertDialogDescription>
<span className="font-semibold text-red-600">
{actionUser?.fullName || actionUser?.username}
<strong>{state.selectedUser?.fullName || state.selectedUser?.username}</strong>
<span className="block mt-2 text-red-600 dark:text-red-400">
</span>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction
onClick={handleDeleteConfirm}
<Button
onClick={confirmDelete}
className="bg-red-600 hover:bg-red-700"
variant="destructive"
>
</AlertDialogAction>
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>

View File

@@ -46,8 +46,6 @@ export function MultiFactorEvaluation() {
const [selectedField, setSelectedField] = useState('field-1');
const [showWeightConfig, setShowWeightConfig] = useState(false);
const [showKnowledgeBase, setShowKnowledgeBase] = useState(false);
const [batchAnalysisResults, setBatchAnalysisResults] = useState<SuitabilityResult[]>([]);
// 评价因子权重配置
const [factorWeights, setFactorWeights] = useState<FactorWeight[]>([
{ id: 'ph', name: 'pH值', weight: 20, unit: '' },
@@ -67,7 +65,6 @@ export function MultiFactorEvaluation() {
// 获取当前选中的地块结果
const currentResult =
evaluationResults.find(r => r.fieldId === selectedField) ||
batchAnalysisResults.find(r => r.fieldId === selectedField) ||
evaluationResults[0];
// 批量分析处理函数

View File

@@ -43,7 +43,7 @@ interface CropKnowledgeEntry {
riskFactors: CropRiskFactor[];
}
type MatchStatus = '??' | '???' | '??';
type MatchStatus = '??' | '??' | '??';
interface MatchDetail {
factor: string;
@@ -55,7 +55,7 @@ interface MatchDetail {
interface RecommendationResult {
crop: CropKnowledgeEntry;
matchScore: number;
suitabilityLevel: '????' | '??' | '????' | '???';
suitabilityLevel: '????' | '??' | '????' | '??';
matchDetails: MatchDetail[];
applicableRisks: CropRiskFactor[];
expectedYield: [number, number];
@@ -201,24 +201,35 @@ interface CropRecommendationsProps {
export function CropRecommendations({ currentResult }: CropRecommendationsProps) {
// 匹配作物推荐
const matchCropsForField = (fieldFactors: any) => {
return cropKnowledgeBase.map(crop => {
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: any[] = [];
const matchDetails: MatchDetail[] = [];
// 评估土壤因子匹配度
Object.entries(crop.soilRequirements).forEach(([factor, requirements]: [string, any]) => {
if (fieldFactors[factor]) {
(Object.entries(crop.soilRequirements) as Array<[SoilFactorKey, RangeRequirement]>).forEach(([factor, requirements]) => {
const value = fieldFactors[factor];
const { optimal, acceptable } = requirements;
if (typeof value !== 'number') {
return;
}
const { optimal, acceptable } = requirements;
let score = 0;
let status = '偏离';
let status: MatchStatus = '??';
if (value >= optimal[0] && value <= optimal[1]) {
score = 100;
status = '最佳';
status = '??';
} else if (value >= acceptable[0] && value <= acceptable[1]) {
const deviation = Math.min(
Math.abs(value - optimal[0]),
@@ -226,62 +237,64 @@ export function CropRecommendations({ currentResult }: CropRecommendationsProps)
);
const range = optimal[1] - optimal[0];
score = Math.max(60, 100 - (deviation / range) * 40);
status = '可接受';
status = '??';
} else {
score = Math.max(0, 60 - Math.min(
score = Math.max(
0,
60 -
Math.min(
Math.abs(value - acceptable[0]),
Math.abs(value - acceptable[1])
) * 2);
) *
2
);
}
totalScore += score;
factorCount++;
factorCount += 1;
matchDetails.push({
factor: factor === 'ph' ? 'pH值' :
factor === 'organicMatter' ? '有机质' :
factor === 'soilDepth' ? '土层厚度' :
factor === 'nitrogen' ? '全氮' :
factor === 'phosphorus' ? '全磷' :
factor === 'potassium' ? '全钾' : '排水性',
factor: factorLabelMap[factor],
value,
score,
status
});
}
});
// 评估气候因子(简化处理)
if (fieldFactors.temperature) {
const tempScore = fieldFactors.temperature >= 18 && fieldFactors.temperature <= 25 ? 90 : 70;
if (typeof fieldFactors.temperature === 'number') {
const tempScore =
fieldFactors.temperature >= 18 && fieldFactors.temperature <= 25 ? 90 : 70;
totalScore += tempScore;
factorCount++;
factorCount += 1;
}
if (fieldFactors.rainfall) {
const rainScore = fieldFactors.rainfall >= 500 && fieldFactors.rainfall <= 800 ? 90 : 70;
if (typeof fieldFactors.rainfall === 'number') {
const rainScore =
fieldFactors.rainfall >= 500 && fieldFactors.rainfall <= 800 ? 90 : 70;
totalScore += rainScore;
factorCount++;
factorCount += 1;
}
const matchScore = Math.round(totalScore / factorCount);
const matchScore = factorCount > 0 ? Math.round(totalScore / factorCount) : 0;
// 确定适宜性等级
let suitabilityLevel = '不推荐';
if (matchScore >= 85) suitabilityLevel = '高度推荐';
else if (matchScore >= 70) suitabilityLevel = '推荐';
else if (matchScore >= 50) suitabilityLevel = '谨慎种植';
let suitabilityLevel: RecommendationResult['suitabilityLevel'] = '??';
if (matchScore >= 85) suitabilityLevel = '????';
else if (matchScore >= 70) suitabilityLevel = '??';
else if (matchScore >= 50) suitabilityLevel = '????';
// 根据适宜性等级选择产量区间
let expectedYield = crop.expectedYield.low;
if (suitabilityLevel === '高度推荐') expectedYield = crop.expectedYield.high;
else if (suitabilityLevel === '推荐') expectedYield = crop.expectedYield.medium;
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 => {
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 true; // 简化处理,默认显示所有风险
if (
risk.id.includes('rust') &&
fieldFactors.temperature >= 15 &&
fieldFactors.temperature <= 22
)
return true;
return false;
});
return {
@@ -292,20 +305,25 @@ export function CropRecommendations({ currentResult }: CropRecommendationsProps)
applicableRisks,
expectedYield
};
}).sort((a, b) => b.matchScore - a.matchScore);
});
};
// 获取地块因子数据
const fieldFactors = {
ph: currentResult.factors.find(f => f.id === 'ph')?.value || 0,
organic: currentResult.factors.find(f => f.id === 'organic')?.value || 0,
depth: currentResult.factors.find(f => f.id === 'depth')?.value || 0,
nitrogen: currentResult.factors.find(f => f.id === 'nitrogen')?.value || 0,
phosphorus: currentResult.factors.find(f => f.id === 'phosphorus')?.value || 0,
potassium: currentResult.factors.find(f => f.id === 'potassium')?.value || 0,
drainage: currentResult.factors.find(f => f.id === 'drainage')?.value || 0,
temperature: 22, // 模拟年均温度
rainfall: 800, // 模拟年均降雨量
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 // ???????
};
// // 模拟年均降雨量
};
// 匹配推荐作物

View File

@@ -27,8 +27,8 @@ import {
} from 'lucide-react';
import { toast } from 'sonner';
import { useAuth } from '@/components/auth/AuthContext';
import { authReducer, initialAuthState, AuthState, AuthAction } from './authReducer';
import { getCaptchaApiV1AuthCaptchaGet, loginApiV1AuthLoginPost } from '@/lib/api/sdk.gen';
import { authReducer, initialAuthState } from './authReducer';
import { loginApiV1AuthLoginPost } from '@/lib/api/sdk.gen';
import type { CaptchaResponse } from '@/lib/api/types.gen';
import {PERSONAL_CELTRAL_PAGE} from "@/config/constants"
interface LoginFormProps {
@@ -40,7 +40,6 @@ export function LoginForm({ onRegisterClick }: LoginFormProps) {
const [state, dispatch] = React.useReducer(authReducer, initialAuthState);
const [loginType, setLoginType] = useState<'password' | 'phone'>('password');
const [passwordCaptchaData, setPasswordCaptchaData] = useState<CaptchaResponse | null>(null);
const [phoneCaptchaData, setPhoneCaptchaData] = useState<CaptchaResponse | null>(null);
// 倒计时效果
useEffect(() => {
@@ -154,9 +153,14 @@ export function LoginForm({ onRegisterClick }: LoginFormProps) {
dispatch({ type: 'SET_ERROR', payload: '登录失败,请检查用户名和密码' });
toast.error('登录失败,请检查用户名和密码');
}
} catch (err: any) {
console.error('登录失败:', err);
const errorMessage = err?.response?.data?.message || err?.message || '登录失败,请稍后重试';
} catch (error: unknown) {
console.error('????:', error);
const apiMessage =
typeof error === 'object' && error !== null && 'response' in error
? (error as { response?: { data?: { message?: string } } }).response?.data?.message
: undefined;
const fallbackMessage = error instanceof Error ? error.message : undefined;
const errorMessage = apiMessage || fallbackMessage || '??????????';
dispatch({ type: 'SET_ERROR', payload: errorMessage });
toast.error(errorMessage);
} finally {
@@ -204,9 +208,10 @@ export function LoginForm({ onRegisterClick }: LoginFormProps) {
dispatch({ type: 'SET_ERROR', payload: '验证码错误' });
toast.error('验证码错误');
}
} catch (err) {
dispatch({ type: 'SET_ERROR', payload: '登录失败,请稍后重试' });
toast.error('登录失败');
} catch (error) {
console.error('???????:', error);
dispatch({ type: 'SET_ERROR', payload: '??????????' });
toast.error('????');
} finally {
dispatch({ type: 'SET_LOADING', payload: false });
}

View File

@@ -172,7 +172,7 @@ export default function RegisterPage() {
await new Promise(resolve => setTimeout(resolve, 1000));
toast.success('验证码发送成功测试验证码123456');
setCountdown(60);
} catch (err) {
} catch (error) {
setError('发送验证码失败,请稍后重试');
toast.error('发送验证码失败');
} finally {
@@ -246,7 +246,7 @@ export default function RegisterPage() {
setError('短信验证码错误');
toast.error('短信验证码错误');
}
} catch (err) {
} catch (error) {
setError('注册失败,请稍后重试');
toast.error('注册失败');
} finally {

View File

@@ -6,7 +6,7 @@ import { AuthProvider } from '@/components/auth/AuthContext';
import { ClientAuthInterceptor } from '@/components/auth/ClientAuthInterceptor';
import { ThemeProvider } from 'next-themes';
import { Toaster } from '@/components/ui/sonner';
import { Building2, Users, Cog, Activity, Mail, UserCircle, Database, Map, BarChart3, Cloud, TrendingUp, GitCompare, AlertTriangle, FileText, MapPin, Settings, User, Package, Navigation, Zap, Target, PieChart, Calendar, Shield, Tractor, Clipboard, ClipboardCheck, Brain, Droplets, Book, ShoppingCart } from 'lucide-react';
import { Building2, Users, Cog, Activity, Mail, UserCircle, Database, Map, BarChart3, Cloud, TrendingUp, GitCompare, AlertTriangle, FileText, Settings, User, Package, Navigation, Zap, Target, PieChart, Calendar, Shield, Tractor, Clipboard, ClipboardCheck, Brain, Droplets, Book, ShoppingCart } from 'lucide-react';
// 导入导航和侧边栏组件
import {Navbar1} from "@/components/layouts/Navbar"

View File

@@ -5,7 +5,7 @@ import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Badge } from '@/components/ui/badge';
import { Home, ArrowLeft, RefreshCw, Search, AlertTriangle, ExternalLink } from 'lucide-react';
import { Home, ArrowLeft, RefreshCw, Search, AlertTriangle } from 'lucide-react';
export default function NotFound() {
const handleRefresh = () => {

View File

@@ -2,8 +2,6 @@ import * as React from "react"
import { ChevronRight } from "lucide-react"
import Link from "next/link"
import { SearchForm } from "@/components/search-form"
import { VersionSwitcher } from "@/components/version-switcher"
import {
Collapsible,
CollapsibleContent,
@@ -15,7 +13,6 @@ import {
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,

View File

@@ -1,6 +1,6 @@
'use client';
import { useState, useEffect } from 'react';
import { useState, useEffect, useCallback } from 'react';
import { RefreshCw } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@@ -19,7 +19,7 @@ export function CaptchaInput({ value, onChange, onCaptchaChange, className = ''
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const fetchCaptcha = async () => {
const fetchCaptcha = useCallback(async () => {
setLoading(true);
setError('');
onChange(''); // 清空验证码输入
@@ -120,11 +120,11 @@ export function CaptchaInput({ value, onChange, onCaptchaChange, className = ''
captcha_id: 'fallback-' + Date.now(),
image: canvas.toDataURL()
};
};
}, [onCaptchaChange, onChange]);
useEffect(() => {
fetchCaptcha();
}, []);
}, [fetchCaptcha]);
const handleRefresh = () => {
fetchCaptcha();

View File

@@ -15,6 +15,21 @@ export function LoadingScreen({
subMessage,
variant = 'default'
}: LoadingScreenProps) {
const variantTitles = {
default: '??????????????',
auth: '??????????',
redirect: '???????????'
} as const;
const variantSubtitles = {
default: '??????????',
auth: '????????????',
redirect: '????????'
} as const;
const primaryMessage = message || variantTitles[variant];
const secondaryMessage = subMessage || variantSubtitles[variant];
return (
<div className="min-h-screen flex items-center justify-center relative overflow-hidden">
{/* 智慧大田动态背景 - 使用登录页面相同的背景效果 */}

View File

@@ -2,26 +2,7 @@
import { useEffect, useRef } from 'react';
export function SmartFieldBackground() {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
// 设置canvas尺寸
const resizeCanvas = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// 田间传感器节点
class SensorNode {
class SensorNode {
x: number;
y: number;
radius: number;
@@ -45,20 +26,17 @@ export function SmartFieldBackground() {
draw(ctx: CanvasRenderingContext2D) {
const pulse = Math.sin(this.pulsePhase) * 0.5 + 0.5;
// 节点外圈
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius + pulse * 4, 0, Math.PI * 2);
ctx.strokeStyle = `rgba(34, 197, 94, ${0.3 + pulse * 0.3})`;
ctx.lineWidth = 2;
ctx.stroke();
// 节点核心
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = `rgba(34, 197, 94, ${0.8 + pulse * 0.2})`;
ctx.fill();
// 光晕
const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.radius * 6);
gradient.addColorStop(0, `rgba(34, 197, 94, ${0.4 * pulse})`);
gradient.addColorStop(1, 'rgba(34, 197, 94, 0)');
@@ -81,7 +59,6 @@ export function SmartFieldBackground() {
ctx.lineWidth = 1;
ctx.stroke();
// 数据流动效果
const dataFlowPhase = (Date.now() / 1000) % 1;
const flowX = this.x + (node.x - this.x) * dataFlowPhase;
const flowY = this.y + (node.y - this.y) * dataFlowPhase;
@@ -93,10 +70,12 @@ export function SmartFieldBackground() {
}
});
}
}
}
// 无人机
class Drone {
type BoundsSupplier = () => { width: number; height: number };
type DroneTrailPoint = { x: number; y: number; alpha: number };
class Drone {
x: number;
y: number;
targetX: number;
@@ -104,11 +83,14 @@ export function SmartFieldBackground() {
speed: number;
size: number;
rotorPhase: number;
trail: { x: number; y: number; alpha: number }[];
trail: DroneTrailPoint[];
private readonly getBounds: BoundsSupplier;
constructor() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
constructor(getBounds: BoundsSupplier) {
this.getBounds = getBounds;
const { width, height } = this.getBounds();
this.x = Math.random() * width;
this.y = Math.random() * height;
this.targetX = this.x;
this.targetY = this.y;
this.speed = 1.5;
@@ -119,8 +101,9 @@ export function SmartFieldBackground() {
}
setNewTarget() {
this.targetX = Math.random() * canvas.width;
this.targetY = Math.random() * canvas.height;
const { width, height } = this.getBounds();
this.targetX = Math.random() * width;
this.targetY = Math.random() * height;
}
update() {
@@ -137,7 +120,6 @@ export function SmartFieldBackground() {
this.rotorPhase += 0.3;
// 更新轨迹
this.trail.push({ x: this.x, y: this.y, alpha: 1 });
if (this.trail.length > 30) {
this.trail.shift();
@@ -148,7 +130,6 @@ export function SmartFieldBackground() {
}
draw(ctx: CanvasRenderingContext2D) {
// 绘制轨迹
this.trail.forEach((point, index) => {
if (index > 0) {
const prev = this.trail[index - 1];
@@ -161,15 +142,12 @@ export function SmartFieldBackground() {
}
});
// 无人机机身
ctx.save();
ctx.translate(this.x, this.y);
// 机身
ctx.fillStyle = 'rgba(59, 130, 246, 0.9)';
ctx.fillRect(-this.size / 2, -this.size / 2, this.size, this.size);
// 四个螺旋桨
const rotorPositions = [
{ x: -this.size * 0.7, y: -this.size * 0.7 },
{ x: this.size * 0.7, y: -this.size * 0.7 },
@@ -196,28 +174,18 @@ export function SmartFieldBackground() {
ctx.restore();
});
// 扫描光束
const scanRadius = 40;
ctx.beginPath();
ctx.arc(0, 0, scanRadius, 0, Math.PI * 2);
ctx.strokeStyle = 'rgba(59, 130, 246, 0.3)';
ctx.lineWidth = 1;
ctx.strokeStyle = 'rgba(59, 130, 246, 0.2)';
ctx.lineWidth = 2;
ctx.stroke();
ctx.restore();
}
}
// 数据上传指示
if (Math.random() < 0.1) {
ctx.beginPath();
ctx.arc(this.x, this.y, 3, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(34, 197, 94, 0.8)';
ctx.fill();
}
}
}
// 田地波浪效果
class FieldWave {
class FieldWave {
offset: number;
speed: number;
amplitude: number;
@@ -236,11 +204,11 @@ export function SmartFieldBackground() {
this.offset += this.speed;
}
draw(ctx: CanvasRenderingContext2D) {
draw(ctx: CanvasRenderingContext2D, canvasWidth: number) {
ctx.beginPath();
ctx.moveTo(0, this.y);
for (let x = 0; x <= canvas.width; x += 5) {
for (let x = 0; x <= canvasWidth; x += 5) {
const waveY = this.y + Math.sin((x + this.offset) * this.frequency) * this.amplitude;
ctx.lineTo(x, waveY);
}
@@ -249,10 +217,9 @@ export function SmartFieldBackground() {
ctx.lineWidth = 2;
ctx.stroke();
}
}
}
// 数据流粒子
class DataParticle {
class DataParticle {
x: number;
y: number;
vx: number;
@@ -286,7 +253,6 @@ export function SmartFieldBackground() {
ctx.fillStyle = `rgba(34, 197, 94, ${alpha * 0.8})`;
ctx.fill();
// 光晕
const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.size * 3);
gradient.addColorStop(0, `rgba(34, 197, 94, ${alpha * 0.4})`);
gradient.addColorStop(1, 'rgba(34, 197, 94, 0)');
@@ -299,7 +265,33 @@ export function SmartFieldBackground() {
isDead() {
return this.life >= this.maxLife;
}
}
}
export function SmartFieldBackground() {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
// 设置canvas尺寸
const resizeCanvas = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// 田间传感器节点
// 无人机
// 田地波浪效果
// 数据流粒子
// 初始化传感器网络
const sensors: SensorNode[] = [];
@@ -325,9 +317,10 @@ export function SmartFieldBackground() {
});
// 初始化无人机
const canvasBounds = () => ({ width: canvas.width, height: canvas.height });
const drones: Drone[] = [];
for (let i = 0; i < 2; i++) {
drones.push(new Drone());
drones.push(new Drone(canvasBounds));
}
// 初始化田地波浪
@@ -349,7 +342,7 @@ export function SmartFieldBackground() {
// 绘制田地波浪
waves.forEach(wave => {
wave.update();
wave.draw(ctx);
wave.draw(ctx, canvas.width);
});
// 绘制传感器连接线

View File

@@ -911,7 +911,7 @@ export class SpatialIndex {
}): Field[] {
const results: Field[] = [];
for (const [_, item] of this.rtree) {
for (const [, item] of this.rtree) {
if (this._bboxesIntersect(bbox, item.bbox)) {
results.push(item.field);
}

View File

@@ -1,7 +1,7 @@
// 通用类型定义
// API响应类型
export interface ApiResponse<T = any> {
export interface ApiResponse<T = unknown> {
code: number
message: string
data: T
@@ -34,11 +34,11 @@ export interface SortParams {
// 搜索类型
export interface SearchParams {
keyword?: string
filters?: Record<string, any>
filters?: Record<string, unknown>
}
// 表格列定义
export interface ColumnDef<T = any> {
export interface ColumnDef<T = Record<string, unknown>> {
key: string
title: string
dataIndex: keyof T
@@ -46,7 +46,7 @@ export interface ColumnDef<T = any> {
fixed?: 'left' | 'right'
sortable?: boolean
filterable?: boolean
render?: (value: any, record: T, index: number) => React.ReactNode
render?: (value: T[keyof T], record: T, index: number) => React.ReactNode
}
// 表单字段类型
@@ -56,7 +56,7 @@ export interface FormField {
type: 'text' | 'number' | 'select' | 'date' | 'textarea' | 'checkbox' | 'radio'
required?: boolean
placeholder?: string
options?: Array<{ label: string; value: any }>
options?: Array<{ label: string; value: string | number | boolean }>
validation?: {
min?: number
max?: number
@@ -343,7 +343,7 @@ export interface AppConfig {
// 路由类型
export interface Route {
path: string
component: React.ComponentType<any>
component: React.ComponentType<unknown>
exact?: boolean
title?: string
icon?: React.ReactNode
@@ -390,7 +390,7 @@ export interface DashboardStats {
export interface AppError {
code: string
message: string
details?: any
details?: unknown
timestamp: string
}
@@ -402,7 +402,7 @@ export interface LogEntry {
module: string
userId?: string
timestamp: string
metadata?: Record<string, any>
metadata?: Record<string, unknown>
}
export enum LogLevel {

View File

@@ -37,7 +37,7 @@ export interface MessageLog {
readTime?: string;
failReason?: string;
retryCount: number;
variables?: Record<string, any>;
variables?: Record<string, unknown>;
}
// 消息发送记录

View File

@@ -57,7 +57,7 @@ export interface DataDictionary {
description?: string;
isSystem: boolean; // 是否系统内置
isActive: boolean;
extendData?: Record<string, any>;
extendData?: Record<string, unknown>;
createdAt: string;
updatedAt: string;
}

View File

@@ -15,7 +15,7 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"jsx": "react-jsx",
/** TODO: */
"noImplicitAny": false,
"strictNullChecks": false,
@@ -70,7 +70,8 @@
},
"include": [
"src",
".next/types/**/*.ts"
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"paths": {
"@/*": [