Kaynağa Gözat

university major picker

abpcoder 1 ay önce
ebeveyn
işleme
955008f94b

+ 1 - 0
src/common/routes.ts

@@ -36,6 +36,7 @@ export const routes = {
   * 院校专业选择
   * */
   targetPicker: '/pagesStudy/pages/targeted-add/targeted-add',
+  targetSetting: '/pagesStudy/pages/targeted-setting/targeted-setting',
   /**
    * 职业库
    */

+ 85 - 84
src/pagesOther/pages/skill/index/index.vue

@@ -1,106 +1,107 @@
 <template>
-  <ie-page bg-color="#F6F8FA">
-    <ie-navbar title="职业技能分测算" transparent bg-color="#FFFFFF" title-color="black" keep-title-color />
-    <!-- #ifdef MP-WEIXIN -->
-    <uv-gap height="44" />
-    <!-- #endif -->
-    <ie-image is-oss src="/volunteer/skill/index/bg.png" custom-class="w-full h-600" />
-    <view class="-mt-420 z-1">
-      <view class="px-36 flex justify-between items-center">
-        <view class="text-3xl text-primary keep-all">
-          <view class="mb-10 font-bold">测职业技能分</view>
-          <view class="text-base">输入分数,测算所需职业技能分</view>
+    <ie-page bg-color="#F6F8FA">
+        <ie-navbar title="职业技能分测算" transparent bg-color="#FFFFFF" title-color="black" keep-title-color/>
+        <!-- #ifdef MP-WEIXIN -->
+        <uv-gap height="44"/>
+        <!-- #endif -->
+        <ie-image is-oss src="/volunteer/skill/index/bg.png" custom-class="w-full h-600"/>
+        <view class="-mt-420 z-1">
+            <view class="px-36 flex justify-between items-center">
+                <view class="text-3xl text-primary keep-all">
+                    <view class="mb-10 font-bold">测职业技能分</view>
+                    <view class="text-base">输入分数,测算所需职业技能分</view>
+                </view>
+                <ie-image is-oss src="/volunteer/skill/index/banner.png" custom-class="w-208 h-208"/>
+            </view>
+            <view class="mt-60 mx-30 bg-white rounded-xl p-35">
+                <view class="flex justify-between items-center">
+                    <view class="text-lg text-fore-title">报考院校专业</view>
+                    <view class="text-base text-fore-placeholder flex items-center" @click="handleSelect">
+                        <text>请选择</text>
+                        <uv-icon name="arrow-right" color="info"/>
+                    </view>
+                </view>
+                <ie-empty v-if="!rules.length" :image="emptyImg" text="请选择你的报考院校专业~"/>
+                <voluntary-form v-else ref="form" disable-simulate/>
+            </view>
         </view>
-        <ie-image is-oss src="/volunteer/skill/index/banner.png" custom-class="w-208 h-208" />
-      </view>
-      <view class="mt-60 mx-30 bg-white rounded-xl p-35">
-        <view class="flex justify-between items-center">
-          <view class="text-lg text-fore-title">报考院校专业</view>
-          <view class="text-base text-fore-placeholder flex items-center" @click="handleSelect">
-            <text>请选择</text>
-            <uv-icon name="arrow-right" color="info" />
-          </view>
-        </view>
-        <ie-empty v-if="!rules.length" :image="emptyImg" text="请选择你的报考院校专业~" />
-        <voluntary-form v-else ref="form" disable-simulate />
-      </view>
-    </view>
-    <ie-safe-toolbar :height="84" :shadow="false">
-      <view class="px-30 py-16">
-        <ie-button @click="handleSubmit">开始计算</ie-button>
-      </view>
-    </ie-safe-toolbar>
-  </ie-page>
+        <ie-safe-toolbar :height="84" :shadow="false">
+            <view class="px-30 py-16">
+                <ie-button @click="handleSubmit">开始计算</ie-button>
+            </view>
+        </ie-safe-toolbar>
+    </ie-page>
 </template>
 
 <script setup lang="ts">
 import VoluntaryForm from "@/pagesOther/pages/voluntary/index/components/voluntary-form.vue";
-import { useTransferPage } from "@/hooks/useTransferPage";
-import { routes } from "@/common/routes";
-import { VOLUNTARY_TARGET, VOLUNTARY_RULES, VOLUNTARY_MODEL } from "@/types/injectionSymbols";
-import { EnrollRule, SelectedUniversityMajor, VoluntaryDto, VoluntaryModel, VoluntaryResult } from "@/types/voluntary";
+import {useTransferPage} from "@/hooks/useTransferPage";
+import {routes} from "@/common/routes";
+import {VOLUNTARY_TARGET, VOLUNTARY_RULES, VOLUNTARY_MODEL} from "@/types/injectionSymbols";
+import {EnrollRule, SelectedUniversityMajor, VoluntaryDto, VoluntaryModel, VoluntaryResult} from "@/types/voluntary";
 import config from "@/config";
-import { UniversityPickerPageOptions } from "@/types/transfer";
-import { getSkillRules, postSkillRules } from "@/api/modules/voluntary";
-import { useAuth } from "@/hooks/useAuth";
-import { EnumUserRole } from "@/common/enum";
+import {UniversityPickerPageOptions} from "@/types/transfer";
+import {getSkillRules, postSkillRules} from "@/api/modules/voluntary";
+import {useAuth} from "@/hooks/useAuth";
+import {EnumUserRole} from "@/common/enum";
 
-const { hasPermission } = useAuth();
+const {hasPermission} = useAuth();
 const emptyImg = computed(() => config.ossUrl + '/volunteer/voluntary/index/empty_data.png')
 
 const form = ref<InstanceType<typeof VoluntaryForm>>()
 const target = ref<SelectedUniversityMajor>({} as SelectedUniversityMajor)
 const rules = ref<EnrollRule[]>([])
 const model = ref<VoluntaryModel>({})
-const { transferTo } = useTransferPage()
+const {transferTo} = useTransferPage()
 
 const handleSelect = async () => {
-  const option: UniversityPickerPageOptions = {
-    title: '选择你的报考院校专业',
-    fromVoluntary: true,
-    selectedUniversityId: target.value?.universityId,
-    selectedMajorId: target.value?.majorId
-  }
-  const picked = await transferTo(routes.targetPicker, { data: option })
-  if (!picked) return
-  target.value = picked as SelectedUniversityMajor
-  uni.$ie.showLoading()
-  try {
-    // reset
-    model.value = {}
-    rules.value = []
-    // request render rules
-    const res = await getSkillRules(target.value)
-    rules.value = res.data
-    // init model
-    rules.value.forEach((r) => {
-      r.details?.forEach((d) => {
-        if (d.options?.length) {
-          model.value[d.fieldName] = d.defaultValue ? d.defaultValue : ''
-          model.value[d.fieldName + 'Total'] = d.options ? d.options[0] : null
-        }
-      })
-    })
-  } catch (e) {
-    console.log('getRenderRules ex', e, target.value)
-    target.value = {} as SelectedUniversityMajor // clear for re-pick
-  } finally {
-    uni.$ie.hideLoading()
-  }
+    const option: UniversityPickerPageOptions = {
+        title: '选择你的报考院校专业',
+        fromVoluntary: true,
+        selectedUniversityId: target.value?.universityId,
+        selectedMajorId: target.value?.majorId,
+        useRedirect: true
+    }
+    const picked = await transferTo(routes.universityPicker, {data: option})
+    if (!picked) return
+    target.value = picked as SelectedUniversityMajor
+    uni.$ie.showLoading()
+    try {
+        // reset
+        model.value = {}
+        rules.value = []
+        // request render rules
+        const res = await getSkillRules(target.value)
+        rules.value = res.data
+        // init model
+        rules.value.forEach((r) => {
+            r.details?.forEach((d) => {
+                if (d.options?.length) {
+                    model.value[d.fieldName] = d.defaultValue ? d.defaultValue : ''
+                    model.value[d.fieldName + 'Total'] = d.options ? d.options[0] : null
+                }
+            })
+        })
+    } catch (e) {
+        console.log('getRenderRules ex', e, target.value)
+        target.value = {} as SelectedUniversityMajor // clear for re-pick
+    } finally {
+        uni.$ie.hideLoading()
+    }
 }
 
 const handleSubmit = async () => {
-  const hasAuth = hasPermission([EnumUserRole.VIP]);
-  if (!hasAuth) {
-    return;
-  }
-  if (!target.value.universityId || !target.value.majorId) return uni.$ie.showToast('请选择院校专业')
-  if (!rules.value.length) return uni.$ie.showToast('由于官方未公布历年录取分数或计划变更,暂时无法计算技能分')
-  await form.value?.validate()
-  // make request
-  const { data: result } = await postSkillRules(target.value, model.value)
-  const bigData: VoluntaryDto = { target: target.value, rules: rules.value, model: model.value, result }
-  transferTo(routes.skillResult, { bigData })
+    const hasAuth = hasPermission([EnumUserRole.VIP]);
+    if (!hasAuth) {
+        return;
+    }
+    if (!target.value.universityId || !target.value.majorId) return uni.$ie.showToast('请选择院校专业')
+    if (!rules.value.length) return uni.$ie.showToast('由于官方未公布历年录取分数或计划变更,暂时无法计算技能分')
+    await form.value?.validate()
+    // make request
+    const {data: result} = await postSkillRules(target.value, model.value)
+    const bigData: VoluntaryDto = {target: target.value, rules: rules.value, model: model.value, result}
+    transferTo(routes.skillResult, {bigData})
 }
 
 provide(VOLUNTARY_TARGET, target)

+ 0 - 1
src/pagesOther/pages/university/index/components/plus/college-conditions-picker.vue

@@ -156,7 +156,6 @@ const handleChange = (value: any) => {
 }
 
 watch(() => filter.value.tiers, tiers => {
-    console.log('watch filter.value.tiers', tiers)
     // sync tier from outside
     form.value.tiers = tiers
 }, {immediate: true})

+ 14 - 4
src/pagesOther/pages/university/picker/picker.vue

@@ -2,7 +2,7 @@
     <ie-page>
         <college-list @click="handleItemClick">
             <template #top>
-                <ie-navbar :title="title"/>
+                <ie-navbar title="选择院校"/>
             </template>
         </college-list>
     </ie-page>
@@ -13,12 +13,22 @@ import {Transfer} from "@/types";
 import {useTransferPage} from "@/hooks/useTransferPage";
 import CollegeList from "@/pagesOther/pages/university/index/components/college-list.vue";
 import {University} from "@/types/university";
+import {routes} from "@/common/routes";
 
-const {prevData, transferBack} = useTransferPage<Transfer.UniversityPickerPageOptions, {}>()
-const title = computed(() => prevData.value.title || '选择院校')
+const {prevData, transferTo, transferBack} = useTransferPage<Transfer.UniversityPickerPageOptions, {}>()
 
 const handleItemClick = (u: University) => {
-    transferBack(u)
+    if (prevData.value.useRedirect) {
+        transferTo(routes.targetPicker, {
+            type: 'redirectTo',
+            bigData: {
+                ...prevData.value,
+                picked: u
+            }
+        })
+    } else {
+        transferBack(u)
+    }
 }
 </script>
 

+ 3 - 2
src/pagesOther/pages/voluntary/index/index.vue

@@ -51,9 +51,10 @@ const handleSelect = async () => {
         title: '选择你的报考院校专业',
         fromVoluntary: true,
         selectedUniversityId: target.value?.universityId,
-        selectedMajorId: target.value?.majorId
+        selectedMajorId: target.value?.majorId,
+        useRedirect: true
     }
-    const picked = await transferTo(routes.targetPicker, {data: option})
+    const picked = await transferTo(routes.universityPicker, {data: option})
     if (!picked) return
     await processSelected(picked as SelectedUniversityMajor)
 }

