treeSelect.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import { computed, reactive, ref, watch, watchEffect } from "vue";
  2. type Item<T> = { id: string; children?: Item<T>[] } & T;
  3. export type TreeOption = {
  4. label: string;
  5. level?: number;
  6. value: any;
  7. children?: TreeOption[];
  8. };
  9. export type Props = {
  10. modelValue: string;
  11. label?: string;
  12. hideAll?: boolean;
  13. allText?: string;
  14. notDefault?: boolean;
  15. disabled?: boolean;
  16. id?: string
  17. };
  18. export type Mapper = {
  19. label: string;
  20. level: string;
  21. };
  22. const treeSelectOptionCover = <T>(
  23. data: Item<T>[],
  24. mapping: Mapper
  25. ): TreeOption[] =>
  26. data.map(
  27. (item) =>
  28. ({
  29. label: (item as any)[mapping.label],
  30. value: item.id,
  31. level: (item as any)[mapping.level],
  32. children:
  33. item.children && item.children.length
  34. ? treeSelectOptionCover(item.children, mapping)
  35. : null,
  36. } as any)
  37. );
  38. const getTreePath = (options: TreeOption[], value: string): null | string[] => {
  39. for (const option of options) {
  40. if (option.value === value) {
  41. return [option.value];
  42. } else if (option.children) {
  43. const cpath = getTreePath(option.children, value);
  44. if (cpath) {
  45. return [option.value, ...cpath];
  46. }
  47. }
  48. }
  49. return null;
  50. };
  51. const getTreeSelect = (
  52. options: TreeOption[],
  53. value: string
  54. ): null | TreeOption => {
  55. for (const option of options) {
  56. if (option.value === value) {
  57. return option;
  58. } else if (option.children) {
  59. const coption = getTreeSelect(option.children, value);
  60. if (coption) {
  61. return coption;
  62. }
  63. }
  64. }
  65. return null;
  66. };
  67. const allOption: TreeOption = { label: "全部", value: "" };
  68. export const useTreeSelect = <T>(
  69. props: Props,
  70. request: () => Promise<Item<T>[]>,
  71. update: (val: string) => void,
  72. mapping: Mapper = { label: "name", level: "level" }
  73. ) => {
  74. // 原始数据
  75. const optionsRaw = ref<Item<T>[]>([]);
  76. // 级联控件需要数据
  77. const options = computed<TreeOption[]>(() => {
  78. const dataOptions = treeSelectOptionCover(optionsRaw.value, mapping);
  79. return props.hideAll ? dataOptions : [allOption, ...dataOptions];
  80. });
  81. // 级联控件续高亮value
  82. const path = computed({
  83. get: () => getTreePath(options.value, props.modelValue)!,
  84. set: (path) => {
  85. update(path ? path[path.length - 1] : "");
  86. },
  87. });
  88. const currentOption = computed(() =>
  89. getTreeSelect(options.value, props.modelValue)
  90. );
  91. const label = computed(
  92. () =>
  93. options.value.find((option) => option.value === props.modelValue)
  94. ?.label || ""
  95. );
  96. watch(
  97. () => props.modelValue,
  98. () => {
  99. if (!props.notDefault && !props.modelValue && options.value.length) {
  100. update(options.value[0].value);
  101. }
  102. },
  103. { immediate: true }
  104. );
  105. request().then((data) => (optionsRaw.value = data as any));
  106. return reactive({
  107. label,
  108. path,
  109. options,
  110. raw: optionsRaw,
  111. currentOption,
  112. });
  113. };