feat: 当前志愿表编写中

master
xjs 2025-03-17 18:02:47 +08:00
parent e3977c7e89
commit 7c0357b67d
22 changed files with 1344 additions and 274 deletions

View File

@ -7,7 +7,7 @@
}
</route>
<template>
<view class="bg-[#FAFAFA]">
<view class="bg-[#FAFAFA] h-screen flex flex-col">
<view class="text-[#000] text-[24rpx] p-[32rpx]">
{{ userInfo.estimatedAchievement.expectedScore }}{{
userInfo.estimatedAchievement.subjectGroup.split(',').join('/')
@ -47,7 +47,9 @@
</view>
</view>
<view class="mt-[32rpx] bg-[#fff] rounded-[16rpx] p-[32rpx] box-shadow px-[32rpx] pt-[32rpx]">
<view
class="mt-auto bg-[#fff] rounded-[16rpx] p-[32rpx] box-shadow px-[32rpx] pt-[32rpx] pb-safe"
>
<view
class="text-[#fff] text-[32rpx] rounded-[8rpx] bg-[#1580FF] text-center py-[26rpx]"
@click="navigatorTo"

View File

@ -24,7 +24,7 @@
</template>
<script lang="ts" setup>
defineProps<{
const props = defineProps<{
show: boolean
title?: string
lazyRender?: boolean

View File

@ -0,0 +1,70 @@
<template>
<div class="collapse-container">
<div class="collapse-header" @click="toggleCollapse">
<slot name="header"></slot>
<div class="collapse-icon" :class="{ 'is-active': isCollapsed }">
<i class="el-icon-arrow-down"></i>
</div>
</div>
<div class="collapse-content" :class="{ 'is-collapsed': isCollapsed }">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'Collapse',
props: {
defaultCollapsed: {
type: Boolean,
default: false,
},
},
data() {
return {
isCollapsed: this.defaultCollapsed,
}
},
methods: {
toggleCollapse() {
this.isCollapsed = !this.isCollapsed
},
},
}
</script>
<style scoped>
.collapse-container {
display: grid;
grid-template-rows: auto 1fr;
width: 100%;
}
.collapse-header {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
padding: 10px;
border-radius: 4px;
}
.collapse-icon {
transition: transform 0.3s ease;
}
.collapse-icon.is-active {
transform: rotate(180deg);
}
.collapse-content {
overflow: hidden;
transition: grid-template-rows 0.3s ease;
grid-template-rows: 1fr;
}
.collapse-content.is-collapsed {
grid-template-rows: 0fr;
}
</style>

View File

@ -0,0 +1,754 @@
<template>
<view
class="leo-drag"
id="leo-drag"
ref="leoDragView"
:style="[{ 'touch-action': isStopPropagation ? 'none' : 'auto' }]"
>
<!-- touch-action: 阻止滚动 -->
<movable-area :style="[getAreaStyle]" v-if="showArea">
<movable-view
v-if="isStopPropagation"
v-for="(item, index) in showList"
:animation="animation"
:direction="direction"
:key="item.key"
:damping="damping"
:x="item.x"
:y="item.y"
:disabled="longpress ? disabled : false"
@longpress="controlLongpress(index)"
@touchstart.stop="handleDragStart($event, index)"
@change="handleMoving"
@touchend="handleDragEnd"
:style="[
{ 'z-index': activeIndex === index ? 9 : 1 },
{ top: activeIndex === index ? scrollInfo.y + 'px' : 0 },
getItemStyle,
isList ? { height: item.height + 'px' } : {},
]"
class="base-drag-wrapper"
>
<view class="slotContent" ref="slotContent" :style="[slotContentStyle]">
<slot :data="{ ...item, index, activeIndex, moveToIndex }" name="content"></slot>
</view>
<view
v-if="showPlaceholder(index)"
class="placeholder"
:style="{ height: showList[activeIndex]?.height + 'px', top: placeholderTopValue + 'px' }"
>
<slot name="placeholder"></slot>
</view>
</movable-view>
<movable-view
v-else
v-for="(item, index) in showList"
:animation="animation"
:direction="direction"
:key="`${item.key}-${index}`"
:damping="damping"
:x="item.x"
:y="item.y"
:disabled="longpress ? disabled : false"
@longpress="controlLongpress(index)"
@touchstart="handleDragStart($event, index)"
@change="handleMoving"
@touchend="handleDragEnd"
:style="[
{ 'z-index': activeIndex === index ? 9 : 1 },
{ top: activeIndex === index ? scrollInfo.y + 'px' : 0 },
getItemStyle,
isList ? { height: item.height + 'px' } : {},
]"
class="base-drag-wrapper"
>
<view class="slotContent" ref="slotContent" :style="[slotContentStyle]">
<slot :data="{ ...item, index, activeIndex, moveToIndex }" name="content"></slot>
</view>
<view
v-if="showPlaceholder(index)"
class="placeholder"
:style="{ height: showList[activeIndex]?.height + 'px', top: placeholderTopValue + 'px' }"
>
<slot name="placeholder"></slot>
</view>
</movable-view>
</movable-area>
<!-- 可用于遮罩指引提示考虑定位的zindex问题所以使用插槽插进来 -->
<slot name="custom"></slot>
</view>
</template>
<script setup lang="ts">
import { ref, computed, watch, nextTick, onMounted, getCurrentInstance } from 'vue'
import { deepCopy } from './useSort'
// #ifdef APP-NVUE
const dom = uni.requireNativePlugin('dom') //
// #endif
// MovableViewDirection
type MovableViewDirection = 'all' | 'vertical' | 'horizontal' | 'none'
interface ItemType {
key?: string
x?: number
y?: number
height?: number
dropId?: number
[key: string]: any // 访
}
interface ScrollInfoType {
x: number
y: number
}
interface PositionType {
y: number
}
const props = defineProps({
slotContentStyle: {
// slotContent
type: Object,
default: () => ({}),
},
list: {
//
type: Array as () => ItemType[],
default: () => [],
},
column: {
// (column1),
type: Number,
default: 1,
},
height: {
// auto (itemHeight)
type: String,
default: 'auto',
},
itemHeight: {
// auto
type: String,
default: 'auto',
},
direction: {
//
type: String as () => MovableViewDirection,
default: 'all',
validator: (value: string) => {
return ['all', 'vertical', 'horizontal', 'none'].includes(value)
},
},
dragDirection: {
// upward- down-
type: String,
default: 'down',
},
animation: {
//
type: Boolean,
default: true,
},
damping: {
// xy
type: Number,
default: 20,
},
swapMode: {
// true:false
type: Boolean,
default: true,
},
longpress: {
//
type: Boolean,
default: true,
},
supportLongpress: {
//
type: Boolean,
default: false,
},
isStopPropagation: {
// :
type: Boolean,
default: false,
},
})
const emit = defineEmits([
'getAreaStyle',
'getItemStyle',
'pressStart',
'handleDragStart',
'handleMoving',
'handleDragEnd',
'getList',
'update:list',
'getItemHeight',
])
//
const showList = ref<ItemType[]>([]) // cloneList
const cloneList = ref<ItemType[]>([]) // 使cloneListshowListcloneListshowList
const disabled = ref(true) //
const activeIndex = ref(-1) // item
const moveToIndex = ref(-1) // item > item
const oldIndex = ref(-1) // item
const tempDragInfo = ref({
//
x: '',
y: '',
})
const oldPosition = ref<PositionType>({ y: 0 }) //
const itemMaxHeight = ref('80px')
const width = ref('100%')
const showArea = ref(false) // nvue
const viewMaxHeight = ref(80) // isList === true使
const activeY = ref(0) // y
const scrollInfo = ref<ScrollInfoType>({ x: 0, y: 0 }) //
// refs
const leoDragView = ref<any>(null)
const slotContent = ref<any[]>([])
const instance = getCurrentInstance()
//
const placeholderTopValue = computed(() => {
let height = 0
try {
if (showList.value && showList.value[activeIndex.value]) {
height = -showList.value[activeIndex.value].height!
}
} catch (e) {
console.log('eee', e)
}
return height
})
// :
const isList = computed(() => {
let is = false
// #ifndef APP-NVUE
is = props.column === 1 //
// #endif
return is
})
// item(isList === true) --- item
const getItemHeight = computed(() => {
// item
let value =
props.itemHeight === 'auto' ? parseFloat(itemMaxHeight.value) : parseFloat(props.itemHeight)
emit('getItemHeight', value)
return value
})
// item
const getItemWidth = computed(() => {
// item
const w = getRealWidth(width.value)
return (parseFloat(w) / props.column).toFixed(2)
})
//
const getAreaStyle = computed(() => {
//
const w = getRealWidth(width.value)
let h: string | number = props.height
if (h === 'auto') {
// / * = 使viewMaxHeightitem item
h = isList.value
? viewMaxHeight.value
: Math.ceil(showList.value.length / props.column) * getItemHeight.value
}
let style = {
width: w + 'px',
height: typeof h === 'number' ? h + 'px' : h,
}
emit('getAreaStyle', style)
return style
})
// item
const getItemStyle = computed(() => {
let itemHeightWidth = {
width: getItemWidth.value + 'px',
height: getItemHeight.value + 'px',
}
emit('getItemStyle', itemHeightWidth)
return itemHeightWidth
})
//
const getRealWidth = (w: string | number): string => {
let width = w + ''
if (width.includes('%')) {
const windowWidth = uni.getSystemInfoSync().windowWidth
width = (windowWidth * (parseFloat(width) / 100)).toString()
}
return width
}
// xykey
const omit = (obj: any, args: string[] = ['x', 'y', 'key', 'dropId', 'height']) => {
if (!args) return obj
const newObj: any = {}
const isString = typeof args === 'string'
const keys = Object.keys(obj).filter((item) => {
if (isString) {
return item !== args
}
return !args.includes(item)
})
keys.forEach((key) => {
if (obj[key] !== undefined) newObj[key] = obj[key]
})
return newObj
}
//
const getPosition = (index: number, list = cloneList.value): [number, number] => {
//
let x = (index % props.column) * Number(getItemWidth.value)
let y = 0
if (isList.value) {
// getItemHeightitem
for (let i = 0; i < list.length; i++) {
if (index === i) break
y += list[i].height!
}
} else {
// item
y = Math.floor(index / props.column) * getItemHeight.value
}
return [x, y]
}
//
const initList = (list: ItemType[] = [], changeheight: boolean = false) => {
const newList = deepCopy(list)
// itemxykey
showList.value = newList.map((item, index) => {
const [x, y] = getPosition(index)
let data = {
...item,
x,
y,
dropId: index + 1,
}
let key = 'slot' + Math.random() + index
// xykey
if (x === item?.x && y === item?.y) {
if (activeIndex.value !== index) {
// key
key = item.key
}
}
//
data.key = key
return data
})
cloneList.value = deepCopy(showList.value)
nextTick(() => {
showArea.value = true
})
if (changeheight && props.itemHeight === 'auto') {
// item
nextTick(() => {
setTimeout(async () => {
// #ifdef APP-NVUE
showArea.value = false
let max = 0
for (let i = 0; i < slotContent.value.length; i++) {
//
await new Promise<void>((resolve) => {
let viewMaxHeightVal = 0
dom.getComponentRect(slotContent.value[i], (res: any) => {
let size = res.size
if (isList.value) {
cloneList.value[i].height = size.height
}
viewMaxHeightVal += size.height
if (size.height > max) {
max = size.height
}
resolve()
})
viewMaxHeight.value = viewMaxHeightVal
})
}
itemMaxHeight.value = max + 'px'
nextTick(() => {
initList(cloneList.value)
})
// #endif
// #ifndef APP-NVUE
const query = uni.createSelectorQuery().in(instance.proxy)
query
.selectAll('.slotContent')
.boundingClientRect((data) => {
let domList = JSON.parse(JSON.stringify(data))
let max = 0
let viewMaxHeightVal = 0
for (let i = 0; i < domList.length; i++) {
let height = domList[i].height
if (isList.value) {
cloneList.value[i].height = height
}
viewMaxHeightVal += height
if (height > max) {
max = height
}
}
viewMaxHeight.value = viewMaxHeightVal //
itemMaxHeight.value = max + 'px'
initList(cloneList.value)
})
.exec()
// #endif
}, 0)
})
}
}
//
//
const showPlaceholder = (i: number): boolean => {
// ,
let isShow = false
if (moveToIndex.value > activeIndex.value) {
// i-1;
if (moveToIndex.value === i - 1) {
isShow = true
}
} else if (moveToIndex.value === activeIndex.value) {
return false //
} else if (moveToIndex.value < activeIndex.value) {
if (moveToIndex.value === i) {
isShow = true
}
}
return isShow
}
//
const controlLongpress = (i: number) => {
if (props.supportLongpress) {
handleLongpress(i)
}
}
//
const handleDragStart = (event: any, index: number) => {
emit('pressStart', { event, data: cloneList.value[index] })
}
//
const handleLongpress = (index: number) => {
disabled.value = false
//
activeIndex.value = index
//
oldIndex.value = index
emit('handleDragStart', index)
}
// y
const changeScrollInfo = (detail: { y: number }) => {
//
scrollInfo.value.y += detail.y
}
//
const handleMoving = (e: any) => {
if (e.detail.source !== 'touch') return
let { x, y } = e.detail
if (isList.value) {
//
y = y + scrollInfo.value.y
oldPosition.value.y = y
}
changeListPosition({ x, y })
}
//
const changeListPosition = ({ x = 0, y = 0, type }: { x: number; y: number; type?: string }) => {
// x itemy
let currentX = Math.floor((x + Number(getItemWidth.value) / 2) / Number(getItemWidth.value))
let currentY = Math.floor((y + getItemHeight.value / 2) / getItemHeight.value)
// moveToIndex xy
moveToIndex.value = Math.min(currentY * props.column + currentX, cloneList.value.length - 1) //
// --
if (isList.value) {
// , x; y;
let currentH = 0
for (let i = 0; i < cloneList.value.length; i++) {
currentH += cloneList.value[i].height!
if (props.dragDirection === 'down') {
// ---- ( )
/*
下一个下标的5/1位置触发
如果到了最后一个时让下一个触发的高度变成当前拖动到指定位置的高度避免照成到达了底部后下标位置有差异的问题
**/
let nextH = cloneList.value[i + 1] ? cloneList.value[i + 1].height! / 5 : currentH
if (currentH > y + cloneList.value[activeIndex.value].height! - nextH) {
moveToIndex.value = i
break
}
} else {
// ---- ( )
let theH = cloneList.value[i - 1] ? cloneList.value[i - 1].height! / 5 : 0 // 下一个下标的5/1位置触发
if (currentH > y + theH) {
moveToIndex.value = i
break
}
}
}
}
if (oldIndex.value !== moveToIndex.value && oldIndex.value !== -1 && moveToIndex.value !== -1) {
//
const newList = deepCopy(cloneList.value) // cloneListshowList
const replaceList = deepCopy(cloneList.value) // cloneListshowList
let splicItem = newList.splice(activeIndex.value, 1)[0] //
newList.splice(moveToIndex.value, 0, splicItem) //
if (props.swapMode) {
if (isList.value) {
// ---
replaceList.splice(moveToIndex.value, 0, ...replaceList.splice(activeIndex.value, 1))
}
//
showList.value.forEach((item, index) => {
if (index !== activeIndex.value) {
let itemIndex = newList.findIndex((val) => val.dropId === item.dropId)
// yheightyheight
let position = getPosition(itemIndex, replaceList)
item.x = position[0]
item.y = position[1]
}
})
} else {
//
replaceList[activeIndex.value].height = showList.value[moveToIndex.value].height
replaceList[moveToIndex.value].height = showList.value[activeIndex.value].height
showList.value.forEach((item, index) => {
if (isList.value) {
if (props.dragDirection === 'down') {
if (index > activeIndex.value && index < moveToIndex.value) {
// y
;[item.x, item.y] = getPosition(index, replaceList)
}
} else if (index < activeIndex.value && index > moveToIndex.value) {
// y
;[item.x, item.y] = getPosition(index, replaceList)
}
}
//
if (index === oldIndex.value) {
;[item.x, item.y] = getPosition(oldIndex.value, replaceList)
}
//
if (index === moveToIndex.value) {
;[item.x, item.y] = getPosition(activeIndex.value, replaceList)
}
})
}
oldIndex.value = moveToIndex.value
}
let list = deepCopy(showList.value)
emit('handleMoving', { list, x, y })
}
//
const handleDragEnd = (e: any) => {
setTimeout(() => {
scrollInfo.value = { x: 0, y: 0 }
if (disabled.value) return //
if (
moveToIndex.value !== -1 &&
activeIndex.value !== -1 &&
moveToIndex.value !== activeIndex.value
) {
//
if (props.swapMode) {
cloneList.value.splice(
moveToIndex.value,
0,
...cloneList.value.splice(activeIndex.value, 1),
)
} else {
let active = cloneList.value[activeIndex.value] //
let move = cloneList.value[moveToIndex.value] //
//
cloneList.value[activeIndex.value] = move
cloneList.value[moveToIndex.value] = active
}
}
initList(cloneList.value)
const endList = showList.value.map((item) => omit(item))
const list = deepCopy(cloneList.value)
emit('update:list', endList) // vue3
emit('getList', endList) //
emit('handleDragEnd', { list }) //
activeIndex.value = -1
oldIndex.value = -1
moveToIndex.value = -1
disabled.value = true
})
}
// --
const updataList = (list: ItemType[]) => {
//
if (props.isStopPropagation) return //
const newList = deepCopy(list)
for (let i = 0; i < newList.length; i++) {
showList.value[i] = { ...showList.value[i], ...newList[i] }
}
cloneList.value = deepCopy(showList.value)
}
// 0
const setLeoDrag = (size: number = 5) => {
if (size < 0) return
// #ifdef APP-NVUE
dom.getComponentRect(leoDragView.value, (res: any) => {
let data = res.size
getViewCallback(data, size)
})
// #endif
// #ifndef APP-NVUE
nextTick(() => {
const query = uni.createSelectorQuery().in(instance.proxy)
query
.select('.leo-drag')
.boundingClientRect((data) => {
getViewCallback(data, size)
})
.exec()
})
// #endif
}
//
const getViewCallback = (data: any, size: number) => {
if (!data || data.width < 50) {
setTimeout(() => {
size = size - 1
setLeoDrag(size)
}, 300)
return
}
width.value = data.width
nextTick(() => {
initList(showList.value, true) // dom
})
}
//
watch(
() => props.list,
(newList) => {
let list = deepCopy(newList) || []
// 使
if (list.length > showList.value.length) {
list.splice(0, showList.value.length)
list = [...showList.value, ...list]
initList(list, true)
} else {
//
nextTick(() => {
initList(list, true)
})
}
},
{ deep: true, immediate: true },
)
//
showList.value = deepCopy(props.list) || []
//
onMounted(() => {
setLeoDrag()
})
//
defineExpose({
initList,
updataList,
handleLongpress,
})
</script>
<style lang="scss" scoped>
.base-drag-wrapper {
opacity: 1;
z-index: 1;
color: #212121;
display: flex;
align-items: center;
transition: top 0.4s;
flex-wrap: wrap;
/* #ifndef APP-NVUE */
width: 100%;
/* #endif */
}
.slotContent {
display: flex;
/* #ifndef APP-NVUE */
width: 100%;
/* #endif */
:deep() {
// view--
& > view {
width: 100%;
flex: 1;
}
}
}
.placeholder {
position: absolute;
width: 100%;
height: 100%;
animation: animationShows 0.51s linear;
}
@keyframes animationShows {
0% {
opacity: 0;
}
20% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.leo-drag {
/* #ifndef APP-NVUE */
width: 100%;
/* #endif */
}
.loading {
color: pink;
}
</style>

View File

@ -0,0 +1,3 @@
export const deepCopy = <T>(source: T): T => {
return JSON.parse(JSON.stringify(source))
}

View File

@ -36,7 +36,7 @@
universityBaseInfo?.universityResult.level === 0 ? '本科' : '专科'
}}·{{ universityBaseInfo?.universityResult.nature }}
</text>
<text class="mt-[14rpx] text-[22rpx] font-normal text-[#303030]">
<text class="mt-[14rpx] text-[22rpx] font-normal text-[#303030] max-w-[400rpx] truncate">
{{ universityBaseInfo?.universityResult.features.slice(0, 4).join('/&nbsp;') }}
</text>
</view>

