treeSelect.ts 2.9 KB

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