1
0

2 Ревизии 8dc6d9bee0 ... 0ec3a83c6d

Автор SHA1 Съобщение Дата
  abpcoder 0ec3a83c6d vhs - form init tmp save преди 4 седмици
  abpcoder 2ebcc3c21b vhs - form init tmp save преди 4 седмици
променени са 33 файла, в които са добавени 954 реда и са изтрити 105 реда
  1. 0 5
      src/api/modules/vhs.ts
  2. 24 24
      src/pages.json
  3. 39 0
      src/pagesOther/components/ie-bottom-buttons/ie-bottom-buttons.vue
  4. 60 0
      src/pagesOther/components/ie-condition-dropdown/ie-condition-dropdown-item.vue
  5. 130 0
      src/pagesOther/components/ie-condition-dropdown/ie-condition-dropdown-popup.vue
  6. 48 0
      src/pagesOther/components/ie-condition-dropdown/ie-condition-dropdown.vue
  7. 13 0
      src/pagesOther/components/ie-condition-dropdown/useConditionDropdownPopupInjection.js
  8. 22 0
      src/pagesOther/components/ie-condition/ie-condition-dropdown.vue
  9. 21 0
      src/pagesOther/components/ie-condition/ie-condition.vue
  10. 31 0
      src/pagesOther/components/ie-condition/modules/conditionSharedConfig.js
  11. 12 0
      src/pagesOther/components/ie-condition/modules/useConditionCollegeFeatures.js
  12. 12 0
      src/pagesOther/components/ie-condition/modules/useConditionCollegeLevel.js
  13. 12 0
      src/pagesOther/components/ie-condition/modules/useConditionCollegeLocation.js
  14. 12 0
      src/pagesOther/components/ie-condition/modules/useConditionCollegeNatureTypeCN.js
  15. 12 0
      src/pagesOther/components/ie-condition/modules/useConditionCollegeType.js
  16. 15 0
      src/pagesOther/components/ie-condition/modules/useConditionPickType.js
  17. 60 0
      src/pagesOther/components/ie-condition/useConditionDataManager.js
  18. 72 0
      src/pagesOther/components/ie-condition/useConditionEventManager.js
  19. 108 0
      src/pagesOther/components/ie-condition/useConditionFactory.js
  20. 43 0
      src/pagesOther/components/ie-condition/useSearchModelInjection.js
  21. 131 0
      src/pagesOther/components/ie-vhs-picker/ie-vhs-picker.vue
  22. 1 1
      src/pagesOther/pages/vhs/hooks/useVoluntaryAssistantInjection.js
  23. 1 1
      src/pagesOther/pages/vhs/hooks/useVoluntaryCartInjection.js
  24. 1 1
      src/pagesOther/pages/vhs/hooks/useVoluntaryFormInjection.js
  25. 1 1
      src/pagesOther/pages/vhs/hooks/useVoluntaryHeaderInjection.js
  26. 1 1
      src/pagesOther/pages/vhs/hooks/useVoluntaryMajorHighlightInjection.js
  27. 11 8
      src/pagesOther/pages/vhs/hooks/useVoluntarySearchInjection.js
  28. 1 1
      src/pagesOther/pages/vhs/hooks/useVoluntaryStepInjection.js
  29. 39 40
      src/pagesOther/pages/vhs/index/components/cart-step.vue
  30. 1 1
      src/pagesOther/pages/vhs/index/components/voluntary-history-list.vue
  31. 16 17
      src/pagesOther/pages/vhs/index/components/voluntary-item.vue
  32. 2 2
      src/pagesOther/pages/vhs/index/components/voluntary-search.vue
  33. 2 2
      src/pagesOther/pages/vhs/index/index.vue

+ 0 - 5
src/api/modules/vhs.ts

@@ -54,11 +54,6 @@ export function getZhiyuanDetail(wishResId: number) {
     return flyio.get('/front/syzy/zytb/recordDetail', {wishResId}) as Promise<ApiResponse<VoluntaryEntity>>
 }
 
-// 填报批次  筛选条件
-export function universityFilters(params: any) {
-    return flyio.get('/front/syzy/zytb/university/filters', params) as Promise<ApiResponse<UniversityFilter>>
-}
-
 
 // 查询我的志愿表
 export function selectZytbRecord(params: any) {

+ 24 - 24
src/pages.json

@@ -195,30 +195,30 @@
             "navigationBarTitleText": ""
           }
         },