View File

@ -1,23 +0,0 @@
<template>
<view
class="bg-[#E2EDF9] text-[#1580FF] text-[22rpx] flex justify-between items-center px-[32rpx] py-[10rpx]"
>
<view class="flex gap-[16rpx]">
<text>{{ userInfo.estimatedAchievement.expectedScore }}</text>
<text>
{{ userInfo.estimatedAchievement.subjectGroup.split(',').join('/') }}
</text>
<text>{{ userInfo.batchName }}</text>
</view>
<view>
<text>重要提示</text>
<text class="i-carbon-help"></text>
</view>
</view>
</template>
<script lang="ts" setup>
defineProps<{
userInfo: any
}>()
</script>

View File

@ -1,120 +0,0 @@
<template>
<view class="h-[16rpx] bg-[#F5F5F5]"></view>
<view class="flex items-start p-[32rpx]">
<view class="flex flex-col items-center gap-[16rpx]">
<image :src="item.logo" mode="scaleToFill" class="w-[112rpx] h-[112rpx]" />
<view
class="w-[52rpx] h-[52rpx] rounded-[8rpx] font-semibold text-[28rpx] flex items-center justify-center"
:style="calcTypeName(item.type).style"
>
{{ calcTypeName(item.type).text }}
</view>
<text class="text-[32rpx] font-semibold">
{{
Math.round(
item.items.reduce((a, b) => a + Number(b.percentAge.replace('%', '')), 0) /
item.items.length,
)
}}
%
</text>
</view>
<view class="flex flex-col ml-[24rpx] justify-between flex-1">
<view class="flex justify-between mb-[14rpx]">
<view class="flex justify-between flex-col gap-[6rpx]">
<text class="text-[32rpx] font-semibold">{{ item.name }}</text>
<text class="text-[22rpx] text-[#505050]">
{{ item.city }}·{{ item.educationCategory }}
</text>
<view class="text-[22rpx] text-[#8F959E] flex items-center">
<text class="truncate max-w-[300rpx]" v-show="item.features.length > 0">
{{ item.features.slice(0, 3).join('/') }}/
</text>
<text>排名{{ item.rank }}</text>
</view>
<view class="text-[22rpx] text-[#1F2329] mt-[8rpx] flex gap-[10rpx]">
<text class="">代码{{ item.collegeCode }}</text>
<text>{{ item.year }}计划{{ item.items.reduce((a, b) => a + b.planCount, 0) }}</text>
</view>
</view>
<view class="flex flex-col gap-[8rpx] items-center">
<view
class="text-[24rpx] px-[16rpx] py-[12rpx] rounded-[8rpx] border-[2rpx] border-[#1580FF] border-solid"
@click.stop="handleShow"
>
专业{{ item.items.length }}
</view>
<text class="text-[20rpx] text-[#8F959E]" v-if="collegeMajorCount > 0">
已填 {{ collegeMajorCount }}
</text>
</view>
</view>
<DataTable :data="item.childItems" v-bind="$attrs" />
</view>
</view>
<ActionSheet v-model:show="show" :lazy-render="true">
<template #title>
<view class="flex items-center justify-between px-[32rpx] pt-[32rpx] pb-[24rpx]">
<view class="flex flex-col gap-[14rpx]">
<text class="text-[36rpx] text-[#303030] font-bold">{{ item.name }}</text>
<text class="text-[22rpx] text-[#505050]">
{{ item.city }}·{{ item.educationCategory }}
</text>
</view>
<view class="flex flex-col items-end gap-[40rpx]">
<view class="i-carbon-close-large w-[40rpx] h-[40rpx]" @click.stop="show = false"></view>
<view class="text-[22rpx]">
<text>已填</text>
<text class="text-[#1580FF]">{{ majorCount }}</text>
<text></text>
</view>
</view>
</view>
</template>
<CollegeMajor :item="item" v-bind="$attrs" />
<template #footer>
<view class="flex items-center justify-between gap-[30rpx]">
<view class="cancel-btn" @click.stop="show = false">取消</view>
<view class="submit-btn" @click.stop="handleConfirm">确认</view>
</view>
</template>
</ActionSheet>
</template>
<script lang="ts" setup>
import DataTable from './DataTable.vue'
import { useUserStore } from '@/store/user'
import ActionSheet from '@/pages-sub/components/ActionSheet.vue'
import CollegeMajor from './CollegeMajor.vue'
import { calcTypeName } from './useWisheList'
const userStore = useUserStore()
const props = defineProps({
item: Object,
majorCount: Number,
})
const show = ref(false)
const handleConfirm = () => {
show.value = false
}
const handleShow = () => {
show.value = true
}
const collegeMajorCount = computed(() => {
return (
userStore.userInfo.wishList.find((item) => item.unId === props.item.uId)?.vItems.length || 0
)
})
</script>
<style lang="scss" scoped>
@import '@/pages-sub/home/styles/picker-view-btn.scss';
</style>

