ソースを参照

✨ feat(components): 创建区域树形选择组件AreaTreeSelect

- 实现将扁平区域数据转换为树形结构的convertToTree工具函数
- 使用react-query获取区域数据并处理加载状态
- 支持搜索过滤功能,可通过名称快速查找区域
- 添加类型定义确保类型安全

✨ feat(areas): 优化区域管理页面的父区域选择体验

- 将父区域ID输入框替换为树形选择组件
- 添加"顶级区域无需选择"的提示信息
- 优化表单交互体验,支持直观的层级选择

✨ feat(clients): 改进客户管理页面的区域选择功能

- 将普通下拉选择器替换为树形区域选择组件
- 支持区域层级展示,提升用户选择体验
- 统一区域选择交互模式,保持系统UI一致性
yourname 9 ヶ月 前
コミット
4968af799c

+ 100 - 0
src/client/admin/components/AreaTreeSelect.tsx

@@ -0,0 +1,100 @@
+import React, { useEffect, useState } from 'react';
+import { TreeSelect } from 'antd';
+import { useQuery } from '@tanstack/react-query';
+import { areaClient } from '@/client/api';
+import type { InferResponseType } from 'hono/client';
+
+// 定义区域数据类型
+type AreaItem = {
+  id: number;
+  name: string;
+  parentId?: number;
+  children?: AreaItem[];
+};
+
+// 定义组件属性类型
+interface AreaTreeSelectProps {
+  value?: number;
+  onChange?: (value?: number) => void;
+  placeholder?: string;
+  disabled?: boolean;
+}
+
+/**
+ * 将扁平的区域数据转换为树形结构
+ * @param list 扁平的区域列表
+ * @param parentId 父节点ID,默认为null(顶级节点)
+ * @returns 树形结构的区域数据
+ */
+const convertToTree = (list: AreaItem[], parentId: number | null = null): AreaItem[] => {
+  return list
+    .filter(item => item.parentId === parentId)
+    .map(item => ({
+      ...item,
+      children: convertToTree(list, item.id)
+    }))
+    .filter(item => item.children.length > 0 || parentId !== null);
+};
+
+/**
+ * 区域树形选择组件
+ * 用于选择区域层级结构,支持复用
+ */
+const AreaTreeSelect: React.FC<AreaTreeSelectProps> = ({
+  value,
+  onChange,
+  placeholder = '请选择区域',
+  disabled = false
+}): React.ReactElement => {
+  const [treeData, setTreeData] = useState<AreaItem[]>([]);
+
+  // 获取区域列表数据
+  const { data: areasData } = useQuery({
+    queryKey: ['areas'],
+    queryFn: async (): Promise<InferResponseType<typeof areaClient.$get, 200>> => {
+      const res = await areaClient.$get({ query: { page: 1, pageSize: 1000 } });
+      if (res.status !== 200) {
+        throw new Error('获取区域列表失败');
+      }
+      return res.json();
+    }
+  });
+
+  // 转换数据为树形结构
+  useEffect(() => {
+    if (areasData?.data) {
+      const tree = convertToTree(areasData.data as AreaItem[]);
+      setTreeData(tree);
+    }
+  }, [areasData]);
+
+  // 格式化树形选择器的选项
+  const formattedTreeData = treeData.map(item => ({
+    title: item.name,
+    value: item.id,
+    key: item.id,
+    children: item.children?.map(child => ({
+      title: child.name,
+      value: child.id,
+      key: child.id,
+      children: child.children?.length ? formattedTreeData : undefined
+    }))
+  }));
+
+  return (
+    <TreeSelect
+      value={value}
+      onChange={onChange}
+      treeData={formattedTreeData}
+      placeholder={placeholder}
+      disabled={disabled}
+      style={{ width: '100%' }}
+      showSearch
+      filterTreeNode={(inputValue, treeNode) =>
+        treeNode.title.toLowerCase().includes(inputValue.toLowerCase())
+      }
+    />
+  );
+};
+
+export default AreaTreeSelect;

+ 7 - 2
src/client/admin/pages/Areas.tsx

@@ -267,9 +267,14 @@ const Areas: React.FC = () => {
           </Form.Item>
           <Form.Item
             name="parentId"
-            label="父区域ID"
+            label="父区域"
+            tooltip="选择父区域,顶级区域无需选择"
           >
-            <Input placeholder="请输入父区域ID,顶级区域留空" />
+            <AreaTreeSelect
+              placeholder="请选择父区域(顶级区域无需选择)"
+              value={form.getFieldValue('parentId') || undefined}
+              onChange={(value) => form.setFieldValue('parentId', value)}
+            />
           </Form.Item>
         </Form>
       </Modal>

+ 5 - 7
src/client/admin/pages/Clients.tsx

@@ -351,13 +351,11 @@ const Clients: React.FC = () => {
               name="areaId"
               label="所在区域"
             >
-              <Select placeholder="请选择区域">
-                {areas.map(area => (
-                  <Select.Option key={area.id} value={area.id}>
-                    {area.name}
-                  </Select.Option>
-                ))}
-              </Select>
+              <AreaTreeSelect
+                placeholder="请选择区域"
+                value={form.getFieldValue('areaId') || undefined}
+                onChange={(value) => form.setFieldValue('areaId', value)}
+              />
             </Form.Item>
             
             <Form.Item