| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- <template>
- <div style="display: flex; gap: 8px; align-items: center; width: 100%">
- <div style="flex: 1">
- <el-select
- :model-value="topCategoryId"
- placeholder="一级类目"
- filterable
- remote
- clearable
- :remote-method="handleFirstLevelSearch"
- :loading="loading"
- style="width: 100%"
- @update:model-value="handleFirstLevelChange"
- >
- <el-option v-for="item in firstOptions" :key="item.id" :label="item.categoryName" :value="item.id" />
- </el-select>
- </div>
- <div style="flex: 1">
- <el-select
- :model-value="mediumCategoryId"
- placeholder="二级类目"
- filterable
- remote
- clearable
- :remote-method="handleSecondLevelSearch"
- :loading="loading"
- style="width: 100%"
- :disabled="!topCategoryId"
- @update:model-value="handleSecondLevelChange"
- >
- <el-option v-for="item in secondOptions" :key="item.id" :label="item.categoryName" :value="item.id" />
- </el-select>
- </div>
- <div style="flex: 1">
- <el-select
- :model-value="bottomCategoryId"
- placeholder="三级类目"
- filterable
- remote
- clearable
- :remote-method="handleThirdLevelSearch"
- :loading="loading"
- style="width: 100%"
- :disabled="!mediumCategoryId"
- @update:model-value="(val) => emit('update:bottomCategoryId', val)"
- >
- <el-option v-for="item in thirdOptions" :key="item.id" :label="item.categoryName" :value="item.id" />
- </el-select>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { listCategory } from '@/api/product/category';
- import { CategoryVO } from '@/api/product/category/types';
- const props = defineProps<{
- topCategoryId?: string | number;
- mediumCategoryId?: string | number;
- bottomCategoryId?: string | number;
- }>();
- const emit = defineEmits<{
- (e: 'update:topCategoryId', val: string | number | undefined): void;
- (e: 'update:mediumCategoryId', val: string | number | undefined): void;
- (e: 'update:bottomCategoryId', val: string | number | undefined): void;
- }>();
- const loading = ref(false);
- const firstOptions = ref<CategoryVO[]>([]);
- const secondOptions = ref<CategoryVO[]>([]);
- const thirdOptions = ref<CategoryVO[]>([]);
- const firstSearchTimer = ref<ReturnType<typeof setTimeout> | null>(null);
- const secondSearchTimer = ref<ReturnType<typeof setTimeout> | null>(null);
- const thirdSearchTimer = ref<ReturnType<typeof setTimeout> | null>(null);
- /** 加载一级分类 */
- const loadFirst = async (keyword?: string) => {
- loading.value = true;
- try {
- const res = await listCategory({ classLevel: 1, categoryName: keyword });
- firstOptions.value = res.data || (res as any).rows || [];
- } catch (e) {
- console.error('加载一级分类失败', e);
- } finally {
- loading.value = false;
- }
- };
- /** 加载二级分类 */
- const loadSecond = async (parentId?: string | number, keyword?: string) => {
- if (!parentId) {
- secondOptions.value = [];
- return;
- }
- loading.value = true;
- try {
- const res = await listCategory({ parentId, classLevel: 2, categoryName: keyword });
- secondOptions.value = res.data || (res as any).rows || [];
- } catch (e) {
- console.error('加载二级分类失败', e);
- } finally {
- loading.value = false;
- }
- };
- /** 加载三级分类 */
- const loadThird = async (parentId?: string | number, keyword?: string) => {
- if (!parentId) {
- thirdOptions.value = [];
- return;
- }
- loading.value = true;
- try {
- const res = await listCategory({ parentId, classLevel: 3, categoryName: keyword });
- thirdOptions.value = res.data || (res as any).rows || [];
- } catch (e) {
- console.error('加载三级分类失败', e);
- } finally {
- loading.value = false;
- }
- };
- /** 一级分类远程搜索(防抖) */
- const handleFirstLevelSearch = (query: string) => {
- if (firstSearchTimer.value) clearTimeout(firstSearchTimer.value);
- firstSearchTimer.value = setTimeout(() => loadFirst(query || undefined), 300);
- };
- /** 二级分类远程搜索(防抖) */
- const handleSecondLevelSearch = (query: string) => {
- if (secondSearchTimer.value) clearTimeout(secondSearchTimer.value);
- secondSearchTimer.value = setTimeout(() => loadSecond(props.topCategoryId, query || undefined), 300);
- };
- /** 三级分类远程搜索(防抖) */
- const handleThirdLevelSearch = (query: string) => {
- if (thirdSearchTimer.value) clearTimeout(thirdSearchTimer.value);
- thirdSearchTimer.value = setTimeout(() => loadThird(props.mediumCategoryId, query || undefined), 300);
- };
- /** 一级分类改变 */
- const handleFirstLevelChange = (val: string | number | undefined) => {
- emit('update:topCategoryId', val);
- emit('update:mediumCategoryId', undefined);
- emit('update:bottomCategoryId', undefined);
- secondOptions.value = [];
- thirdOptions.value = [];
- if (val) {
- loadSecond(val);
- }
- };
- /** 二级分类改变 */
- const handleSecondLevelChange = (val: string | number | undefined) => {
- emit('update:mediumCategoryId', val);
- emit('update:bottomCategoryId', undefined);
- thirdOptions.value = [];
- if (val) {
- loadThird(val);
- }
- };
- /** 重置组件状态(外部可调用) */
- const reset = () => {
- secondOptions.value = [];
- thirdOptions.value = [];
- };
- /** 初始化:加载一级分类;若已有值则加载对应下级 */
- onMounted(async () => {
- await loadFirst();
- if (props.topCategoryId) {
- await loadSecond(props.topCategoryId);
- }
- if (props.mediumCategoryId) {
- await loadThird(props.mediumCategoryId);
- }
- });
- defineExpose({ reset });
- </script>
|