View File

@ -1,6 +1,7 @@
<template>
<view
v-for="major in item.items"
v-for="major in college.items"
:key="major.planId"
class="pt-[32rpx] pl-[58rpx] pr-[32rpx] pb-[30rpx] custom-background flex gap-[58rpx]"
:style="`--background-color:${calcTypeName(major.type).style.backgroundColor}`"
>
@ -23,7 +24,7 @@
<view class="flex justify-between text-[22rpx] text-[#1F2329] mt-[14rpx]">
<view class="flex flex-col gap-[6rpx]">
<text>代码{{ major.majorCode }}</text>
<text>{{ item.year }}计划{{ major.planCount }}</text>
<text>{{ college.year }}计划{{ major.planCount }}</text>
</view>
<view class="flex flex-col gap-[6rpx]">
<text>选科:{{ major.subjectClam }}</text>
@ -45,51 +46,40 @@
</template>
<script lang="ts" setup>
import { calcTypeName } from './useWisheList'
import { calcTypeName } from '../composable/useWishesList'
import DataTable from './DataTable.vue'
import { useUserStore } from '@/store/user'
const userStore = useUserStore()
const props = defineProps<{
item: any
college: any
score?: number
}>()
const handleClick = (major: any) => {
let exitMajorUnList = userStore.userInfo.wishList.find((item) => item.unId === props.item.uId)
let exitMajor = exitMajorUnList?.vItems.find((item) => item._pId === major.planId)
let exitMajorUnList = userStore.userInfo.wishList.find((item) => item.uId === props.college.uId)
let exitMajor = exitMajorUnList?.vItems.find((item) => item.planId === major.planId)
if (exitMajor) {
userStore.deleteWishListMajor({ unId: props.item.uId, _pId: major.planId })
userStore.deleteWishListMajor({ uId: props.college.uId, planId: major.planId })
} else if (exitMajorUnList) {
userStore.setWishListMajor({
val: {
_pId: major.planId,
major: major.major,
majorCode: major.majorCode,
majorGroup: major.majorGroup,
percentAge: major.percentAge,
...major,
},
unId: props.item.uId,
uId: props.college.uId,
})
} else {
let _major = {
unId: props.item.uId,
unName: props.item.name,
unCode: props.item.collegeCode,
type: props.item.type,
...props.college,
vItems: [
{
_pId: major.planId,
major: major.major,
majorCode: major.majorCode,
majorGroup: major.majorGroup,
percentAge: major.percentAge,
...major,
sort: 1,
type: major.type,
},
],
}
delete _major.items
userStore.setWishListMajorWithUn(_major)
}
}
@ -97,9 +87,9 @@ const handleClick = (major: any) => {
const checkActive = (major: unknown) => {
const _major = major as { planId: string }
return userStore.userInfo.wishList.find((item) => {
if (item.unId !== props.item.uId) {
if (item.uId !== props.college.uId) {
return false
} else if (item.vItems.find((vItem) => vItem._pId === _major.planId)) {
} else if (item.vItems.find((vItem) => vItem.planId === _major.planId)) {
return true
}
})

View File

@ -4,7 +4,8 @@
<!-- 头部 -->
<view class="flex items-center gap-[16rpx]">
<view
v-for="headItem in columns"
v-for="(headItem, index) in columns"
:key="index"
class="text-[22rpx] text-[#505050]"
:style="{ width: headItem.width }"
>
@ -18,11 +19,16 @@
<!-- 表格内容 -->
<view>
<view v-for="(item, index) in recompileData" class="flex items-center gap-[16rpx]">
<view
v-for="col in columns"
v-for="(item, index) in recompileData"
class="flex items-center gap-[16rpx]"
:key="index"
>
<view
v-for="(col, colIndex) in columns"
class="text-[22rpx] text-[#505050]"
:style="{ width: col.width }"
:key="colIndex"
>
<view v-if="col.key === 'lineDiff'">
{{ item['lineDiff'] }}

View File

@ -0,0 +1,56 @@
<template>
<view
class="bg-[#E2EDF9] text-[#1580FF] text-[22rpx] flex justify-between items-center px-[32rpx] py-[10rpx]"
>
<view class="flex gap-[16rpx]">
<text>{{ userInfo.estimatedAchievement.expectedScore }}</text>
<text>
{{ userInfo.estimatedAchievement.subjectGroup.split(',').join('/') }}
</text>
<text>{{ userInfo.batchName }}</text>
</view>
<view v-if="type === 0" class="flex items-center">
<text>重要提示</text>
<text class="i-carbon-help"></text>
</view>
<view v-if="type === 1" class="flex items-center gap-[26rpx] text-[22rpx] text-[#000]">
<view v-for="(model, index) of tModel" :key="model.type" class="flex items-center gap-[6rpx]">
<view
class="w-[16rpx] h-[16rpx] rounded-full"
:style="{ backgroundColor: calcTypeName(model.type).roundedBgColor }"
></view>
<text class="text-[22rpx]">{{ calcTypeName(model.type)?.text }}({{ model.count }})</text>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { useUserStore } from '@/store/user'
import { countModel, calcTypeName } from '../composable/useWishesList'
const userStore = useUserStore()
defineProps({
userInfo: {
type: Object,
default: () => ({}),
},
type: {
type: Number,
default: 0,
},
})
const userWhishList = computed(() => userStore.userInfo.wishList)
const { tModel } = countModel(userWhishList.value)
</script>
<style lang="scss" scoped>
.t-model-base {
font-weight: 400;
font-size: 22rpx;
color: #000000;
}
</style>

View File

@ -0,0 +1,85 @@
<template>
<view class="h-[16rpx] bg-[#F5F5F5]"></view>
<view class="flex items-start p-[32rpx]">
<view class="flex flex-col items-center gap-[16rpx]">
<image :src="college.logo" mode="scaleToFill" class="w-[112rpx] h-[112rpx]" />
<view
class="w-[52rpx] h-[52rpx] rounded-[8rpx] font-semibold text-[28rpx] flex items-center justify-center"
:style="calcTypeName(college.type).style"
>
{{ calcTypeName(college.type).text }}
</view>
<text class="text-[32rpx] font-semibold">
{{
Math.round(
college.items.reduce((a, b) => a + Number(b.percentAge.replace('%', '')), 0) /
college.items.length,
)
}}
%
</text>
</view>
<view class="flex flex-col ml-[24rpx] justify-between flex-1">
<view class="flex justify-between mb-[14rpx]">
<view class="flex justify-between flex-col gap-[6rpx]">
<text class="text-[32rpx] font-semibold">{{ college.name }}</text>
<text class="text-[22rpx] text-[#505050]">
{{ college.city }}·{{ college.educationCategory }}
</text>
<view class="text-[22rpx] text-[#8F959E] flex items-center">
<text class="truncate max-w-[300rpx]" v-show="college.features.length > 0">
{{ college.features.slice(0, 3).join('/') }}/
</text>
<text>排名{{ college.rank }}</text>
</view>
<view class="text-[22rpx] text-[#1F2329] mt-[8rpx] flex gap-[10rpx]">
<text class="">代码{{ college.collegeCode }}</text>
<text>
{{ college.year }}计划{{ college.items.reduce((a, b) => a + b.planCount, 0) }}
</text>
</view>
</view>
<view class="flex flex-col gap-[8rpx] items-center">
<view
class="text-[24rpx] px-[16rpx] py-[12rpx] rounded-[8rpx] border-[2rpx] border-[#1580FF] border-solid"
@click.stop="handleShow"
>
专业{{ college.items.length }}
</view>
<text class="text-[20rpx] text-[#8F959E]" v-if="collegeMajorCount > 0">
已填 {{ collegeMajorCount }}
</text>
</view>
</view>
<DataTable :data="college.childItems" v-bind="$attrs" />
</view>
</view>
</template>
<script lang="ts" setup>
import DataTable from './DataTable.vue'
import { useUserStore } from '@/store/user'
import { calcTypeName } from '../composable/useWishesList'
const userStore = useUserStore()
const props = defineProps({
college: Object,
})
const emit = defineEmits(['showAction'])
const handleShow = () => {
emit('showAction', props.college)
}
const collegeMajorCount = computed(() => {
return (
userStore.userInfo.wishList.find((item) => item.uId === props.college.uId)?.vItems.length || 0
)
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,62 @@
<template>
<ActionSheet v-model:show="innerShowFlag" :lazy-render="true">
<template #title>
<view class="flex items-center justify-between px-[32rpx] pt-[32rpx] pb-[24rpx]">
<view class="flex flex-col gap-[14rpx]">
<text class="text-[36rpx] text-[#303030] font-bold">{{ college?.name }}</text>
<text class="text-[22rpx] text-[#505050]">
{{ college?.city }}·{{ college?.educationCategory }}
</text>
</view>
<view class="flex flex-col items-end gap-[40rpx]">
<view class="i-carbon-close-large w-[40rpx] h-[40rpx]" @click.stop="handleConfirm"></view>
<view class="text-[22rpx]">
<text>已填</text>
<text class="text-[#1580FF]">{{ majorCount }}</text>
<text></text>
</view>
</view>
</view>
</template>
<CollegeMajor :college="college" v-bind="$attrs" v-if="college" />
<template #footer>
<view class="flex items-center justify-between gap-[30rpx]">
<view class="cancel-btn" @click="handleConfirm"></view>
<view class="submit-btn" @click="handleConfirm"></view>
</view>
</template>
</ActionSheet>
</template>
<script setup lang="ts">
import ActionSheet from '@/pages-sub/components/ActionSheet.vue'
import CollegeMajor from './CollegeMajor.vue'
const props = defineProps({
college: Object,
majorCount: Number,
show: Boolean,
})
const emit = defineEmits<{
(e: 'update:show', value: boolean): void
}>()
const innerShowFlag = computed({
get: () => {
return props.show
},
set: (value) => {
emit('update:show', value)
},
})
const handleConfirm = () => {
emit('update:show', false)
}
</script>
<style lang="scss" scoped>
@import '@/pages-sub/home/styles/picker-view-btn.scss';
</style>

View File

@ -0,0 +1,107 @@
<template>
<view class="h-[16rpx] bg-[#f8f8f8]"></view>
<Collapse
:default-collapsed="false"
class="flex flex-col items-center gap-[16rpx] custom-background px-[32rpx]"
:style="`--background-color:${calcTypeName(college.type).style.backgroundColor}`"
>
<template #header>
<view class="flex py-[32rpx] gap-[30rpx]">
<view class="flex flex-col items-center gap-[16rpx]">
<view class="flex items-center gap-[8rpx] text-[#303030] text-[28rpx]">
{{ collegeIndex }}
<view class="i-carbon-chevron-down text-[16rpx]"></view>
</view>
<view
class="w-[52rpx] h-[52rpx] rounded-[8rpx] font-semibold text-[28rpx] flex items-center justify-center"
:style="calcTypeName(college.type).style"
>
{{ calcTypeName(college.type).text }}
</view>
<text class="text-[32rpx] font-semibold">
{{
Math.round(
college.vItems.reduce((a, b) => a + Number(b.percentAge.replace('%', '')), 0) /
college.vItems.length,
)
}}%
</text>
</view>
<view class="flex flex-col justify-between flex-1">
<view class="flex justify-between mb-[14rpx]">
<view class="flex justify-between flex-col gap-[6rpx]">
<text class="text-[32rpx] font-semibold">{{ college.name }}</text>
<text class="text-[22rpx] text-[#505050]">
{{ college.city }}·{{ college.educationCategory }}
</text>
<view class="text-[22rpx] text-[#8F959E] flex items-center">
<text class="truncate max-w-[300rpx]" v-show="college.features.length > 0">
{{ college.features.slice(0, 3).join('/') }}/
</text>
<text>排名{{ college.rank }}</text>
</view>
<view class="text-[22rpx] text-[#1F2329] mt-[8rpx] flex gap-[10rpx]">
<text class="">代码{{ college.collegeCode }}</text>
<text>
{{ college.year }}计划{{ college.vItems.reduce((a, b) => a + b.planCount, 0) }}
</text>
</view>
</view>
</view>
<DataTable :data="college.childItems" v-bind="$attrs" />
</view>
</view>
</template>
<view class="h-[2rpx] bg-[#EDEDED] w-full"></view>
<view
v-for="major in college.vItems"
:key="major.planId"
class="pt-[32rpx] pb-[30rpx] flex gap-[30rpx] not-last:border-b border-[#EDEDED]"
>
<view class="flex flex-col gap-[16rpx]">
<text class="text-[32rpx] font-semibold text-[#000]">{{ major.percentAge || '0%' }}</text>
</view>
<view class="flex flex-col gap-[16rpx]">
<view class="flex justify-between flex-auto">
<view class="flex flex-col">
<text class="text-[32rpx] text-[#000] font-semibold truncate max-w-[400rpx]">
{{ major.major.replace(/(\r\n|\n|\r)/g, '') }}
</text>
<text class="text-[22rpx] text-[#1F2329] mt-[14rpx]">{{ major.remark }}</text>
<view class="flex justify-between text-[22rpx] text-[#1F2329] mt-[14rpx]">
<view class="flex flex-col gap-[6rpx]">
<text>代码{{ major.majorCode }}</text>
<text>{{ college.year }}计划{{ major.planCount }}</text>
</view>
<view class="flex flex-col gap-[6rpx]">
<text>选科:{{ major.subjectClam }}</text>
<text>学费/学制:{{ major.fee }}/{{ major.academic }}</text>
</view>
</view>
</view>
</view>
<DataTable :data="major.items" :score="score" />
</view>
</view>
</Collapse>
</template>
<script setup lang="ts">
import { calcTypeName } from '../composable/useWishesList'
import DataTable from './DataTable.vue'
import Collapse from '@/pages-sub/components/collapse/Collapse.vue'
defineProps<{
college: any
score: number
collegeIndex: number
}>()
</script>
<style lang="scss" scoped>
.custom-background {
background: linear-gradient(180deg, var(--background-color) 0%, #fff 30%, #fff 100%);
}
</style>

View File

@ -0,0 +1,86 @@
import { getBatchBase } from '@/service/index/api'
export const calcTypeName = (type: number) => {
const style = {
2: {
text: '冲',
color: '#EB5241',
bgColor: 'rgba(235,82,65,0.15)',
roundedBgColor: '#E75859',
},
1: {
text: '稳',
color: '#FA8E23',
bgColor: 'rgba(250,142,35,0.15)',
roundedBgColor: '#FF8800',
},
0: {
text: '保',
color: '#15C496',
bgColor: 'rgba(21,196,150,0.15)',
roundedBgColor: '#34C724',
},
}[type] || { text: '保', color: '#15C496', bgColor: 'rgba(21,196,150,0.15)' }
return {
text: style.text,
style: {
color: style.color,
backgroundColor: style.bgColor,
},
roundedBgColor: style.roundedBgColor,
}
}
export const coverTypeModel = (tModel: Record<keyof typeof TYPE_LABELS, number>, total: number) => {
const TYPE_LABELS = {
c: '冲',
w: '稳',
b: '保',
} as const
let _result = Object.entries(TYPE_LABELS).map(([key, label]) => ({
name: `${label}(${tModel[key]})`,
value: key === 'b' ? '0' : key === 'w' ? '1' : '2',
}))
_result.unshift({
name: `全部${total}`,
value: '-1',
})
return _result
}
export const countModel = (list: any[]) => {
const tModel = ref([
{ type: 2, count: 0 },
{ type: 1, count: 0 },
{ type: 0, count: 0 },
])
list.forEach((item) => {
item.vItems.forEach((vItem) => {
const target = tModel.value.find((t) => t.type === vItem.type)
if (target) target.count++
})
})
return { tModel }
}
export const useScore = (provinceCode, batchName) => {
const score = ref(0)
const minScore = ref(0)
const maxScore = ref(0)
getBatchBase({ locationCode: provinceCode }).then((resp) => {
if (resp.code === 200) {
const _result = resp.result as { batches: any[]; maxScore: number; minScore: number }
if (_result.batches.length > 0) {
const _score = _result.batches.find((item) => item.batch === batchName)?.score || 0
score.value = _score
}
minScore.value = _result.minScore
maxScore.value = _result.maxScore
}
})
return { score, minScore, maxScore }
}

View File

@ -69,7 +69,12 @@
v-for="(item, index) in schoolList"
@click="itemClick(item, index)"
>
<ScrollListItem :item="item" :score="score" :major-count="majorCount" />
<ScrollListItem
:college="item"
:score="score"
:major-count="majorCount"
@show-action="handleShowAction"
/>
</view>
<template #bottom>
@ -109,6 +114,12 @@
</view>
</template>
</ActionSheet>
<ScrollListItemAction
v-model:show="collegeShow"
:college="showCollegeItem"
:major-count="majorCount"
/>
</template>
<script lang="ts" setup>
@ -118,15 +129,16 @@ import Region from '@/pages-sub/home/components/Region.vue'
import FilterMenu from '@/pages-sub/home/components/FilterMenu.vue'
import Slider from '@/pages-sub/components/Slider.vue'
import CustomPickerView from '@/pages-sub/components/CustomPickerView.vue'
import { getBatchBase } from '@/service/index/api'
import ScrollListItem from './ScrollListItem.vue'
import HeaderTip from './HeaderTip.vue'
import ScrollListItemAction from './components/ScrollListItemAction.vue'
import ScrollListItem from './components/ScrollListItem.vue'
import HeaderTip from './components/HeaderTip.vue'
import { getPlanProListByFilter } from '@/service/index/api'
import { useUserStore } from '@/store/user'
import { coverTypeModel } from './useWisheList'
import { coverTypeModel, useScore } from './composable/useWishesList'
const userStore = useUserStore()
@ -136,6 +148,13 @@ const filterMenuRef = ref(null)
const location = ref(userStore.userInfo.estimatedAchievement.provinceCode)
const showCollegeItem = ref(null)
const collegeShow = ref(false)
const handleShowAction = (item) => {
showCollegeItem.value = item
collegeShow.value = true
}
const total = ref(0)
const typeModelList = ref([])
const handleTypeModelChange = ({ item }) => {
@ -233,28 +252,15 @@ const handleSliderChange = (val) => {
paging.value.reload()
}
const score = ref(0)
const minScore = ref(0)
const maxScore = ref(0)
onLoad(() => {
getBatchBase({ locationCode: userStore.userInfo.estimatedAchievement.provinceCode }).then(
(resp) => {
if (resp.code === 200) {
const _result = resp.result as { batches: any[]; maxScore: number; minScore: number }
if (_result.batches.length > 0) {
const _score =
_result.batches.find((item) => item.batch === userStore.userInfo.batchName)?.score || 0
score.value = _score
}
minScore.value = _result.minScore
maxScore.value = _result.maxScore
}
},
)
})
const { score, minScore, maxScore } = useScore(
userStore.userInfo.estimatedAchievement.provinceCode,
userStore.userInfo.batchName,
)
const handlePreview = () => {
if (majorCount.value === 0) {
return
}
uni.navigateTo({ url: '/pages-sub/home/wishesList/wishesList' })
}
</script>

View File

@ -1,44 +0,0 @@
export const calcTypeName = (type: number) => {
const style = {
2: {
text: '冲',
color: '#EB5241',
bgColor: 'rgba(235,82,65,0.15)',
},
1: {
text: '稳',
color: '#FA8E23',
bgColor: 'rgba(250,142,35,0.15)',
},
0: {
text: '保',
color: '#15C496',
bgColor: 'rgba(21,196,150,0.15)',
},
}[type] || { text: '保', color: '#15C496', bgColor: 'rgba(21,196,150,0.15)' }
return {
text: style.text,
style: {
color: style.color,
backgroundColor: style.bgColor,
},
}
}
export const coverTypeModel = (tModel: Record<keyof typeof TYPE_LABELS, number>, total: number) => {
const TYPE_LABELS = {
c: '冲',
w: '稳',
b: '保',
} as const
let _result = Object.entries(TYPE_LABELS).map(([key, label]) => ({
name: `${label}(${tModel[key]})`,
value: key === 'b' ? '0' : key === 'w' ? '1' : '2',
}))
_result.unshift({
name: `全部${total}`,
value: '-1',
})
return _result
}

View File

@ -1,11 +1,58 @@
<route lang="json5" type="page">
{
style: {
navigationBarTitleText: '当前志愿表',
},
}
</route>
<template>
<view class="">
<HeaderTip :user-info="userStore.userInfo" />
<view class="h-screen flex flex-col">
<HeaderTip :user-info="userStore.userInfo" :type="1" />
<DragSortList v-model:list="wishList" class="flex-auto pb-safe overflow-auto" ref="myDrop">
<template #content="{ data }">
<view
class="drop"
:style="[{ transform: data.index === data.activeIndex ? 'scale(1.05)' : 'scale(1)' }]"
>
<SortCollege :college="data" :score="score" :college-index="data.index + 1" />
</view>
</template>
</DragSortList>
</view>
</template>
<script setup lang="ts">
import { useUserStore } from '@/store/user'
import HeaderTip from './components/HeaderTip.vue'
import DragSortList from '@/pages-sub/components/dragSort/DragSort.vue'
import SortCollege from './components/SortCollege.vue'
import { useUserStore } from '@/store/user'
import { useScore } from './composable/useWishesList'
const userStore = useUserStore()
const { score } = useScore(
userStore.userInfo.estimatedAchievement.provinceCode,
userStore.userInfo.batchName,
)
const wishList = ref(userStore.userInfo.wishList)
const myDrop = ref()
const getDropList = (list) => {
//
console.log(list, 'eeee')
}
const touchstart = (i) => {
//
console.log(myDrop.value)
myDrop.value.handleLongpress(i)
}
const changeList = () => {
//
let list = wishList
myDrop.value.initList(list, true)
}
</script>

View File

@ -239,18 +239,6 @@
"navigationStyle": "custom"
}
},
{
"path": "home/wishesList/CollegeMajor",
"type": "page"
},
{
"path": "home/wishesList/DataTable",
"type": "page"
},
{
"path": "home/wishesList/HeaderTip",
"type": "page"
},
{
"path": "home/wishesList/index",
"type": "page",
@ -258,13 +246,12 @@
"navigationBarTitleText": "我的志愿表"
}
},
{
"path": "home/wishesList/ScrollListItem",
"type": "page"
},
{
"path": "home/wishesList/wishesList",
"type": "page"
"type": "page",
"style": {
"navigationBarTitleText": "当前志愿表"
}
}
]
},

View File

@ -118,9 +118,9 @@ export const useUserStore = defineStore(
userInfo.value.batchName = val
}
const setWishListMajor = ({ val, unId }: { val: any; unId: string }) => {
const setWishListMajor = ({ val, uId }: { val: any; uId: string }) => {
try {
const targetItem = userInfo.value.wishList.find((item) => item.unId === unId)
const targetItem = userInfo.value.wishList.find((item) => item.uId === uId)
if (!targetItem) {
console.error('未找到对应的志愿清单项')
return
@ -129,7 +129,7 @@ export const useUserStore = defineStore(
userInfo.value = {
...userInfo.value,
wishList: userInfo.value.wishList.map((item) => {
if (item.unId === unId) {
if (item.uId === uId) {
return {
...item,
vItems: [...item.vItems, { ...val, sort: item.vItems.length + 1 }],
@ -143,10 +143,10 @@ export const useUserStore = defineStore(
}
}
const deleteWishListMajor = ({ unId, _pId }: { unId: string; _pId: string }) => {
const deleteWishListMajor = ({ uId, planId }: { uId: string; planId: string }) => {
userInfo.value.wishList = userInfo.value.wishList.map((item) => {
if (item.unId === unId) {
item.vItems = item.vItems.filter((vItem) => vItem._pId !== _pId)
if (item.uId === uId) {
item.vItems = item.vItems.filter((vItem) => vItem.planId !== planId)
return item
}
return item

View File

@ -27,11 +27,7 @@ interface NavigateToOptions {
"/pages-sub/home/news/index" |
"/pages-sub/home/news/newsList" |
"/pages-sub/home/schoolRank/index" |
"/pages-sub/home/wishesList/CollegeMajor" |
"/pages-sub/home/wishesList/DataTable" |
"/pages-sub/home/wishesList/HeaderTip" |
"/pages-sub/home/wishesList/index" |
"/pages-sub/home/wishesList/ScrollListItem" |
"/pages-sub/home/wishesList/wishesList" |
"/login-sub/index" |
"/pages-evaluation-sub/index" |