shmily1213 1 месяц назад
Родитель
Сommit
9c5b7247e5

+ 9 - 0
src/api/modules/university.ts

@@ -512,3 +512,12 @@ export function removeConcernedUniversity(params: any) {
 export function getCollectedUniversities(params: any) {
     return flyio.get('/front/customer/university/list', params) as Promise<ApiResponseList<Major.University>>;
 }
+
+/**
+ * 获取院校位置
+ * @param params
+ * @returns
+ */
+export function getUniversityLocation() {
+    return flyio.get('/front/university/locations2', {}) as Promise<ApiResponse<{cityName: string[], location: string}[]>>;
+}

+ 32 - 19
src/components/ie-dropdown/ie-dropdown-item.vue

@@ -3,7 +3,7 @@
     <view :id="`dropdown-trigger-${config.prop}`" class="w-full flex items-center justify-center gap-10 h-full relative"
       @click="handleClick">
       <view class="relative">
-        <view class="text-xs text-center ellipsis-1" :class="[show ? 'text-primary' : 'text-fore-title ']">
+        <view class="text-[14px] text-center ellipsis-1" :class="[show ? 'text-primary' : 'text-fore-title ']">
           {{ config.label }}
         </view>
         <view class="absolute -top-4 -right-4 w-6 h-6 bg-red-500 rounded-full" v-if="hasValue"></view>
@@ -19,24 +19,27 @@
       <view class="relative z-1 overflow-hidden ">
         <view class="left-0 right-0 z-1 w-full bg-white box-border transition-transform duration-300 ease-out"
           :style="contentStyle" @click="">
-          <scroll-view class="relative max-h-[300px]" scroll-y>
-            <uv-checkbox-group v-model="checkboxValue" placement="column" iconPlacement="right" borderBottom>
-              <uv-checkbox :customStyle="{ marginBottom: '0', paddingBottom: '0', height: '38px', padding: '0 10px' }"
-                v-for="(item, index) in config.options" :key="index" :label="item.label"
-                :name="item.value"></uv-checkbox>
-            </uv-checkbox-group>
-          </scroll-view>
-          <view class="flex items-center justify-between gap-24 p-[12px]">
-            <view class="flex-1">
-              <uv-button type="primary" plain shape="circle" @click="handleReset">重置</uv-button>
+          <slot v-if="config.slot" :name="config.slot"></slot>
+          <template v-else>
+            <scroll-view class="relative max-h-[300px]" scroll-y>
+              <uv-checkbox-group v-model="checkboxValue" placement="column" iconPlacement="right" borderBottom>
+                <uv-checkbox :customStyle="{ marginBottom: '0', paddingBottom: '0', height: '38px', padding: '0 10px' }"
+                  v-for="(item, index) in config.options" :key="index" :label="item.label"
+                  :name="item.value"></uv-checkbox>
+              </uv-checkbox-group>
+            </scroll-view>
+            <view class="flex items-center justify-between gap-24 p-[12px]">
+              <view class="flex-1">
+                <uv-button type="primary" plain shape="circle" @click="handleReset">重置</uv-button>
+              </view>
+              <view class="flex-1">
+                <uv-button type="primary" shape="circle" @click="handleSubmit">
+                  <text>确定</text>
+                  <text v-if="checkboxValue.length > 0">({{ checkboxValue.length }})</text>
+                </uv-button>
+              </view>
             </view>
-            <view class="flex-1">
-              <uv-button type="primary" shape="circle" @click="handleSubmit">
-                <text>确定</text>
-                <text v-if="checkboxValue.length > 0">({{ checkboxValue.length }})</text>
-              </uv-button>
-            </view>
-          </view>
+          </template>
         </view>
       </view>
     </view>
@@ -44,7 +47,7 @@
   </view>
 </template>
 <script lang="ts" setup>
-import { type Dropdown } from '@/types';
+import type { Dropdown } from '@/types';
 import { DROPDOWN_SYMBOL } from './ie-dropdown-hooks';
 import { getCurrentInstance } from 'vue';
 
@@ -90,6 +93,9 @@ const contentStyle = computed(() => {
 });
 
 watch(() => dropdown?.openIndex.value, (newVal) => {
+  if (props.config.custom) {
+    return;
+  }
   if (newVal === props.index) {
     open();
   } else {
@@ -163,11 +169,18 @@ const handleMaskClick = () => {
   }
 }
 
+const emits = defineEmits<{
+  (e: 'click'): void;
+  (e: 'open', config: Dropdown.DropdownItem): void;
+  (e: 'close', config: Dropdown.DropdownItem): void;
+}>();
 const handleClick = () => {
   if (isOpen.value) {
     dropdown?.close();
+    emits('close', props.config);
   } else {
     dropdown?.open(props.index);
+    emits('open', props.config);
   }
 }
 </script>

+ 23 - 1
src/components/ie-dropdown/ie-dropdown.vue

@@ -1,7 +1,13 @@
 <template>
   <view class="flex items-center gap-20 px-20 h-[44px] border-0 border-b border-solid border-border-light">
     <view v-for="(item, index) in configs" :key="item.prop" class="flex-1 min-w-1 h-full">
-      <ie-dropdown-item :config="item" :index="index" :absolute="absolute" />
+      <ie-dropdown-item :config="item" :index="index" :absolute="absolute" @open="handleOpen" @close="handleClose">
+        <template #[getSlotName(item.slot)]>
+          <view v-if="item.slot">
+            <slot :name="getSlotName(item.slot)" :index="index"></slot>
+          </view>
+        </template>
+      </ie-dropdown-item>
     </view>
   </view>
 </template>
@@ -10,9 +16,25 @@ import IeDropdownItem from './ie-dropdown-item.vue';
 import type { Dropdown } from '@/types';
 import { useDropdown, DROPDOWN_SYMBOL } from './ie-dropdown-hooks';
 
+const getSlotName = (slot?: string) => {
+  return slot ? `${slot}` : '';
+}
+
 const emit = defineEmits<{
   (e: 'change', value: any): void;
+  (e: 'open', config: Dropdown.DropdownItem): void;
+  (e: 'close', config: Dropdown.DropdownItem): void;
 }>();
+const handleOpen = (config: Dropdown.DropdownItem) => {
+  if (config.custom) {
+    emit('open', config);
+  }
+}
+const handleClose = (config: Dropdown.DropdownItem) => {
+  if (config.custom) {
+    emit('close', config);
+  }
+}
 const modelValue = defineModel<Record<string, any>>();
 const props = defineProps<{
   configs: Dropdown.DropdownItem[];

+ 205 - 0
src/pagesOther/pages/university/index/components/plus/college-conditions-location.vue

@@ -0,0 +1,205 @@
+<template>
+  <view class="bg-white flex flex-col h-[350px]">
+    <view class="flex flex-1 min-h-1">
+      <scroll-view scroll-y class="h-full w-[80px] bg-back-light">
+        <view v-for="(item, index) in list" :key="item.name" class="h-[36px] px-20 flex items-center"
+          :class="[currentIndex === index ? 'bg-white text-primary' : 'text-fore-title']" @click="handleClick(index)">
+          <view class="text-[13px] ellipsis-1">{{ item.name }}</view>
+        </view>
+      </scroll-view>
+      <scroll-view scroll-y class="flex-1 min-w-1 h-full bg-white">
+        <view class="grid grid-cols-3 gap-20 p-20">
+          <view v-for="item in childrenList" :key="item.name"
+            class="h-[34px] px-20 flex items-center rounded-6 bg-back-light border border-solid relative overflow-hidden"
+            :class="[isSelected(item) ? 'border-primary text-primary' : 'border-transparent text-fore-title']"
+            @click="handleChildrenClick(item)">
+            <view v-if="isSelected(item)" class="absolute bottom-0 -right-2 p-2 bg-primary rounded-tl-4 rounded-br-6">
+              <uv-icon name="checkmark" size="10" color="#fff" />
+            </view>
+            <view class="flex-1 min-w-1 text-[13px] text-center ellipsis-1">{{ item.name }}</view>
+          </view>
+        </view>
+      </scroll-view>
+    </view>
+    <view class="flex items-center justify-between gap-24 p-[12px]">
+      <view class="flex-1">
+        <uv-button type="primary" plain shape="circle" @click="handleReset">重置</uv-button>
+      </view>
+      <view class="flex-1">
+        <uv-button type="primary" shape="circle" @click="handleSubmit">
+          <text>确定</text>
+        </uv-button>
+      </view>
+    </view>
+  </view>
+</template>
+<script lang="ts" setup>
+import { getUniversityLocation } from "@/api/modules/university";
+import type { Dropdown } from '@/types';
+import { DROPDOWN_SYMBOL } from '@/components/ie-dropdown/ie-dropdown-hooks';
+
+const props = defineProps<{
+  index: number;
+}>();
+const dropdown = inject(DROPDOWN_SYMBOL);
+
+type LocationItem = {
+  name: string;
+  parent?: string;
+  children: LocationItem[];
+}
+const list = ref<LocationItem[]>([]);
+const currentIndex = ref(0);
+const childrenList = computed(() => {
+  return list.value[currentIndex.value]?.children || [];
+});
+
+const selectedProvince = ref<string>('');
+const selectedCity = ref<string[]>([]);
+const selected = ref<LocationItem[]>([]);
+
+watch(() => dropdown?.openIndex.value, (newVal) => {
+  if (newVal === props.index) {
+    initValue();
+  }
+});
+
+const isSelected = (item: LocationItem) => {
+  const currentProvince = selected.value.find(province => province.name === item.parent);
+  if (currentProvince) {
+    return currentProvince.children.some(city => city.name === item.name);
+  }
+  return false;
+}
+
+const initValue = () => {
+  const { location } = dropdown?.form.value || {};
+  if (location) {
+    // eg. 湖南:长沙,张家界;河南:开封,郑州; 还原成 selected 数据
+    const locationParams: LocationItem[] = location.split(';').filter((item: string) => item).map((item: string) => {
+      return {
+        name: item.split(':')[0],
+        children: item.split(':')[1].split(',').map(city => {
+          return {
+            name: city,
+            parent: item.split(':')[0],
+            children: []
+          }
+        }),
+      }
+    });
+    const aar: LocationItem[] = [];
+    locationParams.forEach(item => {
+      aar.push({
+        name: item.name,
+        children: item.children
+      });
+    })
+    selected.value = aar;
+    // 找到第一个选中的省份
+    const firstIndex = list.value.findIndex(item => item.name === selected.value[0].name);
+    if (firstIndex !== -1) {
+      currentIndex.value = firstIndex;
+    } else {
+      currentIndex.value = 0;
+    }
+  }
+}
+
+const handleReset = () => {
+  selected.value = [];
+};
+const handleSubmit = () => {
+  // eg. 湖南:长沙,张家界;河南:开封,郑州;
+  const value = selected.value.reduce((acc, item) => {
+    return acc + item.name + ':' + item.children.map(city => city.name).join(',') + ';';
+  }, '');
+  dropdown?.submit(props.index, value);
+  dropdown?.close();
+};
+
+
+const handleClick = (index: number) => {
+  currentIndex.value = index;
+  selectedProvince.value = list.value[index]?.name ?? '';
+  selectedCity.value = [];
+}
+
+const handleChildrenClick = (item: LocationItem) => {
+  selected.value = selected.value.filter(province => province.name === selectedProvince.value);
+  if (item.name === '不限') {
+    const currentProvince = list.value[currentIndex.value];
+    const currentSelected = selected.value.find(province => province.name === currentProvince.name);
+    if (currentSelected) {
+      currentSelected.children = [{
+        name: '不限',
+        children: []
+      }];
+    } else {
+      selected.value.push({
+        name: currentProvince.name,
+        children: [{
+          name: '不限',
+          children: []
+        }]
+      });
+    }
+    return;
+  } else {
+    const currentProvince = list.value[currentIndex.value];
+    const currentSelected = selected.value.find(province => province.name === currentProvince.name);
+    if (currentSelected) {
+      currentSelected.children = currentSelected!.children.filter(city => city.name !== '不限');
+      if (currentSelected.children.findIndex(city => city.name === item.name) === -1) {
+        currentSelected.children.push(item);
+      } else {
+        currentSelected.children = currentSelected.children.filter(city => city.name !== item.name);
+        // 如果没有选中的,就自动选中不限
+        if (currentSelected.children.length === 0) {
+          currentSelected.children.push({
+            name: '不限',
+            parent: currentProvince.name,
+            children: []
+          });
+        }
+      }
+    } else {
+      selected.value.push({
+        name: currentProvince.name,
+        children: [item]
+      });
+    }
+  }
+}
+const loadData = () => {
+  getUniversityLocation().then(res => {
+    list.value = res.data.map(item => {
+      return {
+        name: item.location,
+        children: [
+          {
+            name: '不限',
+            parent: item.location,
+            children: []
+          },
+          ...item.cityName.map(city => {
+            return {
+              name: city,
+              parent: item.location,
+              children: []
+            }
+          })
+        ]
+      }
+    });
+  });
+}
+
+onMounted(() => {
+  selectedProvince.value = '';
+  selectedCity.value = [];
+  currentIndex.value = 0;
+});
+loadData();
+</script>
+<style lang="scss" scoped></style>

+ 100 - 140
src/pagesOther/pages/university/index/components/plus/college-conditions-picker.vue

@@ -1,164 +1,124 @@
 <template>
-    <view>
-        <ie-dropdown :configs="configs" v-model="form" :absolute="absolute" @change="handleChange"/>
-    </view>
+  <view>
+    <ie-dropdown :configs="configs" v-model="form" :absolute="absolute" @change="handleChange">
+      <template #location="{ index }">
+        <college-conditions-location :index="index" />
+      </template>
+    </ie-dropdown>
+  </view>
 </template>
 
 <script setup lang="ts">
-
-import type {Dropdown} from "@/types";
-import {UNIVERSITY_FILTER} from "@/types/injectionSymbols";
-import type {UniversityQueryDto, UniversityFilter} from "@/types/university";
-import {useUserStore} from "@/store/userStore";
+import CollegeConditionsLocation from "./college-conditions-location.vue";
+import type { Dropdown } from "@/types";
+import { UNIVERSITY_FILTER } from "@/types/injectionSymbols";
+import type { UniversityQueryDto, UniversityFilter } from "@/types/university";
+import { useUserStore } from "@/store/userStore";
 
 const props = defineProps<{
-    options: UniversityFilter;
-    absolute?: boolean;
+  options: UniversityFilter;
+  absolute?: boolean;
 }>()
 const filter = inject(UNIVERSITY_FILTER) || ref({} as UniversityQueryDto)
 const userStore = useUserStore()
 
 const emit = defineEmits<{
-    (e: 'change', value: UniversityQueryDto): void;
+  (e: 'change', value: UniversityQueryDto): void;
 }>();
 const options = computed(() => {
-    // return userStore.isHN ? [
-    //     {
-    //         label: '院校层次',
-    //         prop: 'features',
-    //         optionKey: 'features',
-    //         keyName: 'label',
-    //         keyValue: 'value',
-    //     },
-    //     {
-    //         label: '院校类型',
-    //         prop: 'type',
-    //         optionKey: 'types',
-    //         keyName: 'label',
-    //         keyValue: 'value',
-    //     },
-    //     {
-    //         label: '办学类型',
-    //         prop: 'natureTypeCN',
-    //         optionKey: 'natureTypes',
-    //         keyName: 'label',
-    //         keyValue: 'value',
-    //     },
-    //     {
-    //         label: '院校梯队',
-    //         prop: 'tiers',
-    //         optionKey: 'tiers',
-    //         keyName: 'label',
-    //         keyValue: 'value',
-    //     },
-    // ] : [
-    //     {
-    //         label: '院校层次',
-    //         prop: 'features',
-    //         optionKey: 'features',
-    //         keyName: 'label',
-    //         keyValue: 'value',
-    //     },
-    //     {
-    //         label: '院校类型',
-    //         prop: 'type',
-    //         optionKey: 'types',
-    //         keyName: 'label',
-    //         keyValue: 'value',
-    //     },
-    //     {
-    //         label: '办学类型',
-    //         prop: 'natureTypeCN',
-    //         optionKey: 'natureTypes',
-    //         keyName: 'label',
-    //         keyValue: 'value',
-    //     },
-    //     {
-    //         label: '院校省份',
-    //         prop: 'location',
-    //         optionKey: 'locations',
-    //         keyName: 'label',
-    //         keyValue: 'value',
-    //     }
-    // ]
-    return [
-        {
-            label: '院校省市',
-            prop: 'location',
-            optionKey: 'locations',
-            keyName: 'label',
-            keyValue: 'value',
-        },
-        {
-            label: '院校类型',
-            prop: 'type',
-            optionKey: 'types',
-            keyName: 'label',
-            keyValue: 'value',
-        },
-        {
-            label: '办学类型',
-            prop: 'natureTypeCN',
-            optionKey: 'natureTypes',
-            keyName: 'label',
-            keyValue: 'value',
-        },
-        {
-            label: '院校梯队',
-            prop: 'tiers',
-            optionKey: 'tiers',
-            keyName: 'label',
-            keyValue: 'value',
-        }
-    ]
+  return [
+    {
+      label: '院校省市',
+      prop: 'location',
+      optionKey: 'locations',
+      keyName: 'label',
+      keyValue: 'value',
+      slot: 'location',
+    },
+    {
+      label: '院校类型',
+      prop: 'type',
+      optionKey: 'types',
+      keyName: 'label',
+      keyValue: 'value'
+    },
+    {
+      label: '办学类型',
+      prop: 'natureTypeCN',
+      optionKey: 'natureTypes',
+      keyName: 'label',
+      keyValue: 'value'
+    },
+    {
+      label: '院校梯队',
+      prop: 'tiers',
+      optionKey: 'tiers',
+      keyName: 'label',
+      keyValue: 'value'
+    }
+  ]
 })
 const form = ref<Record<string, any>>({});
 const configs = computed<Dropdown.DropdownItem[]>(() => {
-    return options.value.map(item => {
-        const list = props.options[item.optionKey as keyof UniversityFilter] || [];
-        if (Array.isArray(list)) {
-            return {
-                label: item.label,
-                prop: item.prop,
-                keyName: item.keyName,
-                keyValue: item.keyValue,
-                options: list.map(item => {
-                    return {
-                        label: item,
-                        value: item,
-                    }
-                }),
-                value: []
-            }
-        } else {
-            return {
-                label: item.label,
-                prop: item.prop,
-                keyName: item.keyName,
-                keyValue: item.keyValue,
-                options: Object.entries(list).map(([key, value]) => {
-                    return {
-                        label: value,
-                        value: key,
-                    }
-                }),
-                value: []
-            }
-        }
-    });
+  return options.value.map(item => {
+    const list = props.options[item.optionKey as keyof UniversityFilter] || [];
+    if (Array.isArray(list)) {
+      return {
+        label: item.label,
+        prop: item.prop,
+        keyName: item.keyName,
+        keyValue: item.keyValue,
+        options: list.map(item => {
+          return {
+            label: item,
+            value: item,
+          }
+        }),
+        value: [],
+        slot: item.slot,
+      }
+    } else {
+      return {
+        label: item.label,
+        prop: item.prop,
+        keyName: item.keyName,
+        keyValue: item.keyValue,
+        options: Object.entries(list).map(([key, value]) => {
+          return {
+            label: value,
+            value: key,
+          }
+        }),
+        value: [],
+        slot: item.slot,
+      }
+    }
+  });
 });
 const handleChange = (value: any) => {
-    filter.value = {
-        ...filter.value,
-        ...value,
-    }
-    emit('change', filter.value);
+  let locationParams: { location: string, cityName: string[] }[] = [];
+  if (value.location) {
+    locationParams = value.location.split(';').filter((item: string) => item).map((item: string) => {
+      return {
+        location: item.split(':')[0],
+        cityName: item.split(':')[1].split(',').filter(city => city !== '不限'),
+      }
+    });
+  }
+  filter.value = {
+    ...filter.value,
+    ...value,
+    // 省市新的参数要求:location: 湖南,河南; cityName: 长沙,张家界,开封,郑州;
+    location: locationParams.map(item => item.location).join(','),
+    cityName: locationParams.map(item => item.cityName).flat().join(','),
+  }
+  emit('change', filter.value);
 }
 
 watch(() => filter.value.tiers, tiers => {
-    // sync tier from outside
-    form.value.tiers = tiers
-}, {immediate: true})
+  // sync tier from outside
+  form.value.tiers = tiers
+}, { immediate: true })
 </script>
 
 <style scoped></style>

+ 2 - 0
src/types/dropdown.ts

@@ -6,6 +6,8 @@ export interface DropdownItem {
   keyName?: string;
   keyValue?: string;
   options?: DropdownOption[];
+  custom?: boolean;
+  slot?: string;
 }
 
 export interface DropdownOption {