|
|
@@ -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;
|