-         // {
-         //   "path": "pages/vhs/index/index",
-         //   "style": {
-         //     "navigationBarTitleText": ""
-         //   }
-         // },
-         // {
-         //   "path": "pages/vhs/detail/detail",
-         //   "style": {
-         //     "navigationBarTitleText": ""
-         //   }
-         // },
-         // {
-         //   "path": "pages/vhs/edit/edit",
-         //   "style": {
-         //     "navigationBarTitleText": ""
-         //   }
-         // },
-         // {
-         //   "path": "pages/vhs/list/list",
-         //   "style": {
-         //     "navigationBarTitleText": ""
-         //   }
-         // },
+         {
+           "path": "pages/vhs/index/index",
+           "style": {
+             "navigationBarTitleText": ""
+           }
+         },
+//         {
+//           "path": "pages/vhs/detail/detail",
+//           "style": {
+//             "navigationBarTitleText": ""
+//           }
+//         },
+//         {
+//           "path": "pages/vhs/edit/edit",
+//           "style": {
+//             "navigationBarTitleText": ""
+//           }
+//         },
+//         {
+//           "path": "pages/vhs/list/list",
+//           "style": {
+//             "navigationBarTitleText": ""
+//           }
+//         },
         {
           "path": "pages/ranking/ranking",
           "style": {

+ 39 - 0
src/pagesOther/components/ie-bottom-buttons/ie-bottom-buttons.vue

@@ -0,0 +1,39 @@
+<template>
+    <view v-if="left||right" class="flex gap-30" :class="containerClass">
+        <uv-button v-if="left" :text="left" :type="leftType" :icon="leftIcon" :icon-color="leftType" plain
+                   v-bind="buttonStyle" @click="$emit('left')"/>
+        <uv-button v-if="right" :text="right" :type="rightType" :icon="rightIcon" icon-color="white"
+                   :loading="loading" v-bind="buttonStyle" @click="$emit('right')"/>
+    </view>
+</template>
+
+<script setup>
+import {computed} from 'vue';
+import {createPropDefine} from "@/utils";
+
+const props = defineProps({
+    // left 和 right 必须提供值才会显示
+    left: createPropDefine('取消'),
+    leftIcon: createPropDefine(undefined),
+    right: createPropDefine('保存'),
+    rightIcon: createPropDefine(undefined),
+    leftType: createPropDefine('error'),
+    rightType: createPropDefine('primary'),
+    loading: createPropDefine(false, Boolean)
+})
+defineEmits(['left', 'right'])
+
+const single = computed(() => !props.left || !props.right)
+const buttonStyle = computed(() => ({
+    customStyle: {
+        height: '44px',
+    },
+    shape: 'circle',
+    class: single.value ? '!w-1/2' : '!flex-1'
+}))
+const containerClass = computed(() => single.value ? 'fx-cen-cen' : 'fx-bet-cen')
+</script>
+
+<style scoped>
+
+</style>

+ 60 - 0
src/pagesOther/components/ie-condition-dropdown/ie-condition-dropdown-item.vue

@@ -0,0 +1,60 @@
+<template>
+    <view class="flex items-center gap-5" @click="handleClick" @touchmove.stop>
+        <text class="whitespace-nowrap relative" :class="{'text-primary': opening, 'text-fired': fired}">
+            {{ display }}
+        </text>
+        <uv-icon :name="opening?'arrow-up':'arrow-down'" :color="opening?'primary':undefined"/>
+    </view>
+</template>
+
+<script setup>
+import {computed} from 'vue';
+import {createPropDefine} from "@/utils";
+import {
+    useInjectConditionDropdownPopup
+} from "@/pagesOther/components/ie-condition-dropdown/useConditionDropdownPopupInjection";
+import {useInjectSearchModel} from "@/pagesOther/components/ie-condition/useSearchModelInjection";
+
+const props = defineProps({
+    condition: createPropDefine({}, Object),
+    useValueAsTitle: createPropDefine(false, Boolean)
+})
+
+const {popup} = useInjectConditionDropdownPopup()
+const {queryParams} = useInjectSearchModel()
+const list = computed(() => props.condition.list)
+const config = computed(() => props.condition.config)
+const current = computed(() => toValue(queryParams)[config.value.key])
+const currentItems = computed(() => list.value.filter(i => [].concat(current.value).includes(getValue(i))))
+const display = computed(() => {
+    if (props.useValueAsTitle && currentItems.value.length == 1) return getLabel(currentItems.value[0])
+    return config.value.title
+})
+const fired = computed(() => {
+    // 命中的是默认条件
+    if (currentItems.value.length == 1 && !getValue(currentItems.value[0])) return false
+    // 有条件命中了
+    return !props.useValueAsTitle && currentItems.value.length > 0
+})
+const opening = computed(() => popup.value?.condition == props.condition) // 当前弹窗打开
+
+const getLabel = (item) => config.value.keyName ? item[config.value.keyName] : item
+const getValue = (item) => config.value.keyValue ? item[config.value.keyValue] : item
+
+const handleClick = () => {
+    popup.value.open(props.condition)
+}
+</script>
+
+<style scoped lang="scss">
+.text-fired::before {
+    content: ' ';
+    position: absolute;
+    top: 0;
+    right: -3px;
+    width: 6px;
+    height: 6px;
+    border-radius: 3px;
+    background-color: var(--error-light-color);
+}
+</style>

+ 130 - 0
src/pagesOther/components/ie-condition-dropdown/ie-condition-dropdown-popup.vue

@@ -0,0 +1,130 @@
+<template>
+    <uv-popup ref="popup" mode="top" :safe-area-inset-bottom="false" :offset-top="relTop" @change="handleChange">
+        <scroll-view scroll-y style="max-height: 35vh;">
+            <view class="w-screen px-30 py-10 box-border" @touchmove.stop>
+                <uv-checkbox-group v-if="multiple" v-model="model[config.key]" placement="column" icon-placement="right">
+                    <uv-checkbox v-for="i in list" :name="getValue(i)" :label="getLabel(i)"/>
+                </uv-checkbox-group>
+                <uv-radio-group v-else v-model="model[config.key]" placement="column" icon-placement="right">
+                    <uv-radio v-for="i in list" :name="getValue(i)" :label="getLabel(i)"/>
+                </uv-radio-group>
+            </view>
+        </scroll-view>
+        <ie-bottom-buttons left-type="primary" left="重置" :right="right" class="p-30"
+                           @left="handleReset" @right="handleConfirm"/>
+    </uv-popup>
+</template>
+
+<script setup>
+import {deepClone, sleep} from "@/uni_modules/uv-ui-tools/libs/function";
+import IeBottomButtons from "@/pagesOther/components/ie-bottom-buttons/ie-bottom-buttons.vue";
+import UvCheckboxGroup from "@/uni_modules/uv-checkbox/components/uv-checkbox-group/uv-checkbox-group.vue";
+import UvRadioGroup from "@/uni_modules/uv-radio/components/uv-radio-group/uv-radio-group.vue";
+import UvCheckbox from "@/uni_modules/uv-checkbox/components/uv-checkbox/uv-checkbox.vue";
+import UvRadio from "@/uni_modules/uv-radio/components/uv-radio/uv-radio.vue";
+import {func} from "@/uni_modules/uv-ui-tools/libs/function/test";
+import {useInjectSearchModel} from "@/pagesOther/components/ie-condition/useSearchModelInjection";
+import {
+    useInjectConditionDropdownPopup
+} from "@/pagesOther/components/ie-condition-dropdown/useConditionDropdownPopupInjection";
+// #ifdef H5
+import {useElementBounding} from "@vueuse/core";
+// #endif
+
+const popup = ref(null)
+const {bottom, container} = useInjectConditionDropdownPopup()
+const {queryParams} = useInjectSearchModel()
+const model = ref({}) // 存放queryParams副本
+const show = ref(false)
+
+const condition = ref(null)
+const list = computed(() => condition.value?.list || [])
+const config = computed(() => condition.value?.config || {})
+const multiple = computed(() => config.value?.multiple)
+
+const right = computed(() => {
+    if (!multiple.value) return '确定'
+    const cur = model.value[config.value.key]
+    if (cur.length < 1) return '确定'
+    return `确定(${cur.length})`
+})
+
+// #ifdef H5
+// TODO: 相对位置不精准
+// baseTop 理论上应该找当前容器的值,但目前筛选器是紧贴swiper容器的,所以取的是筛选器的顶部
+// 如果以后有定位不对的情况,请修正该值
+const {top: baseTop} = useElementBounding(container)
+const {bottom: baseBottom} = useElementBounding(bottom)
+const relTop = computed(() => baseBottom.value - baseTop.value - 1)
+// #endif
+// #ifdef MP-WEIXIN
+const refTop = ref(44 - 1)
+// #endif
+
+const handleReset = () => {
+    // 重置本地model状态
+    if (multiple.value) model.value[config.value.key] = []
+    else model.value[config.value.key] = ''
+    if (config.value.defaultValue) {
+        let val = config.value.defaultValue
+        if (func(val)) val = val(condition.value)
+        model.value[config.value.key] = val
+    }
+}
+
+const handleConfirm = () => {
+    // 将本地model状态赋值回queryParams
+    const key = config.value.key
+    queryParams.value[key] = model.value[key]
+    close()
+}
+
+const open = function (cond) {
+    // console.log('open begin', new Date().getTime())
+    if (cond == condition.value && show.value) {
+        popup.value.close()
+        // console.log('open end', new Date().getTime())
+    } else {
+        condition.value = cond
+        // 每次打开都创建一个副本,用户确认后才将结果反向从model赋回queryParams
+        model.value = deepClone(queryParams.value)
+        popup.value.open()
+        // console.log('open end', new Date().getTime())
+    }
+}
+
+const close = function () {
+    // console.log('close begin', new Date().getTime())
+    popup.value.close()
+    // console.log('close end', new Date().getTime())
+}
+
+const handleChange = async function (e) {
+    // console.log('change begin', new Date().getTime())
+    show.value = e.show
+    await sleep()
+    if (!show.value) {
+        // always clean current condition&model while hidden.
+        condition.value = null
+        model.value = {}
+    }
+    // console.log('change end', new Date().getTime())
+}
+
+const getLabel = (item) => config.value.keyName ? item[config.value.keyName] : item
+const getValue = (item) => config.value.keyValue ? item[config.value.keyValue] : item
+
+defineExpose({open, close, show, condition})
+</script>
+
+<style scoped lang="scss">
+::v-deep(.uv-checkbox-group),
+::v-deep(.uv-radio-group) {
+    .uv-checkbox-label--right,
+    .uv-radio-label--right {
+        padding-top: 20rpx;
+        padding-bottom: 20rpx;
+        border-bottom: 0.5px solid var(--border-color);
+    }
+}
+</style>

+ 48 - 0
src/pagesOther/components/ie-condition-dropdown/ie-condition-dropdown.vue

@@ -0,0 +1,48 @@
+<template>
+    <scroll-view :scroll-x="x" :style="{zIndex: z}">
+        <view class="bg-white h-[44px] text-xs text-main px-30" :class="layout">
+            <ie-condition-dropdown-item v-for="c in conditions" :condition="c" :use-value-as-title="useValueAsTitle"/>
+        </view>
+    </scroll-view>
+    <ie-condition-dropdown-popup ref="popup"/>
+    <uv-line ref="bottom" :style="{zIndex: z}"/>
+</template>
+
+<script setup>
+/*TODO:暂不要给ie-condition-dropdown套uv-form,会导致zIndex失效,待改善*/
+import {createPropDefine} from "@/utils";
+import {findAncestorComponentByName} from "@/utils/uni-helper";
+import {useInjectSearchModel} from "@/pagesOther/components/ie-condition/useSearchModelInjection";
+import {
+    useProvideConditionDropdownPopup
+} from "@/pagesOther/components/ie-condition-dropdown/useConditionDropdownPopupInjection";
+import IeConditionDropdownItem from "@/pagesOther/components/ie-condition-dropdown/ie-condition-dropdown-item.vue";
+import IeConditionDropdownPopup from "@/pagesOther/components/ie-condition-dropdown/ie-condition-dropdown-popup.vue";
+
+const props = defineProps({
+    border: createPropDefine(false, Boolean),
+    layout: createPropDefine('fx-row fx-bet-cen'),
+    useValueAsTitle: createPropDefine(false, Boolean),
+    x: createPropDefine(false, Boolean),
+    z: createPropDefine(20000, Number) // 防止被弹层遮挡
+})
+
+const bottom = ref(null) // 最底部的元素,用于定位弹层
+const popup = ref(null) // 弹层元素,将贴合bottom往下弹出
+// relative容器元素,确定弹出层的相对位置 // TODO:理论上这里应该使用外部的swiper
+const container = ref(null)
+const {conditions} = useInjectSearchModel()
+useProvideConditionDropdownPopup(popup, bottom, container)
+
+onMounted(() => {
+    const instance = getCurrentInstance()
+    const swiper = findAncestorComponentByName(instance, 'Swiper')
+    container.value = swiper || document.getElementById('app')
+})
+
+defineExpose({terminate: () => popup.value.close(), getShow: () => popup.value.show})
+</script>
+
+<style scoped>
+
+</style>

+ 13 - 0
src/pagesOther/components/ie-condition-dropdown/useConditionDropdownPopupInjection.js

@@ -0,0 +1,13 @@
+import {injectLocal, provideLocal} from "@vueuse/shared";
+
+const key = Symbol('CONDITION_DROPDOWN_POPUP')
+
+export const useProvideConditionDropdownPopup = (refOrGetter, bottomEleRef, containerEleRef) => {
+    const options = {popup: refOrGetter, bottom: bottomEleRef, container: containerEleRef}
+    provideLocal(key, options)
+    return options
+}
+
+export const useInjectConditionDropdownPopup = function () {
+    return injectLocal(key)
+}

+ 22 - 0
src/pagesOther/components/ie-condition/ie-condition-dropdown.vue

@@ -0,0 +1,22 @@
+<template>
+    <uv-drop-down ref="dropdown">
+        <uv-drop-down-item v-for="c in conditions" :label="c.config.title"/>
+        <uv-drop-down-popup/>
+    </uv-drop-down>
+</template>
+
+<script setup>
+import {ref, watch} from 'vue';
+import {useInjectPageScroll} from "@/hooks/usePageScrollInjection";
+import {useInjectSearchModel} from "@/components/mx-condition/useSearchModelInjection";
+
+const {scrollTop} = useInjectPageScroll()
+const {conditions} = useInjectSearchModel()
+const dropdown = ref(null)
+
+watch(scrollTop, () => dropdown.value.init()) // 位置修正
+</script>
+
+<style scoped>
+
+</style>

+ 21 - 0
src/pagesOther/components/ie-condition/ie-condition.vue

@@ -0,0 +1,21 @@
+<template>
+    <view class="h-90 flex items-center gap-40">
+        <view v-for="c in conditions" :prop="c.config.key">
+            <ie-vhs-picker v-model="queryParams[c.config.key]" :data="c.list"
+                       :label-prop="c.config.keyName" :value-prop="c.config.keyValue"
+                       :empty-display="c.config.title" class="!flex-none"/>
+        </view>
+    </view>
+</template>
+
+<script setup>
+
+import {useInjectSearchModel} from "@/pagesOther/components/ie-condition/useSearchModelInjection";
+import IeVhsPicker from "@/pagesOther/components/ie-vhs-picker/ie-vhs-picker.vue";
+
+const {conditions, queryParams} = useInjectSearchModel()
+</script>
+
+<style scoped>
+
+</style>

+ 31 - 0
src/pagesOther/components/ie-condition/modules/conditionSharedConfig.js

@@ -0,0 +1,31 @@
+import {fnPlaceholder} from "@/utils/uni-helper";
+
+export const conditionSharedConfig = {
+    key: '',
+    // 标题名称
+    title: '',
+    // 如果queryParams参数中未初始化,是否在请求后自动初始化,初始化也和required相关,如果required=false
+    autoInit: true,
+    // 如果required=false,
+    allLabel: '不限',
+    // 获取数据的方法
+    handler: fnPlaceholder,
+    // 依赖项,在其之前dependentKeys必须准备好,将作为watch依据
+    dependentKeys: [],
+    // 非依赖项,不要求一定具备,但会和依赖项一起作用handler的请求参数
+    independentKeys: [],
+    // 如果是对象,则配置keyName作为显示用
+    keyName: '',
+    // 如果是对象,则配置keyValue作为传值用
+    keyValue: '',
+    // 校验规则,uv-form validation rule,array or object
+    required: false, // required 会自动生成非空校验,主要是方便用户改写这个属性
+    rule: [], // 如果有了required=true,这里就不需要重复添加非空校验了
+    // 隐藏,只是不显示(渲染),还是在工作的。
+    hidden: false,
+    // 多选
+    multiple: false,
+    // 重置时的默认条件 也可以配置方法// function(condition)
+    // TODO: 目前只用在了重置功能上,理论上也可以用在autoInit上,看后面的需要
+    defaultValue: ''
+}

+ 12 - 0
src/pagesOther/components/ie-condition/modules/useConditionCollegeFeatures.js

@@ -0,0 +1,12 @@
+import {conditionSharedConfig} from "@/pagesOther/components/ie-condition/modules/conditionSharedConfig";
+
+export const useConditionCollegeFeatures = function (featuresRefOrGetter, options = {}) {
+    return {
+        ...conditionSharedConfig,
+        handler: () => featuresRefOrGetter,
+        key: 'features',
+        title: '院校层次',
+        allLabel: '', // 不需要填充`全部`
+        multiple: true
+    }
+}

+ 12 - 0
src/pagesOther/components/ie-condition/modules/useConditionCollegeLevel.js

@@ -0,0 +1,12 @@
+import {conditionSharedConfig} from "@/pagesOther/components/ie-condition/modules/conditionSharedConfig";
+
+export const useConditionCollegeLevel = function (refOrGetter, options = {}) {
+    return {
+        ...conditionSharedConfig,
+        handler: () => refOrGetter,
+        key: 'level',
+        title: '学历层次',
+        allLabel: '', // 不需要填充`全部`
+        multiple: true
+    }
+}

+ 12 - 0
src/pagesOther/components/ie-condition/modules/useConditionCollegeLocation.js

@@ -0,0 +1,12 @@
+import {conditionSharedConfig} from "@/pagesOther/components/ie-condition/modules/conditionSharedConfig";
+
+export const useConditionCollegeLocation = function (refOrGetter, options = {}) {
+    return {
+        ...conditionSharedConfig,
+        handler: () => refOrGetter,
+        key: 'location',
+        title: '院校省份',
+        allLabel: '', // 不需要填充`全部`
+        multiple: true
+    }
+}

+ 12 - 0
src/pagesOther/components/ie-condition/modules/useConditionCollegeNatureTypeCN.js

@@ -0,0 +1,12 @@
+import {conditionSharedConfig} from "@/pagesOther/components/ie-condition/modules/conditionSharedConfig";
+
+export const useConditionCollegeNatureTypeCN = function (refOrGetter, options = {}) {
+    return {
+        ...conditionSharedConfig,
+        handler: () => refOrGetter,
+        key: 'natureTypeCN',
+        title: '办学类型',
+        allLabel: '', // 不需要填充`全部`
+        multiple: true
+    }
+}

+ 12 - 0
src/pagesOther/components/ie-condition/modules/useConditionCollegeType.js

@@ -0,0 +1,12 @@
+import {conditionSharedConfig} from "@/pagesOther/components/ie-condition/modules/conditionSharedConfig";
+
+export const useConditionCollegeType = function (typeRefOrGetter, options = {}) {
+    return {
+        ...conditionSharedConfig,
+        handler: () => typeRefOrGetter,
+        key: 'type',
+        title: '院校类型',
+        allLabel: '', // 不需要填充`全部`
+        multiple: true
+    }
+}

+ 15 - 0
src/pagesOther/components/ie-condition/modules/useConditionPickType.js

@@ -0,0 +1,15 @@
+import {conditionSharedConfig} from "@/pagesOther/components/ie-condition/modules/conditionSharedConfig";
+import {SimulatePickTypes} from "@/utils/common";
+
+export const useConditionPickType = function (options = {}) {
+    return {
+        ...conditionSharedConfig,
+        key: 'pickType',
+        title: '概率筛选',
+        handler: () => SimulatePickTypes,
+        immediate: true,
+        keyName: 'text',
+        keyValue: 'value',
+        allLabel: '所有'
+    }
+}

+ 60 - 0
src/pagesOther/components/ie-condition/useConditionDataManager.js

@@ -0,0 +1,60 @@
+import _ from "lodash";
+import {empty} from "@/uni_modules/uv-ui-tools/libs/function/test";
+import {useConditionFactory} from "@/pagesOther/components/ie-condition/useConditionFactory";
+
+export const useConditionDataManager = function (configs, eventManager, queryParams, sharedData, console) {
+
+    const container = ref(new Map())
+    const rules = ref({})
+
+    watch(queryParams, async () => {
+        console.log('dataManager begin init', new Date().getTime())
+        const keys = Object.keys(queryParams.value)
+        if (!keys.length) return
+        const keysConfig = configs.map(c => c.key)
+        const missingKeys = _.difference(keys, keysConfig)
+        if (missingKeys.length) console.warn('missing keys: ' + missingKeys.toString())
+
+        // 没有被依赖的属性需要在改变时触发查询
+        // 依赖是在config中指定的,但是否没有被依赖需要遍历所有条件才能知道
+        const keysNotBeDependent = [...keys]
+        keys.forEach(key => {
+            const config = configs.find(c => c.key == key)
+            if (!config) return _.pull(keysNotBeDependent, key)
+            _.pull(keysNotBeDependent, ...config.dependentKeys)
+
+            const condition = useConditionFactory(config, eventManager, queryParams, sharedData, console)
+            container.value.set(key, condition)
+
+            // useConditionFactory可能会对规则进行加工
+            if (!empty(config.rule)) rules.value[config.key] = config.rule
+
+            // 必须条件立马加入workingList,防止事件过早触发
+            if (condition.config.required) eventManager.push('dataManager init of required', key)
+        })
+
+        // 一般来说必须得有1个非被依赖项,才能构成一个响应链,否则没有机会触发onSearch
+        // TODO:这里只是当1个警告,实际运行效果还待评估
+        if (!keysNotBeDependent.length) console.warn('At least one key must not be dependent!')
+        keysNotBeDependent.forEach(k => {
+            watch(() => toValue(queryParams)[k], () => eventManager.trigger('dataManager watch not be dependent of ' + k))
+        })
+    }, {immediate: true})
+
+    const conditions = computed(() => [...container.value.values()])
+    const filterConditions = computed(() => conditions.value.filter(c => !c.config.hidden))
+    const needValidation = computed(() => !empty(rules.value))
+
+    const reset = function () {
+        container.value.clear()
+        rules.value = {}
+    }
+
+    return {
+        container,
+        conditions: filterConditions,
+        rules,
+        needValidation,
+        reset
+    }
+}

+ 72 - 0
src/pagesOther/components/ie-condition/useConditionEventManager.js

@@ -0,0 +1,72 @@
+
+import _ from "lodash";
+import {createEventHook} from "@vueuse/shared";
+import {empty} from "@/uni_modules/uv-ui-tools/libs/function/test";
+
+export const useConditionEventManager = function (queryParams, validator, console) {
+    // 工作列表,工作列表清空时,表示条件已经准备好了
+    const workingList = ref([])
+    // 初次完成标识
+    const initialized = ref(false)
+    // 外抛事件,一般事件
+    const searchEvent = createEventHook()
+    // 初始化成功时的外抛事件,会优先于searchEvent外抛
+    const initEvent = createEventHook()
+
+    const push = function (reason, ...keys) {
+        workingList.value.push(...keys)
+        console.log('eventManager push reason:', reason, ', key:', keys + '', ', result:', workingList.value + '')
+    }
+
+    const pop = async (reason, key) => {
+        if (workingList.value.includes(key)) {
+            _.pull(workingList.value, key)
+            console.log('eventManager pop reason:', reason, ', key:', key, ', result:', workingList.value + '')
+
+            await trigger('working list check')
+        }
+    }
+
+    // loadData 引发的pop 与 dataManager 非被依赖项watch 有时会并发触发trigger
+    // 用triggerLock来控制并发只执行一次,TODO:待观察看有没有更好的方式
+    let triggerLock = false
+    const trigger = async (reason) => {
+        if (triggerLock) {
+            console.log('eventManager trigger ignore by lock,reason', reason)
+            return
+        }
+        triggerLock = true
+        console.log('eventManager trigger reason:', reason)
+
+        try {
+            // check trigger event
+            if (empty(workingList.value) && !empty(queryParams.value)) {
+                // 看当前是否数据有效
+                await validator.value()
+
+                if (!initialized.value) {
+                    console.log('eventManager init trigger')
+                    await initEvent.trigger()
+                    initialized.value = true
+                }
+                console.log('eventManager search trigger', new Date().getTime())
+                await searchEvent.trigger()
+            }
+        } finally {
+            triggerLock = false
+        }
+    }
+
+    const reset = function () {
+        workingList.value.length = 0
+    }
+
+    return {
+        push,
+        pop,
+        trigger,
+        reset,
+        onSearch: searchEvent.on,
+        onInit: initEvent.on
+    }
+}

+ 108 - 0
src/pagesOther/components/ie-condition/useConditionFactory.js

@@ -0,0 +1,108 @@
+
+import _ from "lodash";
+import {empty, func} from "@/uni_modules/uv-ui-tools/libs/function/test";
+import {fnPlaceholder} from "@/utils/uni-helper";
+
+
+// 一般情况下用queryParams即可,但有可能有些全局参数不在queryParams里面,则用sharedData注入进来
+// 通过dependentKeys和independentKeys,获取请求必须的参数
+// dependentKeys是必须参数,independentKeys是非必须参数
+export const useConditionFactory = function (config, eventManager, queryParams, sharedData, console) {
+    const {
+        key, handler, autoInit, required, rule, allLabel, title, multiple,
+        dependentKeys, independentKeys, keyName, keyValue
+    } = config
+
+    if (empty(key)) throw new Error('condition must define a key')
+    if (!func(handler)) throw new Error('handler must be a function')
+
+    const list = ref([])
+    if (required && !rule.some(r => r.required)) {
+        // 没有必填规则就自动创建一条
+        const requiredRule = {required: true, message: `${title}不能为空`, transform: (v) => v + ''}
+        config.rule = [...rule, requiredRule] // 不要直接修改rule,rule可能来自于conditionSharedConfig
+    }
+
+    const makePayload = async () => {
+        const payload = {}
+        const params = toValue(queryParams)
+        // 先检查依赖项
+        for (const key of dependentKeys) {
+            const val = params[key]
+            if (empty(val)) {
+                // 只是给一个警告,reject的方式太重了,会让后续开发认为产生了不可修复问题。
+                // 一般情况下缺必传字段,只需要等必传字段autoInit完成就会自动修复这个问题。
+                // 当然万一运行不符合预期,这里也确实是需要着重检查的点。
+                console.warn(`${config.key}: independent key ${key} is required, wait for next turn.`)
+                return new Promise(fnPlaceholder).then()
+                // return Promise.reject('some message')
+            }
+            payload[key] = val
+        }
+        for (const key of independentKeys) {
+            payload[key] = params[key]
+        }
+        return {...toValue(sharedData), ...payload}
+    }
+
+    const loadData = async () => {
+        eventManager.push('loadData calling', key)
+
+        const payload = await makePayload()
+        const results = await config.handler(payload)
+        console.log('dataManager data loaded', key, results)
+
+        const processData = function (results) {
+            if (allLabel && !required) {
+                const add = keyValue ? {[keyName]: allLabel, [keyValue]: ''} : allLabel
+                list.value = [add, ...results] // 不直接修改,可能会影响缓存结果
+            } else {
+                list.value = results
+            }
+
+            const current = toValue(queryParams)[key]
+            const currentInvalid = (val) => {
+                const valSource = list.value.map(i => keyValue ? i[keyValue] : i)
+                const diff = _.difference(valSource, [].concat(val))
+                console.log('loadData invalid current:', val, ', diff', diff)
+                return diff.length > 0
+            }
+            if (!empty(current) && currentInvalid(current)) {
+                console.log('loadData clear invalid', key, current)
+                queryParams.value[key] = multiple ? [] : ''
+            }
+            if (required && autoInit && empty(current)) {
+                const first = _.first(list.value)
+                const firstValue = keyValue ? first[keyValue] : first
+                if (first) queryParams.value[key] = multiple ? [firstValue] : firstValue
+                console.log('loadData auto init', key, firstValue)
+            }
+
+            eventManager.pop('loadData called', key)
+        }
+
+        if (isRef(results)) {
+            const pureResults = toValue(results)
+            if (empty(pureResults)) {
+                // 此时没有数据,说明后续可能会更新,使用watch监听即可
+                watch(results, (values) => processData(values))
+            } else {
+                processData(pureResults)
+            }
+        } else {
+            processData(results)
+        }
+    }
+
+    // 数据加载
+    if (!dependentKeys.length) {
+        // 无依赖项,直接调用。这里使用了nextTick做保险,万一立即进行校验,要保证uv-form渲染之后才能进行
+        nextTick(async () => await loadData())
+    } else {
+        // 有依赖项,建立监听,依赖变更时重新加载数据
+        const watches = dependentKeys.map(k => () => toValue(queryParams)[k])
+        watch(watches, async () => await loadData(), {immediate: true})
+    }
+
+    return {list, config}
+}

+ 43 - 0
src/pagesOther/components/ie-condition/useSearchModelInjection.js

@@ -0,0 +1,43 @@
+
+import {fnPlaceholder} from "@/utils/uni-helper";
+import {injectLocal, provideLocal} from "@vueuse/shared";
+import {useConditionEventManager} from "@/pagesOther/components/ie-condition/useConditionEventManager";
+import {useConditionDataManager} from "@/pagesOther/components/ie-condition/useConditionDataManager";
+
+const key = Symbol('SEARCH_MODEL_SERVICE')
+
+/* 给业务方使用,希望只是定制查询条件的动态参数与静态参数 */
+export const useProvideSearchModel = function (configs, queryParams, formRef = null, sharedData = {}) {
+    const silence = true
+    // console
+    const log = silence
+        ? {log: fnPlaceholder, warn: console.warn, error: console.error}
+        : {log: console.log, warn: console.warn, error: console.error}
+    // uv-form validation
+    const validator = computed(() => formRef?.value?.validate || fnPlaceholder)
+    // event manager for trigger onSearch/onInit
+    const eventManager = useConditionEventManager(queryParams, validator, log)
+    // request data manager. configs必须提供当前场景所需要的所有condition
+    const dataManager = useConditionDataManager(configs, eventManager, queryParams, sharedData, log)
+    const reset = () => {
+        dataManager.reset()
+        eventManager.reset()
+    }
+    const options = {
+        queryParams, // 原样返回
+        sharedData, // 原样返回
+        rules: dataManager.rules, // 给forms用的校验规则,因为利用uv-form的校验是最简便的
+        conditions: dataManager.conditions, // 显示条件选项的数据
+        needValidation: dataManager.needValidation, // 是否需要校验,和渲染uv-form有关
+        onSearch: eventManager.onSearch, // 搜索事件
+        onInit: eventManager.onInit, // 搜索事件,第一次
+        reset // 重置搜索条件状态
+    }
+    provideLocal(key, options)
+    window.searchModel = options
+    return options
+}
+
+export const useInjectSearchModel = function () {
+    return injectLocal(key)
+}

+ 131 - 0
src/pagesOther/components/ie-vhs-picker/ie-vhs-picker.vue

@@ -0,0 +1,131 @@
+<template>
+    <view class="flex-1 fx-row fx-end-cen gap-10" @click="handleClick">
+        <view v-if="valueMode" class="text-sm">{{ display }}</view>
+        <view v-else class="text-sm text-light">{{ placeholder }}</view>
+        <uv-icon v-if="!disabled" name="arrow-down"/>
+        <uv-picker ref="picker" :columns="columns" :default-index="defaultIndex" :title="title" :key-name="labelProp"
+                   @confirm="handleConfirm" @change="handleChange"/>
+    </view>
+</template>
+
+<script setup>
+import {computed, getCurrentInstance, ref} from 'vue'
+import {empty} from "@/uni_modules/uv-ui-tools/libs/function/test";
+import _ from "lodash";
+import {createPropDefine} from "@/utils";
+import {findTreePath} from "@/utils/tree-helper";
+import {autoFormValidate} from "@/utils/uni-helper";
+
+const picker = ref(null)
+const props = defineProps({
+    title: String,
+    placeholder: String,
+    data: Array,
+    modelValue: [String, Number, Object],
+    labelProp: String,
+    valueProp: String,
+    emptyDisplay: String,
+    treeProp: String,
+    disabled: Boolean
+})
+const emits = defineEmits(['change', 'update:modelValue'])
+
+const instance = getCurrentInstance()
+const valueMode = computed(() => !empty(props.modelValue) || !!props.emptyDisplay)
+const display = computed(() => {
+    if (empty(props.modelValue) && !!props.emptyDisplay) return props.emptyDisplay
+    return getLabel(current.value) || props.emptyDisplay
+})
+
+const currentPath = computed(() => findTreePath(props.data, item => getValue(item) == props.modelValue, props.treeProp))
+const current = computed(() => _.last(currentPath.value))
+const columns = ref([])
+const defaultIndex = ref([])
+
+const handleClick = function () {
+  if (props.disabled) {
+    return;
+  }
+    setDefaultColumnsAndIndexs()
+    picker.value.open()
+}
+
+// columnIndex 正在改变的列 > index 改变后的序号 > indexs 当前选中的全部序号
+// > value 当前选中的全部元素 > values 当前columns
+const handleConfirm = function ({value}) {
+    const selected = _.last(value)
+    const selectedValue = getValue(selected)
+    emits('update:modelValue', selectedValue)
+    emits('change', selected)
+
+    // try trigger uv-form validate
+    autoFormValidate(instance, 'change')
+}
+
+const handleChange = function ({columnIndex, value}) {
+    if (!props.treeProp) return // 不是树形结构
+    if (columnIndex == value.length - 1) return // 操作的是最后一列
+    const changToValue = getValue(value[columnIndex])
+    const {cols, idxes} = calculateColumnsAndIndexs(changToValue)
+    console.log('calculateColumnsAndIndexs', cols, idxes)
+    columns.value = cols
+    picker.value.setIndexs(idxes, true)
+}
+
+const setDefaultColumnsAndIndexs = function () {
+    const {cols, idxes} = calculateColumnsAndIndexs(props.modelValue)
+    columns.value = cols
+    defaultIndex.value = idxes
+}
+
+/*
+* @description 根据给定的值计算picker所需要的columns和indexs
+* */
+const calculateColumnsAndIndexs = function (value, autoEnd = true) {
+    const {data, treeProp} = props
+    const cols = []
+    const idxes = []
+    const path = findTreePath(data, n => getValue(n) == value, treeProp)
+    // auto end // 指定的条件,可能查出来的是中间某列的值,此时自动帮它选中至最后一列
+    if (autoEnd && treeProp && path?.length) {
+        let final = _.last(path)
+        while (final[treeProp]?.length) {
+            final = final[treeProp][0]
+            path.push(final)
+        }
+    }
+    // modelValue logic
+    let prevNode = data
+    _.forEach(path, item => {
+        const idx = prevNode.findIndex(n => getValue(n) == getValue(item))
+        if (idx < 0) {
+            // 无效值,停止迭代
+            cols.length = 0
+            idxes.length = 0
+            return false // lodash.forEach接收到false返回会终止迭代
+        }
+        cols.push(prevNode)
+        idxes.push(idx)
+        prevNode = _.get(prevNode, idx + '.' + treeProp)
+    })
+    // default logic
+    if (!cols.length && data.length) {
+        prevNode = data
+        do {
+            cols.push(prevNode)
+            idxes.push(0)
+            prevNode = _.get(prevNode, '0.' + props.treeProp)
+        } while (prevNode)
+    }
+    return {cols, idxes}
+}
+
+const getValue = item => _.get(item, props.valueProp, item)
+const getLabel = item => _.get(item, props.labelProp, item)
+
+defineExpose({current, currentPath})
+</script>
+
+<style scoped>
+
+</style>

+ 1 - 1
src/pagesOther/pages/vhs/hooks/useVoluntaryAssistantInjection.js

@@ -1,4 +1,4 @@
-import {createEventHook, injectLocal, provideLocal} from "@vueuse/core";
+import {createEventHook, injectLocal, provideLocal} from "@vueuse/shared";
 import {sleep} from "@/uni_modules/uv-ui-tools/libs/function";
 import {empty} from "@/uni_modules/uv-ui-tools/libs/function/test";
 import {recommendMajorSortFn} from "@/utils/common";

+ 1 - 1
src/pagesOther/pages/vhs/hooks/useVoluntaryCartInjection.js

@@ -1,5 +1,5 @@
 import _ from 'lodash';
-import {injectLocal, provideLocal} from "@vueuse/core";
+import {injectLocal, provideLocal} from "@vueuse/shared";
 import {toast} from "@/uni_modules/uv-ui-tools/libs/function";
 
 const key = Symbol('VOLUNTARY_CART')

+ 1 - 1
src/pagesOther/pages/vhs/hooks/useVoluntaryFormInjection.js

@@ -1,4 +1,4 @@
-import {injectLocal, provideLocal} from "@vueuse/core";
+import {injectLocal, provideLocal} from "@vueuse/shared";
 import {array, empty} from "@/uni_modules/uv-ui-tools/libs/function/test";
 import {useUserStore} from "@/store/userStore";
 import {zytbBatches} from "@/api/modules/vhs";

+ 1 - 1
src/pagesOther/pages/vhs/hooks/useVoluntaryHeaderInjection.js

@@ -1,4 +1,4 @@
-import {injectLocal, provideLocal} from "@vueuse/core";
+import {injectLocal, provideLocal} from "@vueuse/shared";
 import {empty} from "@/uni_modules/uv-ui-tools/libs/function/test";
 import {getVoluntaryHeaders} from "@/api/modules/vhs";
 

+ 1 - 1
src/pagesOther/pages/vhs/hooks/useVoluntaryMajorHighlightInjection.js

@@ -1,4 +1,4 @@
-import {injectLocal, provideLocal} from "@vueuse/core";
+import {injectLocal, provideLocal} from "@vueuse/shared";
 import {getMajorTree} from "@/api/modules/major";
 
 const key = Symbol('VOLUNTARY_MAJOR_HIGHLIGHT')

+ 11 - 8
src/pagesOther/pages/vhs/hooks/useVoluntarySearchInjection.js

@@ -1,9 +1,13 @@
-import {injectLocal, provideLocal} from "@vueuse/core";
-import {useConditionPickType} from "@/components/mx-condition/modules/useConditionPickType";
-import {useConditionCollegeFeatures} from "@/components/mx-condition/modules/useConditionCollegeFeatures";
-import {useConditionCollegeType} from "@/components/mx-condition/modules/useConditionCollegeType";
-import {useConditionCollegeNatureTypeCN} from "@/components/mx-condition/modules/useConditionCollegeNatureTypeCN";
-import {useConditionCollegeLocation} from "@/components/mx-condition/modules/useConditionCollegeLocation";
+import {injectLocal, provideLocal} from "@vueuse/shared";
+import {useProvideSearchModel} from "@/pagesOther/components/ie-condition/useSearchModelInjection";
+import {useConditionPickType} from "@/pagesOther/components/ie-condition/modules/useConditionPickType";
+import {useConditionCollegeFeatures} from "@/pagesOther/components/ie-condition/modules/useConditionCollegeFeatures";
+import {useConditionCollegeType} from "@/pagesOther/components/ie-condition/modules/useConditionCollegeType";
+import {
+    useConditionCollegeNatureTypeCN
+} from "@/pagesOther/components/ie-condition/modules/useConditionCollegeNatureTypeCN";
+import {useConditionCollegeLocation} from "@/pagesOther/components/ie-condition/modules/useConditionCollegeLocation";
+import {getUniversityFilters} from "@/api/modules/university";
 
 const key = Symbol('VOLUNTARY_SEARCH')
 
@@ -40,7 +44,6 @@ export const useProvideVoluntarySearch = () => {
     }
 
     const filter = ref({})
-    const {dispatchCache} = useCacheStore()
 
     const {onSearch, conditions, reset: resetCore} = useProvideSearchModel([
         useConditionPickType(),
@@ -59,7 +62,7 @@ export const useProvideVoluntarySearch = () => {
     }
 
     onMounted(async () => {
-        const res = await dispatchCache(filters)
+        const res = await getUniversityFilters()
         filter.value = {...res.data} // 创建副本,防止不能正确触发searchModelService
     })
 

+ 1 - 1
src/pagesOther/pages/vhs/hooks/useVoluntaryStepInjection.js

@@ -1,4 +1,4 @@
-import {injectLocal, provideLocal} from "@vueuse/core";
+import {injectLocal, provideLocal} from "@vueuse/shared";
 
 const key = Symbol('VOLUNTARY_STEP')
 

+ 39 - 40
src/pagesOther/pages/vhs/index/components/cart-step.vue

@@ -1,49 +1,49 @@
 <template>
-    <z-paging ref="paging" v-model="list" :auto="false" :height="safeScrollHeight" auto-show-system-loading
-              @query="handleQuery">
+    <z-paging ref="paging" v-model="list" :auto="false" auto-show-system-loading @query="handleQuery">
         <template #top>
             <slot name="top"/>
-            <mx-condition-dropdown ref="dropdown" x layout="fx-row items-center gap-20 w-max"/>
+            <ie-condition-dropdown ref="dropdown" x layout="flex items-center gap-20 w-max"/>
         </template>
         <voluntary-search @search="handleSearch"/>
-        <view class="fx-col p-20 gap-20">
+        <view class="flex flex-col p-20 gap-20">
             <voluntary-item v-for="item in list" :item="item" @major="openMajorPopup(item)" @notify="showNotify"/>
-            <vip-guide-more v-if="isNotVip"/>
+<!--            <vip-guide-more v-if="isNotVip"/>-->
         </view>
         <template #bottom>
-            <voluntary-bottom @modify="$refs.modifyPopup.open()" @cart="$refs.cartPopup.open()"/>
+<!--            <voluntary-bottom @modify="$refs.modifyPopup.open()" @cart="$refs.cartPopup.open()"/>-->
         </template>
     </z-paging>
     <!--  这里渲染的弹窗不会被back-to-top遮挡  -->
-    <score-batch-popup ref="modifyPopup"/>
-    <voluntary-cart-popup ref="cartPopup"/>
-    <major-popup ref="majorPopup" @notify="showNotify"/>
-    <uv-notify ref="notifier"/>
+<!--    <score-batch-popup ref="modifyPopup"/>-->
+<!--    <voluntary-cart-popup ref="cartPopup"/>-->
+<!--    <major-popup ref="majorPopup" @notify="showNotify"/>-->
+<!--    <uv-notify ref="notifier"/>-->
 </template>
 
 <script setup>
-import {computed, ref, watch} from 'vue';
-import {toValue} from "@vueuse/core";
-import {createPropDefine} from "@/utils";
 import {sleep} from "@/uni_modules/uv-ui-tools/libs/function";
-import {getRecommendVoluntary, getVoluntaryMarjors} from "@/api/webApi/volunteer";
-import {useUserStore} from "@/hooks/useUserStore";
-import {useInjectVoluntaryForm} from "@/pagesOther/pages/voluntary/hooks/useVoluntaryFormInjection";
-import {useInjectVoluntaryAssistant} from "@/pagesOther/pages/voluntary/hooks/useVoluntaryAssistantInjection";
-import {useInjectVoluntaryStep} from "@/pagesOther/pages/voluntary/hooks/useVoluntaryStepInjection";
-import {useInjectVoluntaryHeader} from "@/pagesOther/pages/voluntary/hooks/useVoluntaryHeaderInjection";
-import {useVoluntaryMajorGroupIdentifier} from "@/pagesOther/pages/voluntary/hooks/useVoluntaryMajorGroupIdentifier";
-import {useInjectVoluntaryCart} from "@/pagesOther/pages/voluntary/hooks/useVoluntaryCartInjection";
-import VoluntaryItem from "@/pagesOther/pages/voluntary/index/components/voluntary-item.vue";
-import VoluntaryBottom from "@/pagesOther/pages/voluntary/index/components/voluntary-bottom.vue";
-import MajorPopup from "@/pagesOther/pages/voluntary/index/components/major-popup.vue";
-import ScoreBatchPopup from "@/pagesOther/pages/voluntary/index/components/score-batch-popup.vue";
-import VoluntaryCartPopup from "@/pagesOther/pages/voluntary/index/components/voluntary-cart-popup.vue";
-import VoluntarySearch from "@/pagesOther/pages/voluntary/index/components/voluntary-search.vue";
-import {useProvideVoluntarySearch} from "@/pagesOther/pages/voluntary/hooks/useVoluntarySearchInjection";
+import {useUserStore} from "@/store/userStore";
+import VoluntaryItem from "@/pagesOther/pages/vhs/index/components/voluntary-item.vue";
+import IeConditionDropdown from "@/pagesOther/components/ie-condition-dropdown/ie-condition-dropdown.vue";
+// import VoluntaryBottom from "@/pagesOther/pages/vhs/index/components/voluntary-bottom.vue";
+// import MajorPopup from "@/pagesOther/pages/vhs/index/components/major-popup.vue";
+// import ScoreBatchPopup from "@/pagesOther/pages/vhs/index/components/score-batch-popup.vue";
+// import VoluntaryCartPopup from "@/pagesOther/pages/vhs/index/components/voluntary-cart-popup.vue";
+import VoluntarySearch from "@/pagesOther/pages/vhs/index/components/voluntary-search.vue";
+import {useInjectVoluntaryForm} from "@/pagesOther/pages/vhs/hooks/useVoluntaryFormInjection";
+import {useInjectVoluntaryStep} from "@/pagesOther/pages/vhs/hooks/useVoluntaryStepInjection";
+import {useInjectVoluntaryCart} from "@/pagesOther/pages/vhs/hooks/useVoluntaryCartInjection";
+import {useInjectVoluntaryAssistant} from "@/pagesOther/pages/vhs/hooks/useVoluntaryAssistantInjection";
+import {useInjectVoluntaryHeader} from "@/pagesOther/pages/vhs/hooks/useVoluntaryHeaderInjection";
+import {useProvideVoluntarySearch} from "@/pagesOther/pages/vhs/hooks/useVoluntarySearchInjection";
+import {useVoluntaryMajorGroupIdentifier} from "@/pagesOther/pages/vhs/hooks/useVoluntaryMajorGroupIdentifier";
+import {getRecommendVoluntary, getVoluntaryMarjors} from "@/api/modules/vhs";
 
 const props = defineProps({
-    editMode: createPropDefine(false, Boolean)
+    editMode: {
+        type: Boolean,
+        default: false
+    }
 })
 
 const paging = ref(null)
@@ -51,21 +51,20 @@ const notifier = ref(null)
 const majorPopup = ref(null)
 const cartPopup = ref(null)
 const dropdown = ref(null)
-const {currentUser, GetInfo, isBind} = useUserStore()
+const currentUser = useUserStore()
 const {model, batch, mode} = useInjectVoluntaryForm()
 const {currentStep} = useInjectVoluntaryStep()
 const {
     resetCart, total, list, selectedList,
     syncMajorGroupToSelected, syncMajorsToSelectedGroup
 } = useInjectVoluntaryCart()
-const {scrollHeight, onBeforeBack, save} = useInjectVoluntaryAssistant() // 填报页下面有原生tabs,必须手动设置高度
+const {onBeforeBack, save} = useInjectVoluntaryAssistant() // 填报页下面有原生tabs,必须手动设置高度
 const {isMock, ensureHistoryYears} = useInjectVoluntaryHeader()
 
 const {formatQueryParams, onSearch, reset: resetCondition} = useProvideVoluntarySearch()
 
-const safeScrollHeight = computed(() => scrollHeight ? toValue(scrollHeight) + 'px' : undefined)
 const isNotVip = computed(() => {
-    return !toValue(isBind) && toValue(total) > 1
+    return !currentUser.isVip && toValue(total) > 1
 })
 
 const showNotify = (message) => {
@@ -87,7 +86,7 @@ const openMajorPopup = (item) => {
 }
 
 const loadMajorDetails = (item) => {
-    uni.showLoading()
+    uni.$ie.showLoading()
     getVoluntaryMarjors({
         batchName: toValue(batch).name,
         collegeCode: item.recruitPlan.collegeCode,
@@ -100,17 +99,17 @@ const loadMajorDetails = (item) => {
         syncMajorsToSelectedGroup(res.data, item)
         item.majors = res.data
         majorPopup.value.open(item)
-    }).finally(() => uni.hideLoading())
+    }).finally(() => uni.$ie.hideLoading())
 }
 
 
 const syncUserScoreByNeed = (query) => {
-    const {score, seatInput/*, mode*/} = toValue(currentUser)
+    const {score, seatInput/*, mode*/} = toValue(currentUser.userInfo)
     // if query score mode seatInput changed, re-call getInfo.
     if (query.score != score ||
         // query.mode != mode ||
         query.seatInput != seatInput) {
-        GetInfo()
+        currentUser.getUserInfo()
     }
 }
 
@@ -202,9 +201,9 @@ const reloadWatches = [
 watch(reloadWatches, async ([step, score, input, batch], arg2) => {
     if (step == 2) {
         if (!props.editMode) resetCart() // 编辑模式下保留cart数据
-        cartPopup.value.close()
-        majorPopup.value.close()
-        dropdown.value.terminate()
+        cartPopup.value?.close()
+        majorPopup.value?.close()
+        dropdown.value?.terminate()
         await sleep(400) // wait for animation complete
         resetCondition()
     }

+ 1 - 1
src/pagesOther/pages/vhs/index/components/voluntary-history-list.vue

@@ -46,7 +46,7 @@
 </template>
 
 <script>
-import {useInjectVoluntaryHeader} from "@/pagesOther/pages/voluntary/hooks/useVoluntaryHeaderInjection";
+import {useInjectVoluntaryHeader} from "@/pagesOther/pages/vhs/hooks/useVoluntaryHeaderInjection";
 
 export default {
     name: "voluntary-history-list",

+ 16 - 17
src/pagesOther/pages/vhs/index/components/voluntary-item.vue

@@ -1,10 +1,10 @@
 <template>
-    <view class="fx-row fx-bet-sta bg-white mx-card">
-        <view class="fx-col fx-sta-cen ml-10 mb-10">
-            <view class="fx-row rounded-b-full px-15 pt-5 pb-10 text-white" :class="pickType.bg">
+    <view class="flex justify-between items-start bg-white rounded-lg shadow-card">
+        <view class="flex flex-col justify-start items-center ml-10 mb-10">
+            <view class="flex rounded-b-full px-15 pt-5 pb-10 text-white" :class="pickType.bg">
                 <text>{{ pickType.text }}</text>
             </view>
-            <view class="text-main fx-row fx-cen-base gap-5">
+            <view class="text-main flex justify-center items-base gap-5">
                 <view class="font-bold text-xl">{{ item.enrollRatio || '-' }}</view>
                 %
             </view>
@@ -12,8 +12,8 @@
                 {{ item.enrollRatioText }}
             </view>
         </view>
-        <view class="flex-1 fx-col p-20 gap-15 text-main">
-            <view class="fx-row fx-bet-sta gap-15">
+        <view class="flex-1 flex flex-col p-20 gap-15 text-main">
+            <view class="flex justify-between items-start gap-15">
                 <view class="flex-1" @click="goCollegePage">
                     <view class="font-[PingFang] font-bold">
                         {{ item.university.name }}
@@ -31,7 +31,7 @@
                         }}
                     </view>
                 </view>
-                <view class="fx-col fx-sta-cen relative">
+                <view class="flex flex-col justify-start items-center relative">
                     <uv-tags :plain="!item.majors.filter(major => {return major.selected}).length" shape="circle"
                              type="primary" :text="`专业 ${item.recruitPlan.majorCount}`"
                              @click="$emit('major')"/>
@@ -52,21 +52,20 @@
 </template>
 
 <script setup>
-import {computed} from 'vue';
-import {createPropDefine} from "@/utils";
-import {useInjectTransfer} from "@/hooks/useTransfer";
-import {useInjectVoluntaryHeader} from "@/pagesOther/pages/voluntary/hooks/useVoluntaryHeaderInjection";
-import {useInjectUserSnapshot} from "@/pagesOther/pages/ie/hooks/useUserSnapshotInjection";
-import VoluntaryHistoryList from "@/pagesOther/pages/voluntary/index/components/voluntary-history-list.vue";
+import {routes} from "@/common/routes";
+import {useTransferPage} from "@/hooks/useTransferPage";
+import {useInjectUserSnapshot} from "@/pagesOther/hooks/useUserSnapshotInjection";
+import VoluntaryHistoryList from "@/pagesOther/pages/vhs/index/components/voluntary-history-list.vue";
+import {useInjectVoluntaryHeader} from "@/pagesOther/pages/vhs/hooks/useVoluntaryHeaderInjection";
 
 const props = defineProps({
-    item: createPropDefine({}, Object)
+    item: Object
 })
 defineEmits(['major', 'notify'])
 
-const {transferTo} = useInjectTransfer()
+const {transferTo} = useTransferPage()
+const userSnapshot = useInjectUserSnapshot()
 const {headerPlanYear} = useInjectVoluntaryHeader()
-const {userSnapshot} = useInjectUserSnapshot()
 
 const pickType = computed(() => {
     switch (props.item.pickType) {
@@ -81,7 +80,7 @@ const pickType = computed(() => {
 })
 
 const goCollegePage = () => {
-    const path = '/pagesOther/pages/college-library/detail/detail'
+    const path = routes.universityDetail
     const {university: {code}} = props.item
     transferTo(path, {code})
 }

+ 2 - 2
src/pagesOther/pages/vhs/index/components/voluntary-search.vue

@@ -1,12 +1,12 @@
 <template>
     <view class="px-20 relative pt-20">
-        <mx-search v-model="queryParams.name" placeholder="请输入院校名称" @search="handleSearch"
+        <uv-search v-model="queryParams.name" placeholder="请输入院校名称" @search="handleSearch"
                    @clear="handleSearch"/>
     </view>
 </template>
 
 <script setup>
-import {useInjectVoluntarySearch} from "@/pagesOther/pages/voluntary/hooks/useVoluntarySearchInjection";
+import {useInjectVoluntarySearch} from "@/pagesOther/pages/vhs/hooks/useVoluntarySearchInjection";
 
 const emits = defineEmits(['search'])
 const {queryParams} = useInjectVoluntarySearch()

+ 2 - 2
src/pagesOther/pages/vhs/index/index.vue

@@ -10,7 +10,7 @@
                     <batch-step/>
                 </swiper-item>
                 <swiper-item>
-<!--                    <cart-step/>-->
+                    <cart-step/>
                 </swiper-item>
             </swiper>
         </ie-auto-resizer>
@@ -20,7 +20,7 @@
 <script setup>
 import ScoreStep from "@/pagesOther/pages/vhs/index/components/score-step.vue";
 import BatchStep from "@/pagesOther/pages/vhs/index/components/batch-step.vue";
-// import CartStep from "@/pagesOther/pages/vhs/index/components/cart-step.vue";
+import CartStep from "@/pagesOther/pages/vhs/index/components/cart-step.vue";
 import {useProvideVoluntaryStep} from "@/pagesOther/pages/vhs/hooks/useVoluntaryStepInjection";
 import {useProvideVoluntaryData} from "@/pagesOther/hooks/useVoluntaryDataInjection";
 import {useProvideVoluntaryForm} from "@/pagesOther/pages/vhs/hooks/useVoluntaryFormInjection";