report-table.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. <template>
  2. <div>
  3. <div class="mb10 fx-row fx-bet-cen">
  4. <div class="fx-row fx-end-cen">
  5. <el-button circle icon="el-icon-refresh" @click="refreshData" class="mr30"></el-button>
  6. <elective-enroll-info v-if="enrollInfoVisible" :generation="generation"
  7. :enroll-status="enrollStatus"></elective-enroll-info>
  8. </div>
  9. <el-button v-if="enableAIFeature" type="primary" @click="toAiAnalysis">AI分析</el-button>
  10. </div>
  11. <mx-table :propDefines="formatCols" :rows="formatRows">
  12. <template #underOver="{value}">
  13. <over-under-badge :value="value"></over-under-badge>
  14. </template>
  15. <template #group="{row}">
  16. <over-under-badge :value="row.isRecommend?1:0" over-text="荐" over-type="success">
  17. <el-tag size="medium" :type="row.allowSelect ? 'warning' : 'danger'">{{ row.groupName }}</el-tag>
  18. </over-under-badge>
  19. </template>
  20. <template #temp="{row}">
  21. <span class="btn-blue mr5" @click="toSelectSub(row)">选择</span>
  22. <span class="btn-green" @click="toReport">查看记录</span>
  23. <!-- <el-button>查看</el-button>-->
  24. </template>
  25. <template #signUp="{row}">
  26. <div v-if="!row.allowSelect">
  27. <div v-if="row.selected">
  28. <span class="btn-red" v-if="!row.rejected" @click="singleCommit('','取消报名')">取消报名</span>
  29. <span v-else>已拒绝</span>
  30. </div>
  31. <span v-else>{{ row.disabledReason || '无法报名' }}</span>
  32. </div>
  33. <div v-else>
  34. <!-- 初录后报名代 -->
  35. <span v-if="generation.current > 1" @click="singleCommit(row)" class="btn-green">报名</span>
  36. <!-- 初录报名代 -->
  37. <div v-else>
  38. <span class="f-red btn-red" v-if="row.selected" @click="toUnSelect(row)">取消报名</span>
  39. <span class="btn-green" v-else @click="toSelect(row)">报名</span>
  40. </div>
  41. </div>
  42. </template>
  43. <template #subjects="{row}">
  44. <el-row>
  45. <el-col :span="8" v-for="subject in row.subjects">
  46. <el-popover
  47. placement="top"
  48. popper-class="zero-padding-popover"
  49. trigger="hover"
  50. >
  51. <div class="fx-column">
  52. <el-button plain type="text">{{ subject }}</el-button>
  53. </div>
  54. <el-tag type="success" slot="reference" class="mr10 mb10">{{ subject[0] }}</el-tag>
  55. </el-popover>
  56. </el-col>
  57. </el-row>
  58. </template>
  59. <template #colleges="{row}">
  60. <el-row>
  61. <el-col :span="12" v-for="college in row.colleges" class="mb10 ai-center fx-row">
  62. <el-popover
  63. placement="top"
  64. popper-class="zero-padding-popover"
  65. trigger="hover"
  66. >
  67. <div class="fx-column">
  68. <el-button plain type="text">{{ college.major }}</el-button>
  69. </div>
  70. <el-tag type="success" slot="reference" class="mr10">{{ college.major[0] }}</el-tag>
  71. </el-popover>
  72. :
  73. <span class="text-ellipsis" :title="college.college">{{ college.college }}</span>
  74. </el-col>
  75. </el-row>
  76. </template>
  77. </mx-table>
  78. <!-- 初录 多志愿拖拽 -->
  79. <div
  80. v-if="this.generation.active == generation.options.primary.value || this.generation.active == generation.options.primaryDM.value">
  81. <p>您的选科志愿: <span v-for="(item,index) in activeModels.generation.selectedList">
  82. {{ item.groupName }}
  83. </span>
  84. </p>
  85. <div>
  86. <test-drage ref="drage" :btnDisabled="this.generation.current != generation.options.primary.value"
  87. :selectedList="activeModels.generation.selectedList"
  88. ></test-drage>
  89. <el-button @click="commit" type="primary" v-if="this.generation.current == generation.options.primary.value">
  90. 提交
  91. </el-button>
  92. </div>
  93. </div>
  94. <div v-if="generation.current > generation.active && generation.active > generation.options.primaryDM.value">
  95. <!-- 补录报名和二次补录报名和调剂报名历史报名信息 -->
  96. {{ historySupply }}
  97. </div>
  98. <!-- 补录报名和二次补录报名和调剂报名 -->
  99. <div v-if="flagShow">
  100. <div class="mb5 mt10 text-right">
  101. <el-popover
  102. v-if="activeModels.generation.models.filter(item => {return item.selected}).length == 0"
  103. placement="right"
  104. width="300"
  105. v-model="popoShow"
  106. trigger="click"
  107. >
  108. <div>
  109. <!-- 确定拒绝<span class="f-primary">{{recommendGroup.groupName}}</span>组合么?-->
  110. <!-- 确定拒绝系统给您推荐的<span class="f-primary">{{ recommendGroup.groupName }}</span>组合么?-->
  111. <el-input
  112. type="textarea"
  113. :rows="4"
  114. placeholder="请输入原因"
  115. v-model="regInfo"
  116. >
  117. </el-input>
  118. <p class="fx-row jc-between mt10">
  119. <el-button type="primary" size="mini" @click="popoShow = false">取消</el-button>
  120. <el-button type="danger" size="mini" @click="handleRejectRecommend">提交</el-button>
  121. </p>
  122. </div>
  123. <el-button slot="reference" type="danger">不同意</el-button>
  124. </el-popover>
  125. <el-button v-if="activeModels.generation.models.filter(item => {return item.rejected}).length > 0"
  126. type="primary" @click="singleCommit('','撤销拒绝报名')">撤销拒绝报名
  127. </el-button>
  128. </div>
  129. </div>
  130. <esign-dialog ref="esignDialog"></esign-dialog>
  131. <choose-subject-dialog ref="chooseDialog"></choose-subject-dialog>
  132. <select-subject-report-dialog ref="reportDialog"></select-subject-report-dialog>
  133. <Ai-dialog ref="aiDialog" :optionalMajors="optionalMajors" :prevPreferencesInfo="currentSupplyInfo"
  134. :generation="generation"></Ai-dialog>
  135. </div>
  136. </template>
  137. <script>
  138. import AiDialog from './ai-analysis-dialog'
  139. import MxSelectTranslate from '@/components/Cache/modules/mx-select-translate-mixin.js'
  140. import TestDrage from './test-drage'
  141. import ChooseSubjectDialog from './choose-subject-dialog'
  142. import SelectSubjectReportDialog from '@/views/system/user/profile/components/select-subject-report-dialog'
  143. import EsignDialog from '@/views/system/user/profile/components/esign-dialog'
  144. import ReportStep from './report-step'
  145. import OverUnderBadge from '@/views/elective/publish/components/steps/fauclty/over-under-badge'
  146. import { rejectRecommend, submitElectiveModels } from '@/api/webApi/elective/selected-subject'
  147. import consts from '@/common/mx-const'
  148. import { mapGetters } from 'vuex'
  149. import ElectiveEnrollInfo from '@/views/system/user/profile/components/elective-enroll-info'
  150. const resolverModules = require.context('./round-select-resolvers', false, /\.js$/)
  151. const resolvers = resolverModules.keys().map(key => resolverModules(key).default)
  152. export default {
  153. props: {
  154. generation: Object,
  155. readonly: Boolean, // 校长端不允许操作
  156. optionalMajors: { type: Array, default: () => [] }
  157. },
  158. components: {
  159. ElectiveEnrollInfo,
  160. OverUnderBadge,
  161. SelectSubjectReportDialog,
  162. ReportStep,
  163. EsignDialog,
  164. TestDrage,
  165. ChooseSubjectDialog,
  166. AiDialog
  167. },
  168. inject: {
  169. 'refreshData': {
  170. default: function() {
  171. }
  172. }
  173. },
  174. mixins: [MxSelectTranslate, ...resolvers],
  175. data() {
  176. return {
  177. popoShow: false,
  178. regInfo: '', // 拒绝原因
  179. dialogVisible: false,
  180. singleList: [], // 单志愿列表
  181. rows: []
  182. }
  183. },
  184. computed: {
  185. ...mapGetters(['hasPermissions']),
  186. enrollInfoVisible() {
  187. return this.generation.active > this.generation.options.primary.value
  188. },
  189. enrollStatus() {
  190. const enrolledGroup = this.generation.activeModel.models?.find(m => m.approved || m.forceAdjusted)
  191. if (enrolledGroup) {
  192. let enrolledModel = this.generation.activeModel
  193. do {
  194. const matched = enrolledModel.models?.find(m => m.groupId == enrolledGroup.groupId && (m.approved || m.forceAdjusted))
  195. if (matched && enrolledModel.selectedList.includes(matched)) {
  196. return { enrolledGroup, enrolledModel }
  197. }
  198. enrolledModel = enrolledModel.prevModel
  199. }
  200. while (enrolledModel)
  201. }
  202. return { enrolledGroup, enrolledModel: null }
  203. },
  204. historySupply() {
  205. // 当前代历史报名信息
  206. if (!this.generation.models.length) return ''
  207. // 填报 || 拒绝 || 未选择
  208. // 当前代
  209. const activeModels = this.generation.models.find(item => item.generation == this.generation.active)
  210. const isRefuse = activeModels.models.filter(item => item.rejected)
  211. if (isRefuse.length > 0) {
  212. // 拒绝了
  213. return '您拒绝了填报志愿'
  214. } else {
  215. // 填报 or 未选择
  216. const supply = activeModels.models.filter(item => item.selected)
  217. if (supply.length > 0) {
  218. return `填报志愿为${supply[0].groupName}`
  219. } else {
  220. return '您未填报志愿'
  221. }
  222. }
  223. },
  224. // 不符
  225. currentSupplyInfo() {
  226. if (!this.generation.models.length) return ''
  227. // console.log(this.generation)
  228. if (this.generation.active < this.generation.options.primaryDM.value) return ''
  229. let info = ''
  230. const activeModels = this.generation.models.find(item => item.generation == this.generation.active)
  231. // 当前代是否被录取?
  232. const approved = activeModels.models.filter(item => {
  233. return item.approved
  234. })
  235. if (approved.length) {
  236. // 查找在哪一阶段被录取 ? 根据selected 和 approved 都为true
  237. const model = this.generation.activeModels.filter(model => {
  238. const flag = model.models.some(item => {
  239. return item.selected && item.approved
  240. })
  241. if (flag) return model
  242. })
  243. const admissionAgent = Object.values(this.generation.options).find(item => item.value == model[0].generation - 1)
  244. // 已被录取
  245. info = `你在${admissionAgent.title}所选择的${approved[0].groupName}已被录取`
  246. } else {
  247. // 已报名未被录取的group 取当前models的前一个
  248. const prevModels = this.generation.models.find(item => item.generation == activeModels.generation - 1)
  249. // 是否拒绝 ?
  250. const isRefuse = prevModels.models.filter(item => item.rejected)
  251. if (isRefuse.length > 0) {
  252. // 拒绝报名
  253. let tips = ''
  254. const prevGen = Object.values(this.generation.options).find(item => item.value == prevModels.generation)
  255. if (prevGen.decisionMaking) {
  256. tips = Object.values(this.generation.options).find(item => item.value == prevModels.generation - 1).title
  257. } else {
  258. tips = prevGen.title
  259. }
  260. const isSupply = prevGen.decisionMaking ? ',现有以下标黄组合可以重新报名' : ''
  261. info = `<p>你在${tips}时拒绝填报志愿 ${isSupply} </p>`
  262. } else {
  263. // 没被录取的组合
  264. const refuseGroup = prevModels.models.filter(item => {
  265. return item.selected
  266. })
  267. // 获取报名的阶段
  268. let tips = ''
  269. const prevGen = Object.values(this.generation.options).find(item => item.value == prevModels.generation)
  270. if (prevGen.decisionMaking) {
  271. tips = Object.values(this.generation.options).find(item => item.value == prevModels.generation - 1).title
  272. } else {
  273. tips = prevGen.title
  274. }
  275. const isSupply = prevGen.decisionMaking ? ',现有以下标黄组合可以重新报名' : ''
  276. if (refuseGroup.length > 0) {
  277. info = `<p>你在${tips}所选择的 <span class="f-red">${refuseGroup.map(g => g.groupName).join(',').toString()}</span> 不符合条件${isSupply} </p>`
  278. } else {
  279. info = `<p>你在${tips}时未填报志愿 ${isSupply} </p>`
  280. }
  281. }
  282. }
  283. return info
  284. },
  285. flagShow() {
  286. const stepMatched = this.generation.active == this.generation.current
  287. const selectStep = !this.generation.activeOpt.decisionMaking
  288. return stepMatched && selectStep && !this.readonly && !this.activeModels.isAdmission
  289. },
  290. enableAIFeature() {
  291. if (!this.generation.activeOpt) return false
  292. const options = this.generation.options
  293. return !this.generation.activeOpt.decisionMaking &&
  294. this.generation.activeOpt != options.primary && !this.readonly
  295. },
  296. resolveTablePrefix() {
  297. return {
  298. index: {
  299. type: 'index',
  300. label: '编号'
  301. },
  302. groupName: {
  303. label: '选科组合',
  304. slot: 'group',
  305. width: '85px'
  306. },
  307. scoreSumGroup: {
  308. label: '组合成绩',
  309. hidden: this.hasPermissions([consts.enum.electivePermission.rankInGroup.scoreByGroup])
  310. },
  311. classCount: {
  312. label: '开设班级数'
  313. },
  314. personCount: {
  315. label: '人数设置'
  316. }
  317. }
  318. },
  319. resolveTableSuffix() {
  320. const stepMatched = this.generation.active == this.generation.current
  321. const enableApply = !this.generation.currentOpt.decisionMaking
  322. const enableSignUp = stepMatched && enableApply && !this.readonly
  323. return {
  324. rankInGroup: {
  325. label: '当前组合实时排名',
  326. hidden: this.hasPermissions([consts.enum.electivePermission.rankInGroup])
  327. },
  328. rankInGrade: {
  329. label: '选科全校排名',
  330. hidden: this.hasPermissions([consts.enum.electivePermission.rankInGrade])
  331. },
  332. allowSelectTips: {
  333. label: '报名状态'
  334. },
  335. temp: {
  336. label: '选择专业',
  337. width: '140',
  338. slot: 'temp',
  339. hidden: this.readonly
  340. },
  341. subjects: {
  342. label: '自选专业',
  343. slot: 'subjects',
  344. minWidth: '150'
  345. },
  346. colleges: {
  347. label: '院校',
  348. slot: 'colleges',
  349. minWidth: '250'
  350. },
  351. signUp: {
  352. label: '操作',
  353. slot: 'signUp',
  354. width: '100',
  355. fixed: 'right',
  356. hidden: !enableSignUp
  357. }
  358. }
  359. },
  360. resolveDynamicTable() {
  361. if (!Object.keys(this.formatRows).length) return {}
  362. const options = this.generation.options
  363. if (!options || !this.generation.active) return {}
  364. const optValues = Object.values(options)
  365. const dynamicColumns = {}
  366. for (let gen = options.primary.value; gen <= this.generation.active; gen++) {
  367. const opt = optValues.find(opt => opt.value == gen)
  368. const resolverKey = opt.key + 'Resolver'
  369. const resolver = this[resolverKey]
  370. if (typeof resolver === 'function') {
  371. const genColumns = resolver(gen, this.generation.active, dynamicColumns)
  372. Object.assign(dynamicColumns, genColumns)
  373. }
  374. }
  375. return dynamicColumns
  376. },
  377. activeModels() {
  378. if (!this.generation) return {}
  379. const generation = this.generation.models.find(item => item.generation == this.generation.active)
  380. // 是否被录取
  381. const isAdmission = generation.models.some(item => item.approved || item.forceAdjusted)
  382. console.log(isAdmission)
  383. // generation.selectedList = generation.models.filter(item => {
  384. // return item.selected
  385. // })
  386. return {
  387. isAdmission,
  388. generation
  389. }
  390. },
  391. recommendGroup() {
  392. return this.activeModels?.generation.models?.find(m => m.isRecommend) || {}
  393. },
  394. // 初始化 rows 填充固定数据
  395. formatRows() {
  396. if (!this.optionalMajors) return []
  397. if (!this.generation.roundGroups?.length) return []
  398. if (!this.generation.activeModels?.length) return []
  399. const generationModels = this.generation.activeModels.last()?.models || []
  400. return this.generation.roundGroups.map(rg => {
  401. const row = generationModels.find(item => item.groupId == rg.groupId) || {}
  402. row.allowSelectTips = row.rejected ? '已拒绝' : row.allowSelect ? '报名中' : row.selected ? '已报名' : row.disabledReason || '无法报名'
  403. const matchedMajors = this.optionalMajors.filter(college => college.matchedGroupIds.includes(row.groupId))
  404. row.colleges = matchedMajors.map(m => ({ college: m.collegeName, major: m.majorCategoryName }))
  405. row.subjects = matchedMajors.map(m => m['majorCategoryName'])
  406. return row
  407. })
  408. },
  409. formatCols() {
  410. return {
  411. ...this.resolveTablePrefix,
  412. ...this.resolveDynamicTable,
  413. ...this.resolveTableSuffix
  414. }
  415. }
  416. },
  417. methods: {
  418. getModelsByStep() {
  419. return this.models.findIndex()
  420. },
  421. isGroupOverSetting(row) {
  422. if (this.generation.activeOpt.decisionMaking) {
  423. return row.groupApprovedCount >= row.personCount
  424. } else {
  425. return row.actualCount >= row.groupIndicator
  426. }
  427. },
  428. toReport() {
  429. // 是否更改了报名数据 ?
  430. const flag = this.activeModels.generation.models.filter(item => item.selected).length == this.activeModels.generation.selectedList.length
  431. if (!flag) {
  432. this.$message.warning('请先提交更改过的志愿')
  433. return
  434. }
  435. this.$refs.reportDialog.open()
  436. },
  437. toAiAnalysis() {
  438. // AI 分析 跳转
  439. this.$refs.aiDialog.open(this.formatRows)
  440. },
  441. singleCommit(row, tips) {
  442. const type = row == '' ? tips : '提交报名'
  443. // 补录及之后
  444. this.$confirm(`是否要${type}`, {
  445. confirmButtonText: '确定',
  446. cancelButtonText: '取消',
  447. type: 'warning'
  448. }).then(() => {
  449. const arrRow = row == '' ? [] : [row]
  450. submitElectiveModels({
  451. models: arrRow
  452. // esign:this.base64Img
  453. }).then(res => {
  454. if (res.code == 200) {
  455. this.$message.success('操作成功')
  456. this.refreshData()
  457. }
  458. })
  459. }).catch(() => {
  460. this.$message({
  461. type: 'info',
  462. message: `已取消${type}`
  463. })
  464. })
  465. },
  466. commit(row) {
  467. // 初录
  468. const real = this.activeModels.generation.selectedList.filter(item => {
  469. return item.selected == true
  470. })
  471. if (real.length < this.generation.status.preferenceCount) {
  472. this.$message.warning(`需要选择${this.generation.status.preferenceCount}个志愿`)
  473. return
  474. }
  475. this.$confirm(`是否要提交报名`, {
  476. confirmButtonText: '确定',
  477. cancelButtonText: '取消',
  478. type: 'warning'
  479. }).then(() => {
  480. submitElectiveModels({
  481. models: this.activeModels.generation.selectedList
  482. // esign:this.base64Img
  483. }).then(res => {
  484. if (res.code == 200) {
  485. this.$message.success('报名成功')
  486. this.refreshData()
  487. }
  488. })
  489. }).catch(() => {
  490. this.$message({
  491. type: 'info',
  492. message: '已取消提交'
  493. })
  494. })
  495. //
  496. // this.$refs.esignDialog.open(this.activeModels.selectedList)
  497. },
  498. toSelect(row) {
  499. const preferenceCount = this.generation.status.preferenceCount
  500. const count = this.formatRows.reduce((prev, cur) => {
  501. return prev += cur.selected ? 1 : 0
  502. }, 0)
  503. if (count >= preferenceCount) {
  504. this.$message.warning(`最多选择${preferenceCount}个志愿`)
  505. return
  506. }
  507. this.activeModels.generation.models.find(item => item.groupId == row.groupId).selected = true
  508. row.selected = true
  509. if (!this.activeModels.selectedList.includes(row)) this.activeModels.generation.selectedList.push(row)
  510. },
  511. toUnSelect(row) {
  512. this.$confirm(`是否解除选科组合【${row.groupName}】`, '警告', {
  513. confirmButtonText: '确定',
  514. cancelButtonText: '取消',
  515. type: 'warning'
  516. }).then(() => {
  517. this.activeModels.generation.models.find(item => item.groupId == row.groupId).selected = false
  518. row.selected = false
  519. const start = this.activeModels.generation.selectedList.findIndex(item => item.groupId == row.groupId)
  520. this.activeModels.generation.selectedList.splice(start, 1)
  521. }).catch(() => {
  522. this.$message({
  523. type: 'info',
  524. message: '已取消解除报名'
  525. })
  526. })
  527. },
  528. toSelectSub(row) {
  529. // 是否更改了报名数据 ?
  530. const flag = this.activeModels.generation.models.filter(item => item.selected).length == this.activeModels.generation.selectedList.length
  531. if (!flag) {
  532. this.$message.warning('请先提交更改过的志愿')
  533. return
  534. }
  535. // 打开选科弹窗
  536. const course0 = this.translateCourse0(row.groupId)
  537. const course1 = this.translateCourse1(row.groupId)
  538. this.$refs.chooseDialog.open(course0, course1)
  539. },
  540. async handleRejectRecommend() {
  541. if (!this.regInfo.length) {
  542. this.$message.error('拒绝原因不能为空')
  543. return
  544. }
  545. const rejectRow = this.activeModels.generation.models.filter(item => item.allowSelect)
  546. await this.$confirm(`是否拒绝报名${rejectRow.map(item => item.groupName).join(',')}?`)
  547. // 可以报名的组合都拒绝
  548. rejectRecommend({
  549. models: rejectRow
  550. }).then(res => {
  551. if (res.code == 200) {
  552. this.refreshData()
  553. }
  554. }).finally(_ => {
  555. this.regInfo = ''
  556. })
  557. },
  558. initOption(optionalMajors) {
  559. this.optionalMajors = optionalMajors
  560. }
  561. }
  562. }
  563. </script>
  564. <style scoped>
  565. </style>