+ 148 - 146
src/pagesOther/pages/voluntary/list/list.vue

@@ -1,185 +1,187 @@
 <template>
-  <ie-page>
-    <z-paging ref="paging" v-model="list" bg-color="#F6F8FA" safe-area-inset-bottom :scrollable="!refresherEnabled"
-      :refresher-enabled="refresherEnabled" @query="handleQuery">
-      <template #top>
-        <ie-navbar title="志愿表" />
-      </template>
-      <view class="mt-20 bg-warning-light p-28 text-23 leading-38 text-fore-title">
-        <text class="font-bold">说明:</text>
-        目前志愿计划为2025年,排序前两个为第一、二志愿,可通过修改排序重新选择第一、二志愿
-      </view>
-      <view class="p-28 flex flex-col gap-28">
-        <voluntary-item v-for="(item, i) in list" :key="i" :data="item" :index="i" @more="showActions(item)"
-          @error="handleError" @deleted="handleDeleted" />
-      </view>
-    </z-paging>
-    <ie-safe-toolbar :height="84" :shadow="false">
-      <view class="px-30 py-16">
-        <ie-button @click="handleAdd">添加志愿</ie-button>
-      </view>
-    </ie-safe-toolbar>
-    <uv-action-sheet ref="actionSheet" :actions="moreActions" safe-area-inset-bottom close-on-click-overlay
-      cancel-text="取消" @select="handleActionSelect" />
-  </ie-page>
+    <ie-page>
+        <z-paging ref="paging" v-model="list" bg-color="#F6F8FA" safe-area-inset-bottom :scrollable="!refresherEnabled"
+                  :refresher-enabled="refresherEnabled" @query="handleQuery">
+            <template #top>
+                <ie-navbar title="志愿表"/>
+            </template>
+            <view class="mt-20 bg-warning-light p-28 text-23 leading-38 text-fore-title">
+                <text class="font-bold">说明:</text>
+                目前志愿计划为2025年,排序前两个为第一、二志愿,可通过修改排序重新选择第一、二志愿
+            </view>
+            <view class="p-28 flex flex-col gap-28">
+                <voluntary-item v-for="(item, i) in list" :key="i" :data="item" :index="i" @more="showActions(item)"
+                                @error="handleError" @deleted="handleDeleted"/>
+            </view>
+        </z-paging>
+        <ie-safe-toolbar :height="84" :shadow="false">
+            <view class="px-30 py-16">
+                <ie-button @click="handleAdd">添加志愿</ie-button>
+            </view>
+        </ie-safe-toolbar>
+        <uv-action-sheet ref="actionSheet" :actions="moreActions" safe-area-inset-bottom close-on-click-overlay
+                         cancel-text="取消" @select="handleActionSelect"/>
+    </ie-page>
 </template>
 
 <script setup lang="ts">
