major-picker.vue 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. <template>
  2. <uv-popup ref="popup" mode="bottom">
  3. <scroll-view scroll-y class="bg-bg" style="height: 60vh">
  4. <uv-sticky custom-nav-height="0">
  5. <view class="mx-border-b fx-row fx-bet-cen bg-white h-[44px]">
  6. <view class="text-tips px-30" @click="close">取消</view>
  7. <view class="text-center flex-1 text-main truncate">
  8. {{ detail.baseInfo && detail.baseInfo.name }}
  9. </view>
  10. <view class="text-primary px-30" @click="handleConfirm">确定</view>
  11. </view>
  12. <view v-if="false&&localMajors.length&&limit>1" class="p-20 fx-row flex-wrap bg-bg">
  13. <view v-for="m in localMajors" :key="m.code">
  14. <uv-tags size="mini" type="primary" :text="m.name" plain plain-fill closable
  15. class="keep-all" @close="handleDelete(m)"></uv-tags>
  16. </view>
  17. </view>
  18. </uv-sticky>
  19. <view v-if="!loading" class="px-20 py-8 text-2xs text-tips fx-row fx-bet-cen">
  20. <view>
  21. <text class="text-error mx-5">{{ filteredProfessions.length }}</text>
  22. 个专业
  23. </view>
  24. <view v-if="categoryNode">
  25. 仅显示
  26. <text class="text-error mx-5">{{ categoryNode.name }}</text>
  27. 包含的专业
  28. </view>
  29. </view>
  30. <uv-loading-icon v-if="loading" custom-style="margin-top: 20px"/>
  31. <view v-else-if="filteredProfessions.length">
  32. <common-picker-cell v-for="p in filteredProfessions" :icon="p.hot?'/static/ie/entry/hot.png':''"
  33. :title="p.name" :label="p.majorDirection" :selected="isSelected(p)"
  34. @click="handleAdd(p)"/>
  35. <uv-gap height="20"/>
  36. </view>
  37. <uv-empty v-else margin-top="20"/>
  38. </scroll-view>
  39. </uv-popup>
  40. </template>
  41. <script>
  42. import {computed, ref, watch} from 'vue';
  43. import _ from 'lodash';
  44. import {findTreeNode} from "@/utils/tree-helper";
  45. import CommonPickerCell from "@/pages/ie/components/picker/common-picker-cell.vue";
  46. import {useCacheStore} from "@/hooks/useCacheStore";
  47. import {universityDetail} from "@/api/webApi/collegemajor";
  48. import {toast} from "@/uni_modules/uv-ui-tools/libs/function";
  49. import {useInjectMajorTreeService} from "@/pages/ie/hooks/useMajorTreeInjection";
  50. export default {
  51. name: "major-picker",
  52. components: {CommonPickerCell},
  53. emits: ['confirm'],
  54. props: {
  55. universityCode: {
  56. type: String,
  57. default: ''
  58. },
  59. majors: {
  60. // default majors selected
  61. type: Array,
  62. default: null
  63. },
  64. limit: {
  65. type: Number,
  66. default: 4
  67. },
  68. majorCategory: {
  69. type: [String, Number],
  70. default: ''
  71. }
  72. },
  73. setup(props, {emit}) {
  74. const popup = ref(null)
  75. const loading = ref(true)
  76. const localMajors = ref([])
  77. const detail = ref({})
  78. const {dispatchCache} = useCacheStore()
  79. const {majorTree, findNodeByCode} = useInjectMajorTreeService()
  80. const categoryNode = computed(() => {
  81. if (!props.majorCategory) return null
  82. return findNodeByCode(props.majorCategory)
  83. })
  84. const filteredProfessions = computed(() => {
  85. if (!categoryNode.value) return detail.value?.professions || []
  86. return detail.value?.professions?.filter(p => findTreeNode([categoryNode.value], n => n.code == p.code)) || []
  87. })
  88. watch(() => props.universityCode, async code => {
  89. if (code) {
  90. loading.value = true
  91. const res = await dispatchCache(universityDetail, {code})
  92. detail.value = res.data
  93. loading.value = false
  94. }
  95. })
  96. watch(() => props.majors, (values) => localMajors.value = [...values])
  97. const isSelected = (p) => {
  98. return localMajors.value.some(m => m.code == p.code && m.enrollCode == p.enrollCode)
  99. }
  100. const handleAdd = (p) => {
  101. if (props.limit == 1) {
  102. emit('confirm', p)
  103. return close()
  104. }
  105. if (localMajors.value.length >= props.limit && !isSelected(p)) {
  106. toast('最多选择' + props.limit + '个专业')
  107. return
  108. }
  109. if (isSelected(p)) handleDelete(p);
  110. else localMajors.value.push(p)
  111. }
  112. const handleDelete = (m) => {
  113. _.remove(localMajors.value, item => item.code == m.code)
  114. }
  115. const handleConfirm = () => {
  116. emit('confirm', localMajors.value)
  117. close()
  118. }
  119. const open = () => {
  120. popup.value.open()
  121. }
  122. const close = () => {
  123. popup.value.close()
  124. }
  125. return {
  126. popup,
  127. open,
  128. close,
  129. loading,
  130. localMajors,
  131. majorTree,
  132. detail,
  133. categoryNode,
  134. filteredProfessions,
  135. isSelected,
  136. handleAdd,
  137. handleDelete,
  138. handleConfirm
  139. }
  140. }
  141. }
  142. </script>
  143. <style scoped lang="scss">
  144. .pop-command {
  145. width: 100vw;
  146. position: fixed;
  147. top: 0;
  148. }
  149. ::v-deep .uni-scroll-view-content {
  150. height: fit-content;
  151. }
  152. ::v-deep .uv-cell__label {
  153. color: var(--primary-light-color);
  154. }
  155. </style>