feat: 专业筛选

master
xjs 2025-05-22 16:18:37 +08:00
parent 1f3d5cfb04
commit 26703cd754
12 changed files with 441 additions and 34 deletions

View File

@ -23,8 +23,8 @@
v-model="formData.name"
placeholder="请输入姓名"
confirm-type="done"
placeholder-style="color:#BABABA;font-size:28rpx;"
class="text-start w-[140rpx]"
placeholder-style="color:#BABABA;font-size:28rpx;text-align:right;"
class="text-right w-[140rpx]"
/>
</view>
</view>
@ -63,8 +63,8 @@
v-model="formData.school"
placeholder="请输入您的就读学校"
confirm-type="done"
placeholder-style="color:#BABABA;font-size:28rpx;"
class="text-start w-[252rpx]"
placeholder-style="color:#BABABA;font-size:28rpx;text-align:right;"
class="text-right w-[252rpx]"
/>
</view>
</view>
@ -132,6 +132,7 @@ const handleSubmit = () => {
inviteCode: formData.value.invitedCode,
}).then((resp) => {
if (resp.code === 200) {
userStore.setEstimatedAchievement({ init: true })
uni.switchTab({
url: '/pages/home/index/index',
})
@ -150,7 +151,6 @@ const handleInviteCode = () => {
verifyInviteCode({ code: formData.value.invitedCode }).then((resp) => {
if (resp.code === 200) {
invitedCodeFlag.value = resp.result as boolean
userStore.setEstimatedAchievement({ init: true })
}
})
} else {
@ -166,11 +166,6 @@ const handleLogout = () => {
const instance = getCurrentInstance()
onUnload(() => {
console.log(
' userStore.userInfo.estimatedAchievement.init',
userStore.userInfo.estimatedAchievement.init,
)
if (!userStore.userInfo.estimatedAchievement.init) {
handleLogout()
}

View File

@ -79,7 +79,7 @@ const props = defineProps({
},
max: {
type: Number,
default: 100,
default: 200,
},
step: {
type: Number,

View File

@ -1,7 +1,5 @@
<template>
<view
class="flex items-center rounded-[48rpx] bg-[#F7F7F7]! px-[24rpx] py-[12rpx] w-[350rpx] ml-[32rpx]"
>
<view class="flex items-center rounded-[48rpx] bg-[#F7F7F7]! px-[24rpx] py-[12rpx] ml-[32rpx]">
<view class="i-carbon-search text-[#999]"></view>
<input
v-model="searchValue"

View File

@ -11,7 +11,7 @@ export default defineComponent({
name: 'ProgressBar',
props: {
progress: {
type: Number,
type: Number | String,
required: true,
default: 0,
},

View File

@ -35,6 +35,7 @@
:focus="true"
class="flex-1"
confirm-type="done"
@blur="handleBlurChange"
/>
<text class="" :selectable="false" :decode="false"></text>
</view>
@ -154,12 +155,19 @@ const navigatorBack = () => {
//
const score = ref<number | string>(userStore.userInfo.estimatedAchievement.expectedScore || '')
const handleChange = (e: any) => {
const _score = Number(score.value) || 0
let _score = Number(score.value) || 0
if (_score > userStore.userInfo.city.allscore) {
score.value = userStore.userInfo.city?.allscore
}
}
const handleBlurChange = () => {
let _score = Number(score.value) || 0
if (_score < 150) {
score.value = 150
}
}
//
const requireSubject = ref<number | string>('')

View File

@ -0,0 +1,22 @@
<template>
<view class="flex items-center">
<view class="i-carbon-checkbox w-[32rpx] h-[32rpx]" v-if="status === 1"></view>
<view
class="i-carbon-checkbox-undeterminate-filled w-[32rpx] h-[32rpx] text-[#1677FF]"
v-if="status === 2"
></view>
<view
class="i-carbon-checkbox-checked-filled w-[32rpx] h-[32rpx] text-[#1677FF]"
v-if="status === 3"
></view>
</view>
</template>
<script lang="ts" setup>
const props = defineProps({
status: {
type: Number,
default: 1,
},
})
</script>

View File

@ -0,0 +1,188 @@
<template>
<view class="bg-white flex flex-col">
<view class="flex">
<view class="w-[176rpx] h-[45vh]">
<scroll-view class="bg-[#F7F8FA] flex flex-col items-center" :scroll-y="true">
<view
v-for="item in menus"
:key="item.key"
:class="[
'flex items-center view-block pl-[32rpx] min-h-[96rpx] h-[96rpx] text-[28rpx] text-[#666] font-semibold',
currentMenuObj?.key === item.key ? 'active' : '',
]"
@click="changeMenu(item)"
>
{{ item.name }}
</view>
</scroll-view>
</view>
<scroll-view class="pb-[14rpx] h-[40vh] pt-[32rpx] flex-1" :scroll-y="true">
<view>
<MajorTreeList
v-if="currentMenuObj"
v-model:subMajorList="currentMenuObj"
@change-check="handleChangeCheck"
/>
</view>
</scroll-view>
</view>
<scroll-view class="box-shadow w-full" :scroll-x="true">
<view class="flex items-center gap-[24rpx] w-max px-[32rpx] pt-[32rpx] pb-[40rpx] h-[58rpx]">
<view
class="px-[20rpx] py-[12rpx] bg-[#f8f8f8] rounded-[8rpx]"
v-for="(item, index) in filterParams"
:key="item.zymc"
>
<text class="text-[#303030] text-[24rpx] font-medium mr-[16rpx]">
{{ item.zymc }}
</text>
<view
class="i-carbon-close-filled text-[#CBCBCB] text-[24rpx]"
@click="handleRemove(item, index)"
></view>
</view>
</view>
</scroll-view>
</view>
</template>
<script lang="ts" setup>
import { getMajorLevelList } from '@/service/index/api'
import MajorTreeList from './FilterMajorTreeList.vue'
const props = defineProps({
type: {
type: Number,
default: 1050,
},
keyword: {
type: String,
default: '',
},
})
const filterParams = ref([])
const handleRemove = (item, index) => {
filterParams.value.splice(index, 1)
menus.value[item.groupIndex].items[item.majorIndex].childMajors[item.cIndex].check = 1
}
const menus = ref([])
const currentMenuObj = ref(null)
const changeMenu = (item: any) => {
currentMenuObj.value = item
}
const handleChangeCheck = (params) => {
console.log(params)
const groupIndex = menus.value.findIndex((menu) => menu.key === params.groupItem.key)
if (Object.prototype.toString.call(params.cIndex) === '[object Undefined]') {
//
if (params.pItem.check === 3) {
//
params.pItem.childMajors.forEach((childItem, cIndex) => {
//
const isExist = filterParams.value.some((item) => item.zydm === childItem.zydm)
if (!isExist) {
filterParams.value.push({
...childItem,
groupIndex,
majorIndex: params.pIndex,
cIndex: cIndex,
pName: params.pItem.name,
})
}
})
} else if (params.pItem.check === 1) {
//
filterParams.value = filterParams.value.filter(
(item) => !params.pItem.childMajors.some((childItem) => childItem.zydm === item.zydm),
)
}
} else {
//
if (params.pItem.childMajors[params.cIndex].check === 3) {
//
const childItem = params.pItem.childMajors[params.cIndex]
const isExist = filterParams.value.some((item) => item.zydm === childItem.zydm)
if (!isExist) {
filterParams.value.push({
...childItem,
groupIndex,
majorIndex: params.pIndex,
cIndex: params.cIndex,
pName: params.pItem.name,
})
}
} else if (params.pItem.childMajors[params.cIndex].check === 1) {
//
filterParams.value = filterParams.value.filter(
(item) => item.zydm !== params.pItem.childMajors[params.cIndex].zydm,
)
}
}
}
watch(
() => currentMenuObj.value,
(newV) => {
const index = menus.value.findIndex((item) => item.key === newV.key)
menus.value[index] = newV
},
{ deep: true },
)
const getMajorList = ({ Type, KeyWord }) => {
getMajorLevelList({ Type, KeyWord }).then((resp) => {
if (resp.code === 200) {
menus.value = resp.result[0].rootDtos.map((tItem) => ({
...tItem,
items: tItem.items.map((item) => ({
...item,
childMajors: item.childMajors.map((cItem) => ({ ...cItem, check: 1 })),
check: 1,
})),
}))
currentMenuObj.value = menus.value[0]
// wrapFilterParams = Array.from({ length: menus.value.length }, () => [])
}
})
}
watch(
[() => props.type],
([type]) => {
getMajorList({ Type: type, KeyWord: props.keyword })
},
{ immediate: true },
)
watch(
() => props.keyword,
(newV) => {
getMajorList({ Type: props.type, KeyWord: newV })
},
)
const getSearchParam = () => {
return filterParams.value
}
defineExpose({ getSearchParam })
</script>
<style lang="scss" scoped>
.active {
background-color: #fff;
color: #1580ff;
}
.view-block {
width: calc(100% - 32rpx);
}
.box-shadow {
box-shadow: 0rpx -8rpx 8rpx 0rpx rgba(225, 225, 225, 0.2);
}
</style>

View File

@ -0,0 +1,170 @@
<template>
<scroll-view class="h-full">
<view class="font-semibold text-[#303030] text-[28rpx] px-[32rpx]">
{{ subMajorList.name }}
<text class="text-[22rpx] text-[#bfbfbf] font-normal">({{ subMajorList.count }})</text>
</view>
<Collapse v-model="collapseValue" accordion type="line">
<CollapseItem v-for="(item, index) in subMajorList.items" :key="item.key" :name="`${index}`">
<template #title="{ expanded }">
<view class="header">
<text :class="`text-[24rpx] text-[#303030] ${expanded ? 'text-[#1580FF]!' : ''}`">
{{ item.name }}
</text>
<view class="flex items-center justify-between">
<text
:class="`mr-[12rpx] text-[24rpx] text-[#303030] ${expanded ? 'text-[#1580FF]!' : ''}`"
>
{{ item.childMajors.length }}
</text>
<view
class="i-carbon-chevron-down rotate-180 text-[16rpx] text-[#1580FF]"
v-if="expanded"
></view>
<view class="i-carbon-chevron-down text-[16rpx]" v-else></view>
<view @click.stop="handleCheckSubMajor(item, index)" class="ml-[20rpx]">
<CustomCheckbox :status="item.check" />
</view>
</view>
</view>
</template>
<view class="flex flex-col gap-[48rpx] h-full mb-[40rpx]">
<view
class="text-[24rpx] text-[#303030] ml-[32rpx] flex items-center justify-between"
v-for="(childMajor, cIndex) in item.childMajors"
:key="childMajor.zymc"
@click="handleCheckMajor(childMajor, index, cIndex)"
>
<view>
{{ childMajor.zymc }}
</view>
<view class="mr-[32rpx]">
<CustomCheckbox :status="childMajor.check" />
</view>
</view>
</view>
</CollapseItem>
</Collapse>
</scroll-view>
</template>
<script lang="ts" setup>
import Collapse from '@/pages-sub/components/collapse/Collapse.vue'
import CollapseItem from '@/pages-sub/components/collapse/CollapseItem.vue'
import CustomCheckbox from './CustomCheckbox.vue'
const props = defineProps({
subMajorList: {
type: Object as () => {
name: string
key: string
count: number
itemsCount: number
items: any[]
},
default: () => ({
name: '',
key: '',
count: 0,
itemsCount: 0,
items: [],
}),
},
})
const collapseValue = ref<string>('0')
const emits = defineEmits(['update:subMajorList', 'changeCheck'])
const handleCheckSubMajor = (_item: any, index: number) => {
const newItems = [...props.subMajorList.items]
newItems[index] = {
...newItems[index],
childMajors: newItems[index].childMajors.map((cItem) => ({
...cItem,
check: newItems[index].check === 3 ? 1 : 3,
})),
check: newItems[index].check === 3 ? 1 : 3,
}
const newSubMajorList = {
...props.subMajorList,
items: newItems,
}
emits('update:subMajorList', newSubMajorList)
emits('changeCheck', { pIndex: index, pItem: newItems[index], groupItem: props.subMajorList })
}
const handleCheckMajor = (_item: unknown, index: number, cIndex: number) => {
const newItems = [...props.subMajorList.items]
let childItems = [...newItems[index].childMajors]
childItems[cIndex].check = childItems[cIndex].check === 3 ? 1 : 3
let fatherCheck = childItems.filter((cItem) => cItem.check === 1)
newItems[index] = {
...newItems[index],
childMajors: childItems,
check:
fatherCheck.length > 0 && fatherCheck.length < childItems.length
? 2
: fatherCheck.length === childItems.length
? 1
: 3,
}
const newSubMajorList = {
...props.subMajorList,
items: newItems,
}
emits('update:subMajorList', newSubMajorList)
emits('changeCheck', {
pIndex: index,
pItem: newItems[index],
cItem: childItems[cIndex],
cIndex,
groupItem: props.subMajorList,
})
}
watch(
() => props.subMajorList,
(newVal) => {
if (newVal?.items) {
newVal.items.forEach((item) => {
const checkedCount = item.childMajors.filter((child) => child.check === 3).length
const totalCount = item.childMajors.length
if (checkedCount === 0) {
//
item.check = 1
} else if (checkedCount === totalCount) {
//
item.check = 3
} else {
//
item.check = 2
}
})
}
},
{ deep: true },
)
</script>
<style lang="scss" scoped>
:deep(.custom-collapse-item) {
padding: 16rpx 0 !important;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
}
:deep(.uni-collapse-item__wrap-content) {
border-bottom: 0;
}
</style>

View File

@ -27,20 +27,29 @@
/>
<HeaderTip :user-info="userStore.userInfo" />
<view class="flex justify-between items-center px-[32rpx] py-[16rpx]">
<view class="flex items-center gap-[24rpx] text-[#303030]">
<view class="flex items-center gap-[20rpx] text-[#303030]">
<view
@click="handleShow(1)"
class="flex justify-between items-center text-[28rpx] line-height-none"
class="flex justify-between items-center text-[28rpx] line-height-none text-nowrap"
>
院校
<view class="text-nowrap w-[56rpx]">院校</view>
<view class="i-carbon-caret-down"></view>
</view>
<view
@click="handleShow(2)"
class="flex justify-between items-center text-[28rpx] line-height-none ml-[20rpx]"
class="flex justify-between items-center text-[28rpx] line-height-none text-nowrap"
>
地域
<view class="text-nowrap w-[56rpx]">地域</view>
<view class="i-carbon-caret-down"></view>
</view>
<view
@click="handleShow(4)"
class="flex justify-between items-center text-[28rpx] line-height-none text-nowrap"
>
<view class="text-nowrap w-[56rpx]">专业</view>
<view class="i-carbon-caret-down"></view>
</view>
</view>
@ -114,10 +123,11 @@
label-key="name"
@change="handleTypeModelChange"
/>
<FilterMajorList ref="filterMajorRef" v-if="actionType === 4" :type="1050" class="h-full" />
<template #footer>
<view class="flex items-center justify-between mx-[32rpx] gap-[20rpx]">
<view class="cancel-btn" @click="show = false">取消</view>
<view class="submit-btn" @click="handleConfirm"></view>
<view class="submit-btn" @click="handleConfirm"></view>
</view>
</template>
</ActionSheet>
@ -149,6 +159,8 @@ import { getPlanProListByFilter } from '@/service/index/api'
import { useUserStore } from '@/store/user'
import { coverTypeModel, useScore } from './composable/useWishesList'
import FilterMajorList from './components/FilterMajorList.vue'
const userStore = useUserStore()
const navigatorBack = () => {
@ -158,6 +170,7 @@ const navigatorBack = () => {
const schoolList = ref([])
const paging = ref(null)
const filterMenuRef = ref(null)
const filterMajorRef = ref(null)
const location = ref(userStore.userInfo.estimatedAchievement.provinceCode)
@ -197,6 +210,7 @@ const queryList = (page: number, pageSize: number) => {
keyword: searchValue.value,
type: collegeSearch.value.tModelValue === '-1' ? null : +collegeSearch.value.tModelValue,
utype: collegeSearch.value.utype,
secondmajor: collegeSearch.value.secondmajor,
}).then((resp) => {
if (resp.code === 200) {
paging.value.complete((resp.result as { rows: any[] }).rows)
@ -223,10 +237,11 @@ const itemClick = (item) => {
const collegeSearch = ref({
nature: [],
feature: [],
majors: [],
majors: [], //
tModel: '',
utype: [],
tModelValue: '-1',
secondmajor: [], //
})
const show = ref(false)
@ -238,8 +253,16 @@ const handleConfirm = () => {
let _cur = filterMenuRef.value.handleConfirm()
collegeSearch.value.feature = _cur.chooseCollegeFeature
collegeSearch.value.nature = _cur.chooseNature
collegeSearch.value.majors = _cur.chooseMajors
collegeSearch.value.utype = _cur.chooseUniType
} else if (actionType.value === 4) {
let _cur = filterMajorRef.value.getSearchParam()
let temSet = new Set()
collegeSearch.value.secondmajor = []
_cur.forEach((item) => {
temSet.add(item.pName)
collegeSearch.value.secondmajor.push(item.zymc)
})
collegeSearch.value.majors = Array.from(temSet.values())
}
paging.value.reload()
}
@ -249,6 +272,8 @@ const handleShow = (type: number) => {
actionTitle.value = '院校'
} else if (type === 2) {
actionTitle.value = '地域'
} else if (type === 4) {
actionTitle.value = '专业筛选'
}
actionType.value = type
@ -267,7 +292,9 @@ const handleRegionName = (val: string[]) => {
}
const sliderValue = ref([
+userStore.userInfo.estimatedAchievement.expectedScore - 100,
+userStore.userInfo.estimatedAchievement.expectedScore < 200
? +userStore.userInfo.estimatedAchievement.expectedScore
: +userStore.userInfo.estimatedAchievement.expectedScore - 100,
+userStore.userInfo.estimatedAchievement.expectedScore,
])
const handleSliderChange = (val) => {

View File

@ -23,8 +23,8 @@
v-model="formData.name"
placeholder="请输入姓名"
confirm-type="done"
placeholder-style="color:#BABABA;font-size:28rpx;"
class="text-start w-[140rpx]"
placeholder-style="color:#BABABA;font-size:28rpx;text-align:right;"
class="text-right w-[140rpx]"
/>
</view>
</view>
@ -65,8 +65,8 @@
v-model="formData.school"
placeholder="请输入您的就读学校"
confirm-type="done"
placeholder-style="color:#BABABA;font-size:28rpx;"
class="text-start w-[252rpx]"
placeholder-style="color:#BABABA;font-size:28rpx;text-align:right;"
class="text-right w-[252rpx]"
/>
</view>
</view>
@ -81,8 +81,8 @@
placeholder="请输入您的邀请码"
confirm-type="done"
:maxlength="4"
placeholder-style="color:#BABABA;font-size:28rpx;"
class="text-start w-[252rpx]"
placeholder-style="color:#BABABA;font-size:28rpx;text-align:right;"
class="text-right w-[252rpx]"
@input="handleInviteCode"
/>
</view>
@ -95,7 +95,7 @@
:class="'bg-[#1580FF]!'"
@click="handleSubmit"
>
提交
保存
</button>
</view>
</view>

View File

@ -182,8 +182,6 @@ const toSetting = () => {
}
const goLogin = () => {
console.log('hhh')
if (!userStore.userInfo.openid) {
uni.navigateTo({
url: '/login-sub/index',

View File

@ -267,6 +267,7 @@ export interface AutoFillParams {
subjects?: string[] | null
subjectType?: string[] | null
type?: number | null
secondmajor?: string[]
}
export const getUniversityListByFilter = (params: AutoFillParams) => {
return http.post('/api/PlanPro/v2/oneKey', params)