-import { SelectedUniversityMajor, VoluntaryRecord } from "@/types/voluntary";
+import {SelectedUniversityMajor, VoluntaryRecord} from "@/types/voluntary";
 import VoluntaryItem from "@/pagesOther/pages/voluntary/list/components/voluntary-item.vue";
-import { VOLUNTARY_REFRESHER_ENABLED } from "@/types/injectionSymbols";
+import {VOLUNTARY_REFRESHER_ENABLED} from "@/types/injectionSymbols";
 import {
-  addVoluntary,
-  getVoluntaryList,
-  removeVoluntaryByUniversity,
-  sortVoluntaryByUniversity
+    addVoluntary,
+    getVoluntaryList,
+    removeVoluntaryByUniversity,
+    sortVoluntaryByUniversity
 } from "@/api/modules/voluntary";
 import UvActionSheet from "@/uni_modules/uv-action-sheet/components/uv-action-sheet/uv-action-sheet.vue";
-import { useTransferPage } from "@/hooks/useTransferPage";
-import { UniversityPickerPageOptions } from "@/types/transfer";
-import { routes } from "@/common/routes";
-import { useAuth } from "@/hooks/useAuth";
-import { EnumUserRole } from "@/common/enum";
+import {useTransferPage} from "@/hooks/useTransferPage";
+import {UniversityPickerPageOptions} from "@/types/transfer";
+import {routes} from "@/common/routes";
+import {useAuth} from "@/hooks/useAuth";
+import {EnumUserRole} from "@/common/enum";
 
 interface ActionItem {
-  id: string;
-  name: string;
-  icon?: string;
-  color?: string;
-  iconColor?: string;
-  disabled?: boolean;
+    id: string;
+    name: string;
+    icon?: string;
+    color?: string;
+    iconColor?: string;
+    disabled?: boolean;
 }
