Commit d81db890dfeb533d60f378ddb86f8ac50a31252b

Authored by 无木
1 parent c1178027

feat(api-tree-select): add `api` options to tree-select

添加ApiTreeSelect组件
CHANGELOG.zh_CN.md
@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 2
3 - **NoticeList** 添加分页、超长自动省略、标题点击事件、标题删除线等功能 3 - **NoticeList** 添加分页、超长自动省略、标题点击事件、标题删除线等功能
4 - **MixSider** 优化 Mix 菜单布局时 底部折叠按钮 的样式,与其它菜单布局时的风格保持一致 4 - **MixSider** 优化 Mix 菜单布局时 底部折叠按钮 的样式,与其它菜单布局时的风格保持一致
  5 +- **ApiTreeSelect** 扩展`antdv`的`TreeSelect`组件,支持远程数据源,用法类似`ApiSelect`
5 - 可以为不同的用户指定不同的后台首页: 6 - 可以为不同的用户指定不同的后台首页:
6 - 在`getUserInfo`接口返回的用户信息中增加`homePath`字段(可选)即可为当前用户定制首页路径 7 - 在`getUserInfo`接口返回的用户信息中增加`homePath`字段(可选)即可为当前用户定制首页路径
7 8
mock/demo/tree-demo.ts 0 → 100644
  1 +import { MockMethod } from 'vite-plugin-mock';
  2 +import { resultSuccess } from '../_util';
  3 +
  4 +const demoTreeList = (keyword) => {
  5 + const result = {
  6 + list: [] as Recordable[],
  7 + };
  8 + for (let index = 0; index < 5; index++) {
  9 + const children: Recordable[] = [];
  10 + for (let j = 0; j < 3; j++) {
  11 + children.push({
  12 + title: `${keyword ?? ''}选项${index}-${j}`,
  13 + value: `${index}-${j}`,
  14 + key: `${index}-${j}`,
  15 + });
  16 + }
  17 + result.list.push({
  18 + title: `${keyword ?? ''}选项${index}`,
  19 + value: `${index}`,
  20 + key: `${index}`,
  21 + children,
  22 + });
  23 + }
  24 + return result;
  25 +};
  26 +
  27 +export default [
  28 + {
  29 + url: '/basic-api/tree/getDemoOptions',
  30 + timeout: 1000,
  31 + method: 'get',
  32 + response: ({ query }) => {
  33 + const { keyword } = query;
  34 + console.log(keyword);
  35 + return resultSuccess(demoTreeList(keyword));
  36 + },
  37 + },
  38 +] as MockMethod[];
src/api/demo/tree.ts 0 → 100644
  1 +import { defHttp } from '/@/utils/http/axios';
  2 +
  3 +enum Api {
  4 + TREE_OPTIONS_LIST = '/tree/getDemoOptions',
  5 +}
  6 +
  7 +/**
  8 + * @description: Get sample options value
  9 + */
  10 +export const treeOptionsListApi = (params?: Recordable) =>
  11 + defHttp.get<Recordable[]>({ url: Api.TREE_OPTIONS_LIST, params });
