|
|
@@ -2,12 +2,10 @@ import React from 'react';
|
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
|
import { Button } from '@/client/components/ui/button';
|
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
|
|
|
-import { Plus, Search, RotateCcw } from 'lucide-react';
|
|
|
-import { useState, useCallback } from 'react';
|
|
|
+import { Plus } from 'lucide-react';
|
|
|
+import { useState } from 'react';
|
|
|
import { areaClient } from '@/client/api';
|
|
|
import type { InferResponseType, InferRequestType } from 'hono/client';
|
|
|
-import { Input } from '@/client/components/ui/input';
|
|
|
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/client/components/ui/select';
|
|
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/client/components/ui/dialog';
|
|
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/client/components/ui/alert-dialog';
|
|
|
import { AreaForm } from '../components/AreaForm';
|
|
|
@@ -16,7 +14,6 @@ import type { CreateAreaInput, UpdateAreaInput } from '@d8d/server/modules/areas
|
|
|
|
|
|
// 类型提取规范
|
|
|
type AreaResponse = InferResponseType<typeof areaClient.$get, 200>['data'][0];
|
|
|
-type SearchAreaRequest = InferRequestType<typeof areaClient.$get>['query'];
|
|
|
type CreateAreaRequest = InferRequestType<typeof areaClient.$post>['json'];
|
|
|
type UpdateAreaRequest = InferRequestType<typeof areaClient[':id']['$put']>['json'];
|
|
|
|
|
|
@@ -44,20 +41,9 @@ const handleOperation = async (operation: () => Promise<any>) => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-// 防抖搜索函数
|
|
|
-const debounce = (func: Function, delay: number) => {
|
|
|
- let timeoutId: NodeJS.Timeout;
|
|
|
- return (...args: any[]) => {
|
|
|
- clearTimeout(timeoutId);
|
|
|
- timeoutId = setTimeout(() => func(...args), delay);
|
|
|
- };
|
|
|
-};
|
|
|
|
|
|
export const AreasTreePage: React.FC = () => {
|
|
|
const queryClient = useQueryClient();
|
|
|
- const [keyword, setKeyword] = useState('');
|
|
|
- const [level, setLevel] = useState<string>('all');
|
|
|
- const [isDisabled, setIsDisabled] = useState<string>('all');
|
|
|
const [expandedNodes, setExpandedNodes] = useState<Set<number>>(new Set());
|
|
|
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
|
|
|
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
|
|
|
@@ -65,11 +51,6 @@ export const AreasTreePage: React.FC = () => {
|
|
|
const [isStatusDialogOpen, setIsStatusDialogOpen] = useState(false);
|
|
|
const [selectedArea, setSelectedArea] = useState<AreaResponse | null>(null);
|
|
|
|
|
|
- // 构建搜索参数
|
|
|
- const filters: Record<string, any> = {};
|
|
|
- if (level && level !== 'all') filters.level = Number(level);
|
|
|
- if (isDisabled && isDisabled !== 'all') filters.isDisabled = Number(isDisabled);
|
|
|
-
|
|
|
// 查询省级数据(异步加载)
|
|
|
const { data: provinceData, isLoading: isProvinceLoading } = useQuery({
|
|
|
queryKey: ['areas-tree-province'],
|
|
|
@@ -152,25 +133,6 @@ export const AreasTreePage: React.FC = () => {
|
|
|
},
|
|
|
});
|
|
|
|
|
|
- // 防抖搜索
|
|
|
- const debouncedSearch = useCallback(
|
|
|
- debounce((keyword: string) => {
|
|
|
- setKeyword(keyword);
|
|
|
- }, 300),
|
|
|
- []
|
|
|
- );
|
|
|
-
|
|
|
- // 处理筛选变化
|
|
|
- const handleFilterChange = (filterType: string, value: string) => {
|
|
|
- switch (filterType) {
|
|
|
- case 'level':
|
|
|
- setLevel(value);
|
|
|
- break;
|
|
|
- case 'isDisabled':
|
|
|
- setIsDisabled(value);
|
|
|
- break;
|
|
|
- }
|
|
|
- };
|
|
|
|
|
|
// 处理创建省市区
|
|
|
const handleCreateArea = async (data: CreateAreaInput | UpdateAreaInput) => {
|
|
|
@@ -253,12 +215,6 @@ export const AreasTreePage: React.FC = () => {
|
|
|
});
|
|
|
};
|
|
|
|
|
|
- // 重置筛选
|
|
|
- const handleResetFilters = () => {
|
|
|
- setKeyword('');
|
|
|
- setLevel('all');
|
|
|
- setIsDisabled('all');
|
|
|
- };
|
|
|
|
|
|
return (
|
|
|
<div className="space-y-6">
|
|
|
@@ -283,57 +239,6 @@ export const AreasTreePage: React.FC = () => {
|
|
|
</CardDescription>
|
|
|
</CardHeader>
|
|
|
<CardContent>
|
|
|
- {/* 搜索和筛选区域 */}
|
|
|
- <div className="flex flex-col gap-4 mb-6">
|
|
|
- <div className="flex gap-4">
|
|
|
- <div className="flex-1">
|
|
|
- <div className="relative">
|
|
|
- <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
|
|
- <Input
|
|
|
- placeholder="搜索省市区名称或代码..."
|
|
|
- className="pl-8"
|
|
|
- value={keyword}
|
|
|
- onChange={(e) => {
|
|
|
- setKeyword(e.target.value);
|
|
|
- debouncedSearch(e.target.value);
|
|
|
- }}
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <Button
|
|
|
- variant="outline"
|
|
|
- onClick={handleResetFilters}
|
|
|
- disabled={!keyword && level === 'all' && isDisabled === 'all'}
|
|
|
- >
|
|
|
- <RotateCcw className="mr-2 h-4 w-4" />
|
|
|
- 重置
|
|
|
- </Button>
|
|
|
- </div>
|
|
|
- <div className="flex gap-4">
|
|
|
- <Select value={level} onValueChange={(value) => handleFilterChange('level', value)}>
|
|
|
- <SelectTrigger className="w-[180px]">
|
|
|
- <SelectValue placeholder="选择层级" />
|
|
|
- </SelectTrigger>
|
|
|
- <SelectContent>
|
|
|
- <SelectItem value="all">全部层级</SelectItem>
|
|
|
- <SelectItem value="1">省/直辖市</SelectItem>
|
|
|
- <SelectItem value="2">市</SelectItem>
|
|
|
- <SelectItem value="3">区/县</SelectItem>
|
|
|
- </SelectContent>
|
|
|
- </Select>
|
|
|
- <Select value={isDisabled} onValueChange={(value) => handleFilterChange('isDisabled', value)}>
|
|
|
- <SelectTrigger className="w-[180px]">
|
|
|
- <SelectValue placeholder="选择状态" />
|
|
|
- </SelectTrigger>
|
|
|
- <SelectContent>
|
|
|
- <SelectItem value="all">全部状态</SelectItem>
|
|
|
- <SelectItem value="0">启用</SelectItem>
|
|
|
- <SelectItem value="1">禁用</SelectItem>
|
|
|
- </SelectContent>
|
|
|
- </Select>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
{/* 树形视图 */}
|
|
|
{isProvinceLoading ? (
|
|
|
<div className="text-center py-8">
|