-const { hasPermission } = useAuth();
-const { transferTo } = useTransferPage()
+
+const {hasPermission} = useAuth();
+const {transferTo} = useTransferPage()
 const list = ref<VoluntaryRecord[]>([])
 const paging = ref<ZPagingInstance>()
 const refresherEnabled = ref<boolean>(true)
 const actionSheet = ref<InstanceType<typeof UvActionSheet>>()
 const actionRecord = ref<VoluntaryRecord>()
 const moreActions = computed<ActionItem[]>(() => {
-  const records = list.value
-  const current = actionRecord.value
-  const idx = current ? records.indexOf(current) : -1
-  const enableTop = records.length > 1 && idx > 0
-  const enableUp = records.length > 1 && idx > 0
-  const enableDown = records.length > 1 && idx < records.length - 1
-  return [{
-    id: 'top',
-    name: '置顶',
-    icon: 'pushpin-fill',
-    color: 'var(--primary-color)',
-    iconColor: enableTop ? 'primary' : 'info',
-    disabled: !enableTop
-  }, {
-    id: 'up',
-    name: '上移',
-    icon: 'arrow-upward',
-    iconColor: enableUp ? '' : 'info',
-    disabled: !enableUp
-  }, {
-    id: 'down',
-    name: '下移',
-    icon: 'arrow-downward',
-    iconColor: enableDown ? '' : 'info',
-    disabled: !enableDown
-  }, {
-    id: 'delete',
-    name: '删除',
-    icon: 'trash',
-    color: 'var(--danger)',
-    iconColor: 'error'
-  }]
+    const records = list.value
+    const current = actionRecord.value
+    const idx = current ? records.indexOf(current) : -1
+    const enableTop = records.length > 1 && idx > 0
+    const enableUp = records.length > 1 && idx > 0
+    const enableDown = records.length > 1 && idx < records.length - 1
+    return [{
+        id: 'top',
+        name: '置顶',
+        icon: 'pushpin-fill',
+        color: 'var(--primary-color)',
+        iconColor: enableTop ? 'primary' : 'info',
+        disabled: !enableTop
+    }, {
+        id: 'up',
+        name: '上移',
+        icon: 'arrow-upward',
+        iconColor: enableUp ? '' : 'info',
+        disabled: !enableUp
+    }, {
+        id: 'down',
+        name: '下移',
+        icon: 'arrow-downward',
+        iconColor: enableDown ? '' : 'info',
+        disabled: !enableDown
+    }, {
+        id: 'delete',
+        name: '删除',
+        icon: 'trash',
+        color: 'var(--danger)',
+        iconColor: 'error'
+    }]
 })
 
 const handleQuery = () => {
-  getVoluntaryList().then(res => {
-    paging.value?.completeByNoMore(res.data, true)
-  }).catch(e => paging.value?.completeByError(e))
+    getVoluntaryList().then(res => {
+        paging.value?.completeByNoMore(res.data, true)
+    }).catch(e => paging.value?.completeByError(e))
 }
 
 const showActions = (record: VoluntaryRecord) => {
-  actionRecord.value = record
-  actionSheet.value?.open()
+    actionRecord.value = record
+    actionSheet.value?.open()
 }
 
 const handleActionSelect = async (e: ActionItem) => {
-  const record = actionRecord.value
-  const recordList = list.value
-  if (!record) return
-  const idx = recordList.findIndex(r => r == record)
-  if (['top', 'up', 'down'].includes(e.id)) {
-    try {
-      switch (e.id) {
-        case 'top':
-          if (idx > 0) {
-            recordList.splice(idx, 1)
-            recordList.unshift(record)
-            await sortVoluntaryByUniversity(recordList.map(r => r.universityId))
-            uni.$ie.showSuccess('保存成功')
-          }
-          break;
-        case 'up':
-          if (idx > 0) {
-            recordList.splice(idx, 1)
-            recordList.splice(idx - 1, 0, record)
-            await sortVoluntaryByUniversity(recordList.map(r => r.universityId))
-            uni.$ie.showSuccess('保存成功')
-          }
-          break;
-        case 'down':
-          if (idx < recordList.length - 1) {
-            recordList.splice(idx, 1)
-            recordList.splice(idx + 1, 0, record)
-            await sortVoluntaryByUniversity(recordList.map(r => r.universityId))
-            uni.$ie.showSuccess('保存成功')
-          }
-          break;
-      }
-    } catch (e) {
-      console.log('action ex', e)
-      paging.value?.reload() // 发生异常时,重新加载列表
-    }
-  } else if (e.id === 'delete') {
-    // 改为 showModal,避免 reject 错误上报
-    const confirm = await uni.$ie.showModal({
-      title: '志愿删除提醒',
-      content: `删除'${record.universityName}',将同时删除该院校下所有意向专业。\n确认删除?`
-    })
-    if (confirm) {
-      await removeVoluntaryByUniversity(record.universityId)
-      uni.$ie.showSuccess('删除成功')
-      paging.value?.reload()
+    const record = actionRecord.value
+    const recordList = list.value
+    if (!record) return
+    const idx = recordList.findIndex(r => r == record)
+    if (['top', 'up', 'down'].includes(e.id)) {
+        try {
+            switch (e.id) {
+                case 'top':
+                    if (idx > 0) {
+                        recordList.splice(idx, 1)
+                        recordList.unshift(record)
+                        await sortVoluntaryByUniversity(recordList.map(r => r.universityId))
+                        uni.$ie.showSuccess('保存成功')
+                    }
+                    break;
+                case 'up':
+                    if (idx > 0) {
+                        recordList.splice(idx, 1)
+                        recordList.splice(idx - 1, 0, record)
+                        await sortVoluntaryByUniversity(recordList.map(r => r.universityId))
+                        uni.$ie.showSuccess('保存成功')
+                    }
+                    break;
+                case 'down':
+                    if (idx < recordList.length - 1) {
+                        recordList.splice(idx, 1)
+                        recordList.splice(idx + 1, 0, record)
+                        await sortVoluntaryByUniversity(recordList.map(r => r.universityId))
+                        uni.$ie.showSuccess('保存成功')
+                    }
+                    break;
+            }
+        } catch (e) {
+            console.log('action ex', e)
+            paging.value?.reload() // 发生异常时,重新加载列表
+        }
+    } else if (e.id === 'delete') {
+        // 改为 showModal,避免 reject 错误上报
+        const confirm = await uni.$ie.showModal({
+            title: '志愿删除提醒',
+            content: `删除'${record.universityName}',将同时删除该院校下所有意向专业。\n确认删除?`
+        })
+        if (confirm) {
+            await removeVoluntaryByUniversity(record.universityId)
+            uni.$ie.showSuccess('删除成功')
+            paging.value?.reload()
+        }
+    } else {
+        throw new Error('Unsupported action id: ' + e.id)
     }
-  } else {
-    throw new Error('Unsupported action id: ' + e.id)
-  }
 }
 
 const handleError = () => {
-  // 内部异常,重新取数
-  paging.value?.reload()
+    // 内部异常,重新取数
+    paging.value?.reload()
 }
 
 const handleDeleted = () => {
-  paging.value?.reload()
+    paging.value?.reload()
 }
 
 const handleAdd = async () => {
-  const hasAuth = hasPermission([EnumUserRole.VIP]);
-  if (!hasAuth) {
-    return;
-  }
-  const option: UniversityPickerPageOptions = {
-    title: '选择你的意向院校专业',
-    fromVoluntary: true
-  }
-  const picked = await transferTo(routes.targetPicker, { data: option })
-  if (!picked) return
-  setTimeout(async () => {
-    await addVoluntary(picked)
-    uni.$ie.showSuccess('保存成功')
-    paging.value?.reload()
-  }, 300)
+    const hasAuth = hasPermission([EnumUserRole.VIP]);
+    if (!hasAuth) {
+        return;
+    }
+    const option: UniversityPickerPageOptions = {
+        title: '选择你的意向院校专业',
+        fromVoluntary: true,
+        useRedirect: true
+    }
+    const picked = await transferTo(routes.universityPicker, {data: option})
+    if (!picked) return
+    setTimeout(async () => {
+        await addVoluntary(picked)
+        uni.$ie.showSuccess('保存成功')
+        paging.value?.reload()
+    }, 300)
 }
 
 provide(VOLUNTARY_REFRESHER_ENABLED, refresherEnabled)

+ 4 - 1
src/pagesStudy/pages/index/compoentns/index-practice-entry.vue

@@ -74,6 +74,7 @@
 <script lang="ts" setup>
 import { useTransferPage } from '@/hooks/useTransferPage';
 import { useUserStore } from '@/store/userStore';
+import {routes} from "@/common/routes";
 const { transferTo } = useTransferPage();
 const userStore = useUserStore();
 
@@ -140,7 +141,9 @@ const handleSetting = async () => {
   }
 }
 const addTarget = () => {
-  transferTo('/pagesStudy/pages/targeted-add/targeted-add');
+  transferTo(routes.universityPicker, {
+      data: {useRedirect: true}
+  });
 }
 </script>
 <style lang="scss" scoped></style>