src/components/Form/src/componentMap.ts
@@ -22,6 +22,7 @@ import { @@ -22,6 +22,7 @@ import {
22 22
23 import RadioButtonGroup from './components/RadioButtonGroup.vue'; 23 import RadioButtonGroup from './components/RadioButtonGroup.vue';
24 import ApiSelect from './components/ApiSelect.vue'; 24 import ApiSelect from './components/ApiSelect.vue';
  25 +import ApiTreeSelect from './components/ApiTreeSelect.vue';
25 import { BasicUpload } from '/@/components/Upload'; 26 import { BasicUpload } from '/@/components/Upload';
26 import { StrengthMeter } from '/@/components/StrengthMeter'; 27 import { StrengthMeter } from '/@/components/StrengthMeter';
27 import { IconPicker } from '/@/components/Icon'; 28 import { IconPicker } from '/@/components/Icon';
@@ -40,6 +41,7 @@ componentMap.set(&#39;AutoComplete&#39;, AutoComplete); @@ -40,6 +41,7 @@ componentMap.set(&#39;AutoComplete&#39;, AutoComplete);
40 componentMap.set('Select', Select); 41 componentMap.set('Select', Select);
41 componentMap.set('ApiSelect', ApiSelect); 42 componentMap.set('ApiSelect', ApiSelect);
42 componentMap.set('TreeSelect', TreeSelect); 43 componentMap.set('TreeSelect', TreeSelect);
  44 +componentMap.set('ApiTreeSelect', ApiTreeSelect);
43 componentMap.set('Switch', Switch); 45 componentMap.set('Switch', Switch);
44 componentMap.set('RadioButtonGroup', RadioButtonGroup); 46 componentMap.set('RadioButtonGroup', RadioButtonGroup);
45 componentMap.set('RadioGroup', Radio.Group); 47 componentMap.set('RadioGroup', Radio.Group);
src/components/Form/src/components/ApiTreeSelect.vue 0 → 100644
  1 +<template> <a-tree-select v-bind="getAttrs"> <template #[item]="data" v-for="item in Object.keys($slots)"> <slot :name="item" v-bind="data"></slot> </template> <template #suffixIcon v-if="loading"> <LoadingOutlined spin /> </template> </a-tree-select> </template> <script lang="ts"> import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue'; import { TreeSelect } from 'ant-design-vue'; import { isArray, isFunction } from '/@/utils/is'; import { get } from 'lodash-es'; import { propTypes } from '/@/utils/propTypes'; import { LoadingOutlined } from '@ant-design/icons-vue'; export default defineComponent({ name: 'ApiTreeSelect', components: { ATreeSelect: TreeSelect, LoadingOutlined }, props: { api: { type: Function as PropType<(arg?: Recordable) => Promise<Recordable>> }, params: { type: Object }, immediate: { type: Boolean, default: true }, resultField: propTypes.string.def(''), }, emits: ['options-change'], setup(props, { attrs, emit }) { const treeData = ref<Recordable[]>([]); const isFirstLoaded = ref<Boolean>(false); const loading = ref(false); const getAttrs = computed(() => { return { ...(props.api ? { treeData: unref(treeData) } : {}), ...attrs, }; }); watch([() => props.params, () => props.immediate], () => { isFirstLoaded.value && fetch(); }); onMounted(() => { props.immediate && fetch(); }); async function fetch() { const { api } = props; if (!api || !isFunction(api)) return; loading.value = true; treeData.value = []; let result; try { result = await api(props.params); } catch (e) { console.error(e); } loading.value = false; if (!result) return; if (!isArray(result)) { result = get(result, props.resultField); } treeData.value = (result as Recordable[]) || []; isFirstLoaded.value = true; emit('options-change', treeData.value); } return { getAttrs, loading }; }, }); </script>
0 \ No newline at end of file 2 \ No newline at end of file
src/components/Form/src/types/index.ts
@@ -91,6 +91,7 @@ export type ComponentType = @@ -91,6 +91,7 @@ export type ComponentType =
91 | 'Select' 91 | 'Select'
92 | 'ApiSelect' 92 | 'ApiSelect'
93 | 'TreeSelect' 93 | 'TreeSelect'
  94 + | 'ApiTreeSelect'
94 | 'RadioButtonGroup' 95 | 'RadioButtonGroup'
95 | 'RadioGroup' 96 | 'RadioGroup'
96 | 'Checkbox' 97 | 'Checkbox'
src/views/demo/form/index.vue
@@ -46,6 +46,7 @@ @@ -46,6 +46,7 @@
46 46
47 import { optionsListApi } from '/@/api/demo/select'; 47 import { optionsListApi } from '/@/api/demo/select';
48 import { useDebounceFn } from '@vueuse/core'; 48 import { useDebounceFn } from '@vueuse/core';
  49 + import { treeOptionsListApi } from '/@/api/demo/tree';
49 50
50 const provincesOptions = [ 51 const provincesOptions = [
51 { 52 {
@@ -348,6 +349,20 @@ @@ -348,6 +349,20 @@
348 defaultValue: '0', 349 defaultValue: '0',
349 }, 350 },
350 { 351 {
  352 + field: 'field33',
  353 + component: 'ApiTreeSelect',
  354 + label: '远程下拉树',
  355 + helpMessage: ['ApiTreeSelect组件', '使用接口提供的数据生成选项'],
  356 + required: true,
  357 + componentProps: {
  358 + api: treeOptionsListApi,
  359 + resultField: 'list',
  360 + },
  361 + colProps: {
  362 + span: 8,
  363 + },
  364 + },
  365 + {
351 field: 'field20', 366 field: 'field20',
352 component: 'InputNumber', 367 component: 'InputNumber',
353 label: '字段20', 368 label: '字段20',
src/views/demo/table/EditCellTable.vue
@@ -84,6 +84,9 @@ @@ -84,6 +84,9 @@
84 editComponent: 'ApiSelect', 84 editComponent: 'ApiSelect',
85 editComponentProps: { 85 editComponentProps: {
86 api: optionsListApi, 86 api: optionsListApi,
  87 + resultField: 'list',
  88 + labelField: 'name',
  89 + valueField: 'id',
87 }, 90 },
88 width: 200, 91 width: 200,
89 }, 92 },
src/views/demo/table/EditRowTable.vue
@@ -80,6 +80,10 @@ @@ -80,6 +80,10 @@
80 label: 'Option2', 80 label: 'Option2',
81 value: '2', 81 value: '2',
82 }, 82 },
  83 + {
  84 + label: 'Option3',
  85 + value: '3',
  86 + },
83 ], 87 ],
84 }, 88 },
85 width: 200, 89 width: 200,
@@ -91,6 +95,9 @@ @@ -91,6 +95,9 @@
91 editComponent: 'ApiSelect', 95 editComponent: 'ApiSelect',
92 editComponentProps: { 96 editComponentProps: {
93 api: optionsListApi, 97 api: optionsListApi,
  98 + resultField: 'list',
  99 + labelField: 'name',
  100 + valueField: 'id',
94 }, 101 },
95 width: 200, 102 width: 200,
96 }, 103 },