voluntary-cart-popup.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. <template>
  2. <uv-popup ref="popup" mode="bottom" round="16" closeable>
  3. <view class="h-[50px] px-40 fx-row fx-bet-cen text-main text-lg font-bold">
  4. <template v-if="!id">
  5. 志愿表预览
  6. </template>
  7. <view v-else class="fx-row flex-1 pr-30">
  8. <text v-if="!nameEditing">{{ name }}</text>
  9. <uv-input v-else v-model="name" placeholder="志愿表名称"/>
  10. <uv-icon name="edit-pen" size="18" class="ml-10" @click="nameEditing=!nameEditing"/>
  11. </view>
  12. <uv-tags icon="list-dot" text="快速排序" plain class="mr-50" @click="openSortList"/>
  13. </view>
  14. <scroll-view :scroll-y="!anyDragging" class="bg-bg" style="height: 50vh" lower-threshold="100"
  15. @scrolltolower="handleGroupScroll">
  16. <uv-sticky v-if="firedSorts.length" :offsetTop="-44">
  17. <view class="px-20 pb-20 fx-row bg-white">
  18. <uv-tags v-for="s in firedSorts" :key="s.name" :text="s.short" :icon="s.icon" size="mini"
  19. type="success" closable plain-fill @close="handleSortRemove(s)"/>
  20. </view>
  21. </uv-sticky>
  22. <view class="p-20 fx-col gap-20">
  23. <view v-for="(college,index) in pagedSelectedList" class="fx-row fx-bet-sta bg-white mx-card">
  24. <view class="ml-10 mb-10 text-sm">
  25. <text class="fx-row rounded-b-full px-15 pt-5 pb-10 text-white bg-primary">
  26. {{ generateSeq(index) }}
  27. </text>
  28. </view>
  29. <view class="flex-1 p-20 fx-col">
  30. <view class="font-bold">
  31. {{ college.university.name }}
  32. <!-- TODO:志愿表还未保存group信息,所以这里显示会在编辑时表现不一致 -->
  33. <template v-if="false">({{ college.recruitPlan.group }})</template>
  34. ({{ college.recruitPlan.collegeCode }})
  35. </view>
  36. <view class="fx-row fx-bet-cen mt-10 text-content text-2xs gap-20">
  37. <view>录取概率:
  38. <view class="font-bold text-main">
  39. {{ college.enrollRatio || '-' }}%
  40. </view>
  41. </view>
  42. <view>最低位次:
  43. <view class="font-bold text-main">
  44. {{ college.history && college.history.seat || '-' }}
  45. </view>
  46. </view>
  47. <view>院校排名:
  48. <view class="font-bold text-main">
  49. {{ college.university.ranking || '-' }}
  50. </view>
  51. </view>
  52. </view>
  53. <view class="fx-row gap-20 my-20">
  54. <uv-button :disabled="selectedList.length<2" type="primary" size="mini" plain
  55. shape="circle" icon="arrow-down-fill" :text="generateSeq(index)"
  56. icon-color="primary" @click="openSeqSelect(index)"/>
  57. <uv-button :disabled="index==0" type="primary" size="mini" plain shape="circle"
  58. icon="arrow-up" icon-color="primary" @click="handleMoveUp(college)"/>
  59. <uv-button :disabled="index==selectedList.length-1" type="primary" size="mini" plain
  60. shape="circle" icon="arrow-down" icon-color="primary"
  61. @click="handleMoveDown(college)"/>
  62. <uv-button type="primary" size="mini" plain shape="circle" icon="trash"
  63. icon-color="primary" @click="handleRemoveAll(college)"/>
  64. </view>
  65. <m-drag ref="drag" :list="getSelectedSortedMajors(college)" :item-height="44"
  66. @change="handleDragComplete">
  67. <template #default="{item:major,index:majorIndex}">
  68. <view class="fx-row items-center text-xs text-content h-[44px] box-border mx-border-b">
  69. <view class="flex-1 truncate">
  70. <text v-if="majorIndex>-1" class="mr-20">{{ majorIndex + 1 }}</text>
  71. <text :class="{'highlight-major': isFormedMajorFired(major)}">
  72. {{ major.marjorName }}[{{ major.marjorBelongs }}]
  73. </text>
  74. </view>
  75. <!-- 因为手机上区域比较小,只保留了拖拽排序 -->
  76. <uv-icon v-if="false" name="arrow-up" size="20px" class="mr10"
  77. @click="handleMajorUp(major, college)"></uv-icon>
  78. <uv-icon v-if="false" name="arrow-down" size="20px" class="mr10"
  79. @click="handleMajorDown(major, college)"></uv-icon>
  80. <uv-icon name="trash" size="20px" class="mr10"
  81. @click="handleMajorDelete(major, college)"></uv-icon>
  82. </view>
  83. </template>
  84. </m-drag>
  85. </view>
  86. </view>
  87. </view>
  88. </scroll-view>
  89. <view class="h-[50px] px-40 fx-row items-center mx-border-t">
  90. <uv-button type="primary" text="保存志愿表" shape="circle" :loading="locking"
  91. @click="handleSave"></uv-button>
  92. </view>
  93. <uv-action-sheet ref="actionSheet" :actions="sortList" :cancel-text="cancelSortText" @select="handleSort"
  94. @cancel="handleSortReset"/>
  95. <!-- We don't use `u-action-sheet` because there maybe a great number of seq options -->
  96. <uv-picker ref="picker" title="指定志愿顺序" :columns="[sequenceOptions]" :default-index="[seqIndex]"
  97. @confirm="handleSeq"/>
  98. </uv-popup>
  99. </template>
  100. <script setup>
  101. import {ref, computed, watch} from 'vue';
  102. import {useInjectVoluntaryCart} from "@/pages/voluntary/hooks/useVoluntaryCartInjection";
  103. import {toValue} from "@vueuse/core";
  104. import {useVoluntarySortService} from "@/pages/voluntary/hooks/useVoluntarySortService";
  105. import {useInjectVoluntaryMajorHighlight} from "@/pages/voluntary/hooks/useVoluntaryMajorHighlightInjection";
  106. import {confirmAsync} from "@/utils/uni-helper";
  107. import {useInjectVoluntaryAssistant} from "@/pages/voluntary/hooks/useVoluntaryAssistantInjection";
  108. import {useInjectVoluntaryHeader} from "@/pages/voluntary/hooks/useVoluntaryHeaderInjection";
  109. const popup = ref(null)
  110. const drag = ref(null)
  111. const picker = ref(null)
  112. const actionSheet = ref(null)
  113. const seqIndex = ref(0)
  114. const nameEditing = ref(false) // 暂时不允许修改名称
  115. const {id, name, locking, selectedList, defaultSort, recalculatePureSelectedList} = useInjectVoluntaryCart()
  116. const {isFormedMajorFired, snapshotSearchingMajorWhenApply} = useInjectVoluntaryMajorHighlight()
  117. const {
  118. firedSorts, sortList, cancelSortText, generateSeq, sequenceOptions,
  119. handleSort, handleSortReset, handleSortRemove,
  120. getSelectedSortedMajors, sortInterrupt,
  121. handleMoveDown, handleMoveUp
  122. } = useVoluntarySortService(popup, defaultSort)
  123. const {isMock} = useInjectVoluntaryHeader()
  124. const {save} = useInjectVoluntaryAssistant()
  125. const anyDragging = computed(() => drag.value?.some(d => d.dragging))
  126. // optimization for faster rendering
  127. const localPageNum = ref(1)
  128. const localPageSize = ref(4)
  129. const pagedSelectedList = computed(() => {
  130. return selectedList.value.slice(0, toValue(localPageNum) * toValue(localPageSize))
  131. })
  132. const handleGroupScroll = () => {
  133. if (toValue(pagedSelectedList).length >= toValue(selectedList).length) return
  134. localPageNum.value += 1
  135. }
  136. const openSortList = () => {
  137. actionSheet.value.open()
  138. }
  139. const handleDragComplete = (newList, oldList) => {
  140. // make localPriority exchange when drag complete.
  141. const copyPriorities = oldList.map(i => i.localPriority)
  142. copyPriorities.forEach((p, i) => newList[i].localPriority = p)
  143. }
  144. const handleRemoveAll = async (college) => {
  145. await confirmAsync('确认删除该专业组下的全部专业?')
  146. college.majors.forEach(m => {
  147. m.selected = false
  148. snapshotSearchingMajorWhenApply(m)
  149. })
  150. recalculatePureSelectedList()
  151. sortInterrupt()
  152. }
  153. // sort for major item
  154. const handleMajorUp = (major, majorGroup) => {
  155. // TODO: HM-DragSorts 已经没有再使用了,所以不存在深拷贝的问题,但这个方法目前没有使用,先不优化
  156. const source = getSelectedSortedMajors(majorGroup)
  157. // HM-DragSorts clone element will cause index error
  158. const index = source.findIndex(s => s.marjorBelongs == major.marjorBelongs)
  159. const targetIndex = index - 1
  160. // exchange two majors' localPriority
  161. if (targetIndex >= 0) {
  162. major = source[index] // this is the original major
  163. const targetMajor = source[targetIndex]
  164. const temp = major.localPriority
  165. major.localPriority = targetMajor.localPriority
  166. targetMajor.localPriority = temp
  167. }
  168. }
  169. const handleMajorDown = (major, majorGroup) => {
  170. // TODO: HM-DragSorts 已经没有再使用了,所以不存在深拷贝的问题,但这个方法目前没有使用,先不优化
  171. const source = getSelectedSortedMajors(majorGroup)
  172. // HM-DragSorts clone element will cause index error
  173. const index = source.findIndex(s => s.marjorBelongs == major.marjorBelongs)
  174. const targetIndex = index + 1
  175. // exchange two majors' localPriority
  176. if (targetIndex < source.length) {
  177. major = source[index] // this is the original major
  178. const targetMajor = source[targetIndex]
  179. const temp = major.localPriority
  180. major.localPriority = targetMajor.localPriority
  181. targetMajor.localPriority = temp
  182. }
  183. }
  184. const handleMajorDelete = (major) => {
  185. major.selected = false
  186. snapshotSearchingMajorWhenApply(major)
  187. if (recalculatePureSelectedList()) sortInterrupt()
  188. }
  189. const openSeqSelect = (groupIndex) => {
  190. seqIndex.value = groupIndex
  191. picker.value.open()
  192. }
  193. const handleSeq = ({indexs}) => {
  194. const oldIndex = seqIndex.value
  195. const newIndex = indexs[0]
  196. if (oldIndex != newIndex) {
  197. const seqGroup = selectedList.value[oldIndex]
  198. selectedList.value.splice(oldIndex, 1)
  199. selectedList.value.splice(newIndex, 0, seqGroup)
  200. sortInterrupt()
  201. }
  202. }
  203. const handleSave = async () => {
  204. await save(isMock.value)
  205. }
  206. const open = () => {
  207. localPageNum.value = 1
  208. popup.value.open()
  209. }
  210. const close = () => {
  211. popup.value.close()
  212. }
  213. defineExpose({open, close})
  214. // export default {
  215. // props: {
  216. // id: {
  217. // type: String | Number,
  218. // default: 0
  219. // },
  220. // name: {
  221. // type: String,
  222. // default: ''
  223. // },
  224. // show: {
  225. // type: Boolean,
  226. // default: false
  227. // },
  228. // selectedList: {
  229. // type: Array,
  230. // default: () => []
  231. // },
  232. // defaultSort: {
  233. // type: Array,
  234. // default: () => []
  235. // },
  236. // locking: {
  237. // type: Boolean,
  238. // default: false
  239. // }
  240. // },
  241. // data() {
  242. // return {
  243. // nameEditing: false,
  244. // confirmGroup: null,
  245. // showConfirm: false,
  246. // showSort: false,
  247. // showSeq: false,
  248. // seqIndex: 0,
  249. // }
  250. // },
  251. // watch: {
  252. // show: function (val) {
  253. // // u-popup will release dom elements when not showing
  254. // // so reset the current page number here
  255. // if (val) this.localPageNum = 1
  256. // }
  257. // },
  258. // methods: {
  259. // getSelectedSortedMajors(group) {
  260. // return group.majors.filter(m => m.selected).sort(MxConst.recommendMajorSortFn)
  261. // },
  262. // getGroupHeight(group) {
  263. // const majors = this.getSelectedSortedMajors(group)
  264. // return majors.length * 45
  265. // },
  266. // openSeqSelect(groupIndex) {
  267. // this.seqIndex = groupIndex
  268. // this.showSeq = true
  269. // },
  270. // handleSeq({indexs}) {
  271. // const oldIndex = this.seqIndex
  272. // const newIndex = indexs[0]
  273. // if (oldIndex != newIndex) {
  274. // const seqGroup = this.selectedList[oldIndex]
  275. // this.selectedList.splice(oldIndex, 1)
  276. // this.selectedList.splice(newIndex, 0, seqGroup)
  277. // this.sortInterrupt()
  278. // }
  279. // this.showSeq = false
  280. // },
  281. // close() {
  282. // this.$emit('closeVolunteer')
  283. // },
  284. // open() {
  285. //
  286. // },
  287. // save() {
  288. // this.$emit('save')
  289. // },
  290. // handleRemoveAll() {
  291. // this.confirmGroup.majors.forEach(m => {
  292. // m.selected = false
  293. // this.snapshotSearchingMajorWhenApply(m)
  294. // })
  295. // this.$emit('change')
  296. // this.showConfirm = false
  297. // },
  298. // // sort for major item
  299. // handleMajorUp(major, majorGroup) {
  300. // const source = this.getSelectedSortedMajors(majorGroup)
  301. // // HM-DragSorts clone element will cause index error
  302. // const index = source.findIndex(s => s.marjorBelongs == major.marjorBelongs)
  303. // const targetIndex = index - 1
  304. // // exchange two majors' localPriority
  305. // if (targetIndex >= 0) {
  306. // major = source[index] // this is the original major
  307. // const targetMajor = source[targetIndex]
  308. // const temp = major.localPriority
  309. // major.localPriority = targetMajor.localPriority
  310. // targetMajor.localPriority = temp
  311. // }
  312. // },
  313. // handleMajorDown(major, majorGroup) {
  314. // const source = this.getSelectedSortedMajors(majorGroup)
  315. // // HM-DragSorts clone element will cause index error
  316. // const index = source.findIndex(s => s.marjorBelongs == major.marjorBelongs)
  317. // const targetIndex = index + 1
  318. // // exchange two majors' localPriority
  319. // if (targetIndex < source.length) {
  320. // major = source[index] // this is the original major
  321. // const targetMajor = source[targetIndex]
  322. // const temp = major.localPriority
  323. // major.localPriority = targetMajor.localPriority
  324. // targetMajor.localPriority = temp
  325. // }
  326. // },
  327. // deleteMajor(major, majorGroup) {
  328. // const source = this.getSelectedSortedMajors(majorGroup)
  329. // // HM-DragSorts clone element will cause index error
  330. // const index = source.findIndex(s => s.marjorBelongs == major.marjorBelongs)
  331. // major = source[index] // this is the original major
  332. // major.selected = false
  333. // this.snapshotSearchingMajorWhenApply(major)
  334. // this.$emit('change')
  335. // },
  336. // handleMajorDrag(group, event) {
  337. // const source = this.getSelectedSortedMajors(group)
  338. // const oldIndex = event.index * 1
  339. // const newIndex = event.moveTo * 1
  340. // if (oldIndex == newIndex) return
  341. // // get localPriority one by one
  342. // const oldSeq = source.map(m => m.localPriority)
  343. // const oldMajor = source[oldIndex]
  344. // const newMajor = source[newIndex]
  345. // const newSource = [...source]
  346. // newSource.splice(oldIndex, 1)
  347. // newSource.splice(newIndex, 0, oldMajor)
  348. // // assign oldSeq to newSource one by one
  349. // newSource.forEach((m, idx) => {
  350. // m.localPriority = oldSeq[idx]
  351. // })
  352. // }
  353. // }
  354. // }
  355. </script>
  356. <style scoped lang="scss">
  357. .uv-button-wrapper {
  358. flex: 1 !important;
  359. }
  360. </style>