+ 193 - 156
src/pagesStudy/pages/targeted-add/targeted-add.vue

@@ -1,205 +1,242 @@
 <template>
-  <ie-page bg-color="#F6F8FA" :fix-height="true">
-    <ie-navbar :title="title" />
-    <view class="mt-16 bg-white py-10">
-      <view class="">
-        <uv-cell-group :border="false">
-          <uv-cell isLink :border="true" :cellStyle="cellStyle" :rightIconStyle="rightIconStyle"
-            @click="handleUniversitySelect">
-            <template #title>
-              <text class="text-30 text-fore-subtitle whitespace-nowrap">选择院校</text>
-            </template>
-            <template #value>
-              <text v-if="form.universityName" class="mx-10 text-30 text-fore-title">
-                {{ form.universityName }}
-              </text>
-              <text v-else class="mr-10 text-30 text-fore-placeholder">请选择</text>
-            </template>
-          </uv-cell>
-          <uv-cell isLink :border="false" :cellStyle="cellStyle" :rightIconStyle="rightIconStyle"
-            @click="handleMajorSelect">
-            <template #title>
-              <text class="text-30 text-fore-subtitle whitespace-nowrap">选择专业</text>
-            </template>
-            <template #value>
-              <text v-if="form.majorName" class="mx-10 text-30 text-fore-title">
-                {{ form.majorName }}
-              </text>
-              <text v-else class="mr-10 text-30 text-fore-placeholder">请选择</text>
-            </template>
-          </uv-cell>
-        </uv-cell-group>
-      </view>
-    </view>
-    <ie-safe-toolbar :height="84" :shadow="false">
-      <view class="px-46 pt-24">
-        <ie-button type="primary" @click="handleAdd">确认</ie-button>
-      </view>
-    </ie-safe-toolbar>
-    <ie-popup ref="popupRef" title="选择专业" @confirm="handleConfirm">
-      <view class="h-[50vh] bg-white px-30 pt-20 flex flex-col">
-        <view>
-          <uv-search v-model="keyword" shape="square" :showAction="false" placeholder="请输入专业名称" />
+    <ie-page bg-color="#F6F8FA" :fix-height="true">
+        <ie-navbar :title="title"/>
+        <view class="mt-16 bg-white py-10">
+            <view class="">
+                <uv-cell-group :border="false">
+                    <uv-cell isLink :border="true" :cellStyle="cellStyle" :rightIconStyle="rightIconStyle"
+                             @click="handleUniversitySelect">
+                        <template #title>
+                            <text class="text-30 text-fore-subtitle whitespace-nowrap">选择院校</text>
+                        </template>
+                        <template #value>
+                            <text v-if="form.universityName" class="mx-10 text-30 text-fore-title">
+                                {{ form.universityName }}
+                            </text>
+                            <text v-else class="mr-10 text-30 text-fore-placeholder">请选择</text>
+                        </template>
+                    </uv-cell>
+                    <uv-cell isLink :border="false" :cellStyle="cellStyle" :rightIconStyle="rightIconStyle"
+                             @click="handleMajorSelect">
+                        <template #title>
+                            <text class="text-30 text-fore-subtitle whitespace-nowrap">选择专业</text>
+                        </template>
+                        <template #value>
+                            <text v-if="form.majorName" class="mx-10 text-30 text-fore-title">
+                                {{ form.majorName }}
+                            </text>
+                            <text v-else class="mr-10 text-30 text-fore-placeholder">请选择</text>
+                        </template>
+                    </uv-cell>
+                </uv-cell-group>
+            </view>
         </view>
