index.vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. <template>
  2. <div style="display: flex; gap: 8px; align-items: center; width: 100%">
  3. <div style="flex: 1">
  4. <el-select
  5. :model-value="topCategoryId"
  6. placeholder="一级类目"
  7. filterable
  8. remote
  9. clearable
  10. :remote-method="handleFirstLevelSearch"
  11. :loading="loading"
  12. style="width: 100%"
  13. @update:model-value="handleFirstLevelChange"
  14. >
  15. <el-option v-for="item in firstOptions" :key="item.id" :label="item.categoryName" :value="item.id" />
  16. </el-select>
  17. </div>
  18. <div style="flex: 1">
  19. <el-select
  20. :model-value="mediumCategoryId"
  21. placeholder="二级类目"
  22. filterable
  23. remote
  24. clearable
  25. :remote-method="handleSecondLevelSearch"
  26. :loading="loading"
  27. style="width: 100%"
  28. :disabled="!topCategoryId"
  29. @update:model-value="handleSecondLevelChange"
  30. >
  31. <el-option v-for="item in secondOptions" :key="item.id" :label="item.categoryName" :value="item.id" />
  32. </el-select>
  33. </div>
  34. <div style="flex: 1">
  35. <el-select
  36. :model-value="bottomCategoryId"
  37. placeholder="三级类目"
  38. filterable
  39. remote
  40. clearable
  41. :remote-method="handleThirdLevelSearch"
  42. :loading="loading"
  43. style="width: 100%"
  44. :disabled="!mediumCategoryId"
  45. @update:model-value="(val) => emit('update:bottomCategoryId', val)"
  46. >
  47. <el-option v-for="item in thirdOptions" :key="item.id" :label="item.categoryName" :value="item.id" />
  48. </el-select>
  49. </div>
  50. </div>
  51. </template>
  52. <script setup lang="ts">
  53. import { listCategory } from '@/api/product/category';
  54. import { CategoryVO } from '@/api/product/category/types';
  55. const props = defineProps<{
  56. topCategoryId?: string | number;
  57. mediumCategoryId?: string | number;
  58. bottomCategoryId?: string | number;
  59. }>();
  60. const emit = defineEmits<{
  61. (e: 'update:topCategoryId', val: string | number | undefined): void;
  62. (e: 'update:mediumCategoryId', val: string | number | undefined): void;
  63. (e: 'update:bottomCategoryId', val: string | number | undefined): void;
  64. }>();
  65. const loading = ref(false);
  66. const firstOptions = ref<CategoryVO[]>([]);
  67. const secondOptions = ref<CategoryVO[]>([]);
  68. const thirdOptions = ref<CategoryVO[]>([]);
  69. const firstSearchTimer = ref<ReturnType<typeof setTimeout> | null>(null);
  70. const secondSearchTimer = ref<ReturnType<typeof setTimeout> | null>(null);
  71. const thirdSearchTimer = ref<ReturnType<typeof setTimeout> | null>(null);
  72. /** 加载一级分类 */
  73. const loadFirst = async (keyword?: string) => {
  74. loading.value = true;
  75. try {
  76. const res = await listCategory({ classLevel: 1, categoryName: keyword });
  77. firstOptions.value = res.data || (res as any).rows || [];
  78. } catch (e) {
  79. console.error('加载一级分类失败', e);
  80. } finally {
  81. loading.value = false;
  82. }
  83. };
  84. /** 加载二级分类 */
  85. const loadSecond = async (parentId?: string | number, keyword?: string) => {
  86. if (!parentId) {
  87. secondOptions.value = [];
  88. return;
  89. }
  90. loading.value = true;
  91. try {
  92. const res = await listCategory({ parentId, classLevel: 2, categoryName: keyword });
  93. secondOptions.value = res.data || (res as any).rows || [];
  94. } catch (e) {
  95. console.error('加载二级分类失败', e);
  96. } finally {
  97. loading.value = false;
  98. }
  99. };
  100. /** 加载三级分类 */
  101. const loadThird = async (parentId?: string | number, keyword?: string) => {
  102. if (!parentId) {
  103. thirdOptions.value = [];
  104. return;
  105. }
  106. loading.value = true;
  107. try {
  108. const res = await listCategory({ parentId, classLevel: 3, categoryName: keyword });
  109. thirdOptions.value = res.data || (res as any).rows || [];
  110. } catch (e) {
  111. console.error('加载三级分类失败', e);
  112. } finally {
  113. loading.value = false;
  114. }
  115. };
  116. /** 一级分类远程搜索(防抖) */
  117. const handleFirstLevelSearch = (query: string) => {
  118. if (firstSearchTimer.value) clearTimeout(firstSearchTimer.value);
  119. firstSearchTimer.value = setTimeout(() => loadFirst(query || undefined), 300);
  120. };
  121. /** 二级分类远程搜索(防抖) */
  122. const handleSecondLevelSearch = (query: string) => {
  123. if (secondSearchTimer.value) clearTimeout(secondSearchTimer.value);
  124. secondSearchTimer.value = setTimeout(() => loadSecond(props.topCategoryId, query || undefined), 300);
  125. };
  126. /** 三级分类远程搜索(防抖) */
  127. const handleThirdLevelSearch = (query: string) => {
  128. if (thirdSearchTimer.value) clearTimeout(thirdSearchTimer.value);
  129. thirdSearchTimer.value = setTimeout(() => loadThird(props.mediumCategoryId, query || undefined), 300);
  130. };
  131. /** 一级分类改变 */
  132. const handleFirstLevelChange = (val: string | number | undefined) => {
  133. emit('update:topCategoryId', val);
  134. emit('update:mediumCategoryId', undefined);
  135. emit('update:bottomCategoryId', undefined);
  136. secondOptions.value = [];
  137. thirdOptions.value = [];
  138. if (val) {
  139. loadSecond(val);
  140. }
  141. };
  142. /** 二级分类改变 */
  143. const handleSecondLevelChange = (val: string | number | undefined) => {
  144. emit('update:mediumCategoryId', val);
  145. emit('update:bottomCategoryId', undefined);
  146. thirdOptions.value = [];
  147. if (val) {
  148. loadThird(val);
  149. }
  150. };
  151. /** 重置组件状态(外部可调用) */
  152. const reset = () => {
  153. secondOptions.value = [];
  154. thirdOptions.value = [];
  155. };
  156. /** 初始化:加载一级分类;若已有值则加载对应下级 */
  157. onMounted(async () => {
  158. await loadFirst();
  159. if (props.topCategoryId) {
  160. await loadSecond(props.topCategoryId);
  161. }
  162. if (props.mediumCategoryId) {
  163. await loadThird(props.mediumCategoryId);
  164. }
  165. });
  166. defineExpose({ reset });
  167. </script>