-        <view class="mt-20 flex-1 min-h-1">
-          <scroll-view scroll-y class="h-full">
-            <view v-for="item in filteredMajorList" :key="item.id"
-              class="px-20 py-16 bg-white sibling-border-top flex items-center gap-x-20" @click="handleSelect(item)">
-              <view class="flex-1 min-w-1 flex-shrink-0">
-                <view class="flex items-center gap-x-10 text-fore-title">
-                  <span class="text-30 " :class="[isActive(item) ? 'text-primary' : 'text-fore-title']">{{
-                    item.name
-                  }}</span>
-                  <span v-if="item.notice" class="text-22 text-fore-light flex-1 min-w-1 ellipsis-1"
-                    :class="[isActive(item) ? 'text-primary' : 'text-fore-title']">{{
-                      `(${item.notice})`
-                    }}</span>
+        <ie-safe-toolbar :height="84" :shadow="false">
+            <view class="px-46 pt-24">
+                <ie-button type="primary" @click="handleAdd">确认</ie-button>
+            </view>
+        </ie-safe-toolbar>
+        <ie-popup ref="popupRef" title="选择专业" @confirm="handleConfirm">
+            <view class="h-[50vh] bg-white px-30 pt-20 flex flex-col">
+                <view class="flex justify-between items-center gap-20">
+                    <ie-picker v-model="majorGroup" :list="majorGroups" width="w-220" icon="arrow-down"
+                               placeholder="专业组" custom-class="px-30 py-8 bg-[#F4F4F4] rounded"/>
+                    <uv-search v-model="keyword" shape="square" :showAction="false" placeholder="请输入专业名称"/>
                 </view>
-                <view class="mt-6 text-22 text-fore-light"
-                  :class="[isActive(item) ? 'text-primary' : 'text-fore-light']">
-                  {{
-                    item.ancestors
-                  }}
+                <view class="mt-20 flex-1 min-h-1">
+                    <scroll-view scroll-y class="h-full">
+                        <view v-for="([label, majors], idx) in groupedMajorList" :key="label">
+                            <view v-if="idx>0" class="h-16 bg-back mb-16 rounded-r-full"/>
+                            <view class="text-28 text-primary font-bold">{{label}}</view>
+                            <view v-for="item in majors" :key="item.id"
+                                  class="px-20 py-16 bg-white sibling-border-top flex items-center gap-x-20"
+                                  @click="handleSelect(item)">
+                                <view class="flex-1 min-w-1 flex-shrink-0">
+                                    <view class="flex items-center gap-x-10 text-fore-title">
+                  <span class="text-30 " :class="[isActive(item) ? 'text-primary' : 'text-fore-title']">{{
+                          item.name
+                      }}</span>
+                                        <span v-if="item.notice"
+                                              class="text-22 text-fore-light flex-1 min-w-1 ellipsis-1"
+                                              :class="[isActive(item) ? 'text-primary' : 'text-fore-title']">{{
+                                                `(${item.notice})`
+                                            }}</span>
+                                    </view>
+                                    <view class="mt-6 text-22 text-fore-light"
+                                          :class="[isActive(item) ? 'text-primary' : 'text-fore-light']">
+                                        {{
+                                            item.ancestors
+                                        }}
+                                    </view>
+                                </view>
+                                <uv-icon v-if="isActive(item)" name="checkmark" size="20" color="#31A0FC"/>
+                            </view>
+                        </view>
+                    </scroll-view>
                 </view>
-              </view>
-              <uv-icon v-if="isActive(item)" name="checkmark" size="20" color="#31A0FC" />
             </view>
-          </scroll-view>
-        </view>
-      </view>
-    </ie-popup>
-  </ie-page>
+        </ie-popup>
+    </ie-page>
 </template>
 
 <script lang="ts" setup>
-import { useTransferPage } from '@/hooks/useTransferPage';
-import { University } from '@/types/university';
-import { DirectedSchool, UniversityMajor } from '@/types/study';
-import { SelectedUniversityMajor } from '@/types/voluntary';
-import { getUniversityMajorList } from '@/api/modules/university';
-import { useUserStore } from '@/store/userStore';
-import { routes } from "@/common/routes";
-import { UniversityPickerPageOptions } from "@/types/transfer";
+import {useTransferPage} from '@/hooks/useTransferPage';
+import {University} from '@/types/university';
+import {DirectedSchool, UniversityMajor} from '@/types/study';
+import {SelectedUniversityMajor} from '@/types/voluntary';
+import {getUniversityMajorList} from '@/api/modules/university';
+import {useUserStore} from '@/store/userStore';
+import {routes} from "@/common/routes";
+import {UniversityPickerPageOptions} from "@/types/transfer";
+import _ from "lodash";
 
 const userStore = useUserStore();
-const { prevData, transferTo, transferBack } = useTransferPage<UniversityPickerPageOptions, any>();
-const { directedSchoolList } = toRefs(userStore);
+const {prevData, transferTo, transferBack} = useTransferPage<UniversityPickerPageOptions, any>();
+const {directedSchoolList} = toRefs(userStore);
 const form = ref<Partial<SelectedUniversityMajor>>({});
 const keyword = ref('');
+const majorGroup = ref('');
+const majorGroups = ref<{ label: string; value: string; }[]>([]);
 const cellStyle = {
-  padding: '20rpx 40rpx'
+    padding: '20rpx 40rpx'
 }
 const rightIconStyle = {
-  fontSize: '14px'
+    fontSize: '14px'
 }
 const majorList = ref<UniversityMajor[]>([]);
 const filteredMajorList = computed(() => {
-  return majorList.value.filter(item => item.name.includes(keyword.value));
+    return majorList.value.filter(item => majorGroup.value
+        ? item.majorGroup == majorGroup.value && item.name.includes(keyword.value)
+        : item.name.includes(keyword.value));
 });
+const groupedMajorList = computed(() => {
+    return Object.entries(_.groupBy(filteredMajorList.value, 'majorGroup'))
+})
 const title = computed(() => prevData.value.title || '添加定向院校')
 const handleUniversitySelect = () => {
-  transferTo(routes.universityPicker).then((res: any) => {
-    if (res) {
-      const university = res as University;
-      if (university.code !== form.value.universityId) {
+    transferTo(routes.universityPicker).then((res) => {
+        if (res) {
+            const university = res as University;
+            processUniversitySelected(university)
+        }
+    });
+}
+
+const processUniversitySelected = (university: University) => {
+    if (university.code !== form.value.universityId) {
         selectedMajor.value = null;
         form.value.majorId = '';
         form.value.majorName = '';
-      }
-      form.value.universityId = university.code;
-      form.value.universityName = university.name;
-      form.value.universityLogo = university.logo;
-      form.value.info = university;
-      loadMajorList(university.code);
+        form.value.majorGroup = '';
     }
-  });
+    form.value.universityId = university.code;
+    form.value.universityName = university.name;
+    form.value.universityLogo = university.logo;
+    form.value.info = university;
+    loadMajorList(university.code);
 }
 
 const popupRef = ref();
-const selectedMajor = ref<Pick<UniversityMajor, 'ancestors' | 'code' | 'id' | 'name'> | null>(null);
+const selectedMajor = ref<Pick<UniversityMajor, 'ancestors' | 'code' | 'id' | 'name' | 'majorGroup'> | null>(null);
 const handleMajorSelect = () => {
-  if (!form.value.universityId) {
-    uni.$ie.showToast('请选择院校');
-    return;
-  }
-  keyword.value = '';
-  if (form.value.majorId) {
-    selectedMajor.value = {
-      ancestors: form.value.majorAncestors || '',
-      code: form.value.majorId || '',
-      id: Number(form.value.majorId),
-      name: form.value.majorName || ''
+    if (!form.value.universityId) {
+        uni.$ie.showToast('请选择院校');
+        return;
     }
-  } else {
-    selectedMajor.value = null;
-  }
-  popupRef.value.open();
+    keyword.value = '';
+    if (form.value.majorId) {
+        selectedMajor.value = {
+            ancestors: form.value.majorAncestors || '',
+            code: form.value.majorId || '',
+            id: Number(form.value.majorId),
+            name: form.value.majorName || '',
+            majorGroup: form.value.majorGroup || ''
+        }
+    } else {
+        selectedMajor.value = null;
+    }
+    popupRef.value.open();
 }
 
 const handleConfirm = () => {
-  if (!selectedMajor.value) {
-    uni.$ie.showToast('请选择专业');
-    return;
-  }
-  form.value.majorAncestors = selectedMajor.value.ancestors;
-  form.value.majorId = selectedMajor.value.id.toString();
-  form.value.majorName = selectedMajor.value.name;
-  popupRef.value.close();
+    if (!selectedMajor.value) {
+        uni.$ie.showToast('请选择专业');
+        return;
+    }
+    form.value.majorAncestors = selectedMajor.value.ancestors;
+    form.value.majorId = selectedMajor.value.id.toString();
+    form.value.majorName = selectedMajor.value.name;
+    form.value.majorGroup = selectedMajor.value.majorGroup;
+    popupRef.value.close();
 }
 const handleSelect = (item: UniversityMajor) => {
-  selectedMajor.value = item;
+    selectedMajor.value = item;
 }
 
 const handleAdd = async () => {
-  if (!form.value.universityId) {
-    uni.$ie.showToast('请选择院校');
-    return;
-  }
-  if (!form.value.majorId) {
-    uni.$ie.showToast('请选择专业');
-    return;
-  }
-  if (prevData.value.fromVoluntary === true) {
-    // 志愿类
-    if (prevData.value.selectedUniversityId === form.value.universityId &&
-      prevData.value.selectedMajorId === form.value.majorId) {
-      return uni.$ie.showToast('请勿选择相同院校专业')
+    if (!form.value.universityId) {
+        uni.$ie.showToast('请选择院校');
+        return;
     }
-    return transferBack(form.value)
-  }
-  // 检查数据是否已存在
-  const historyData = directedSchoolList.value;
-  const isExist = historyData.some(item => item.universityId === form.value.universityId && item.majorId === form.value.majorId);
-  if (isExist) {
-    uni.$ie.showToast('该院校专业已存在');
-    return;
-  }
-  uni.$ie.showLoading();
-  const params = {
-    ...form.value,
-    code: form.value.universityId
-  } as DirectedSchool;
-  await userStore.saveDirectedSchoolList([params, ...directedSchoolList.value]);
-  uni.$ie.hideLoading();
-  uni.$ie.showSuccess('保存成功');
-  setTimeout(() => {
-    transferTo('/pagesStudy/pages/targeted-setting/targeted-setting', {
-      type: 'redirectTo'
-    });
-  }, 600);
+    if (!form.value.majorId) {
+        uni.$ie.showToast('请选择专业');
+        return;
+    }
+    if (prevData.value.fromVoluntary === true) {
+        // 志愿类
+        if (prevData.value.selectedUniversityId === form.value.universityId &&
+            prevData.value.selectedMajorId === form.value.majorId) {
+            return uni.$ie.showToast('请勿选择相同院校专业')
+        }
+        // TODO: 如果该页面是从 routes.universityPicker 通过 redirectTo 跳转,则前页面接收不到eventChannel的回传事件
+        // 日志:上级页面丢失,无法触发transferEnd
+        return transferBack(form.value)
+    }
+    // 检查数据是否已存在
+    const historyData = directedSchoolList.value;
+    const isExist = historyData.some(item => item.universityId === form.value.universityId && item.majorId === form.value.majorId);
+    if (isExist) {
+        uni.$ie.showToast('该院校专业已存在');
+        return;
+    }
+    uni.$ie.showLoading();
+    const params = {
+        ...form.value,
+        code: form.value.universityId
+    } as DirectedSchool;
+    await userStore.saveDirectedSchoolList([params, ...directedSchoolList.value]);
+    uni.$ie.hideLoading();
+    uni.$ie.showSuccess('保存成功');
+    setTimeout(() => {
+        const pages = getCurrentPages()
+        if (pages.some(page => '/' + page.route === routes.targetSetting)) {
+            transferBack()
+        } else {
+            // 这是从定向刷题跳转过来的
+            transferTo('/pagesStudy/pages/targeted-setting/targeted-setting', {
+                type: 'redirectTo'
+            })
+        }
+    }, 600);
 }
 const isActive = (item: UniversityMajor) => {
-  return selectedMajor.value && selectedMajor.value.id === item.id;
+    return selectedMajor.value && selectedMajor.value.id === item.id;
 }
 const loadMajorList = async (universityId: string) => {
-  uni.$ie.showLoading();
-  const { data } = await getUniversityMajorList({ universityId });
-  majorList.value = data;
-  uni.$ie.hideLoading();
+    uni.$ie.showLoading();
+    const {data} = await getUniversityMajorList({universityId});
+    majorList.value = data;
+    majorGroups.value = [{label: '不限', value: ''},
+        ..._.chain(data).map('majorGroup').uniq().map(g => ({label: g, value: g})).value()]
+    uni.$ie.hideLoading();
 }
 
 onLoad(() => {
-  handleUniversitySelect();
+    if (prevData.value.picked) {
+        processUniversitySelected(prevData.value.picked)
+    } else {
+        handleUniversitySelect();
+    }
 });
 </script>
 

+ 3 - 2
src/pagesStudy/pages/targeted-setting/targeted-setting.vue

@@ -42,6 +42,7 @@ import DirectedSchoolItem from './components/directed-school-item.vue';
 import { useTransferPage } from '@/hooks/useTransferPage';
 import { DirectedSchool } from '@/types/study';
 import { useUserStore } from '@/store/userStore';
+import {routes} from "@/common/routes";
 
 const userStore = useUserStore();
 const { transferTo } = useTransferPage();
@@ -52,8 +53,8 @@ const currentDirectedSchool = computed(() => directedSchoolList.value[0]);
 const otherDirectedSchoolList = computed(() => directedSchoolList.value.slice(1));
 
 const handleAdd = () => {
-  transferTo('/pagesStudy/pages/targeted-add/targeted-add', {
-    data: {}
+  transferTo(routes.universityPicker, {
+    data: {useRedirect: true}
   });
 }
 const handleChoose = (data: DirectedSchool) => {

+ 2 - 0
src/types/study.ts

@@ -347,6 +347,8 @@ export interface UniversityMajor {
   id: number;
   name: string;
   type: string;
+
+  majorGroup: string;
   notice?: string;
 }
 

+ 3 - 0
src/types/transfer.ts

@@ -1,4 +1,5 @@
 import { EnumPaperType } from "@/common/enum";
+import {University} from "@/types/university";
 
 export type TransferType = 'redirectTo' | 'reLaunch' | 'switchTab' | 'navigateTo' | 'navigateBack';
 
@@ -51,4 +52,6 @@ export interface UniversityPickerPageOptions {
   fromVoluntary: boolean;
   selectedUniversityId?: string;
   selectedMajorId?: string;
+  useRedirect?: boolean;
+  picked?: University;
 }