student-list-dialog.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. <template>
  2. <!-- 学生列表弹窗 -->
  3. <el-dialog v-model="dialogVisible" :title="dialogTitle" width="1200px" destroy-on-close>
  4. <div class="student-table-container">
  5. <el-table
  6. v-loading="loading"
  7. :data="studentList"
  8. stripe
  9. height="calc(100vh - 350px)"
  10. style="width: 100%"
  11. >
  12. <el-table-column label="姓名-手机" prop="namePhone" min-width="180" align="center"/>
  13. <el-table-column label="卡号" prop="cardNo" min-width="120" align="center"/>
  14. <el-table-column label="注册学校" prop="registerSchool" min-width="120" align="center" show-overflow-tooltip/>
  15. <el-table-column label="学校班级" prop="registerClass" min-width="80" align="center" show-overflow-tooltip/>
  16. <el-table-column label="培训校区" prop="trainSchool" min-width="120" align="center" show-overflow-tooltip/>
  17. <el-table-column label="校区班级" prop="trainClass" min-width="80" align="center" show-overflow-tooltip/>
  18. <el-table-column label="考生类型" prop="examType" min-width="150" align="center">
  19. <template #default="scope">
  20. <template v-if="scope.row && scope.row.examType && exam_type">
  21. <dict-tag :options="exam_type" :value="scope.row.examType" />
  22. </template>
  23. <span v-else>-</span>
  24. </template>
  25. </el-table-column>
  26. <el-table-column label="定向" prop="direct" min-width="150" align="center">
  27. <template #default="{row}">
  28. <div v-if="getFirstDirectedStudyInfo(row.direct)" class="cursor-pointer text-blue-500 hover:text-blue-700" @click.stop="handleShowDirectedStudy(row.direct)">
  29. <el-tooltip :content="getFirstDirectedStudyTooltip(row.direct)" placement="top" :disabled="!getFirstDirectedStudyInfo(row.direct)">
  30. <div class="directed-study-cell">
  31. <div v-if="getFirstDirectedStudyInfo(row.direct).universityName" class="directed-study-line">
  32. {{ getFirstDirectedStudyInfo(row.direct).universityName }}
  33. </div>
  34. <div v-if="getFirstDirectedStudyInfo(row.direct).majorName" class="directed-study-line">
  35. {{ getFirstDirectedStudyInfo(row.direct).majorName }}
  36. </div>
  37. </div>
  38. </el-tooltip>
  39. </div>
  40. <span v-else>-</span>
  41. </template>
  42. </el-table-column>
  43. <el-table-column label="代理商" prop="agent" min-width="120" align="center" show-overflow-tooltip/>
  44. <el-table-column label="单招年份" prop="year" min-width="80" align="center"/>
  45. <el-table-column label="省份" prop="province" min-width="80" align="center"/>
  46. <el-table-column label="机构" prop="institution" min-width="150" align="center" show-overflow-tooltip/>
  47. </el-table>
  48. <!-- 前端分页 -->
  49. <div class="student-pagination">
  50. <el-pagination
  51. v-model:current-page="pageNum"
  52. v-model:page-size="pageSize"
  53. :page-sizes="[10, 20, 50, 100]"
  54. :total="total"
  55. layout="total, sizes, prev, pager, next, jumper"
  56. @size-change="handlePageSizeChange"
  57. @current-change="handlePageChange"
  58. />
  59. </div>
  60. </div>
  61. <!-- 定向信息弹窗 -->
  62. <el-dialog v-model="directDetailVisible" title="定向信息" width="900px" destroy-on-close>
  63. <el-table :data="directDetailData" class="w-full" style="width: 100%">
  64. <el-table-column label="序号" type="index" width="60" align="center"></el-table-column>
  65. <el-table-column label="编码" prop="code" min-width="120" align="center"></el-table-column>
  66. <el-table-column label="学校" prop="universityName" min-width="200" align="center">
  67. <template #default="scope">
  68. <span>{{ scope.row.universityName }}{{ scope.row.universityId ? `(${scope.row.universityId})` : '' }}</span>
  69. </template>
  70. </el-table-column>
  71. <el-table-column label="专业" prop="majorName" min-width="200" align="center">
  72. <template #default="scope">
  73. <span>{{ scope.row.majorName }}{{ scope.row.majorId ? `(${scope.row.majorId})` : '' }}</span>
  74. </template>
  75. </el-table-column>
  76. <el-table-column label="专业类" prop="majorAncestors" min-width="200" align="center"></el-table-column>
  77. </el-table>
  78. </el-dialog>
  79. </el-dialog>
  80. </template>
  81. <script setup name="StudentListDialog">
  82. import { ref, getCurrentInstance } from 'vue'
  83. import { ElMessage } from 'element-plus'
  84. import { getClassesBuildStatsDetail } from '@/api/dz/papers.js'
  85. import DictTag from '@/components/DictTag/index.vue'
  86. const { proxy } = getCurrentInstance()
  87. const { exam_type } = proxy.useDict("exam_type")
  88. // Props
  89. const props = defineProps({
  90. // 统计类型映射,用于显示标题
  91. statTypeMap: {
  92. type: Object,
  93. default: () => ({
  94. 'send': '组卷已完成',
  95. 'total': '班级人数',
  96. 'unexact': '未定向未组卷',
  97. 'unfinish': '组卷未完成',
  98. 'unsend': '定向未组卷'
  99. })
  100. }
  101. })
  102. // 弹窗状态
  103. const dialogVisible = ref(false)
  104. const dialogTitle = ref('')
  105. const loading = ref(false)
  106. // 学生列表数据
  107. const studentList = ref([])
  108. const pageNum = ref(1)
  109. const pageSize = ref(10)
  110. const total = ref(0)
  111. // 当前查询参数
  112. const currentParams = ref(null)
  113. // 定向详情弹窗相关
  114. const directDetailVisible = ref(false)
  115. const directDetailData = ref([])
  116. // 解析directedStudy JSON并获取第一个的显示文本(用于tooltip)
  117. const getFirstDirectedStudyTooltip = (direct) => {
  118. if (!direct) {
  119. return null
  120. }
  121. try {
  122. const directedStudy = typeof direct === 'string'
  123. ? JSON.parse(direct)
  124. : direct
  125. if (Array.isArray(directedStudy) && directedStudy.length > 0) {
  126. const first = directedStudy[0]
  127. const universityName = first?.universityName || ''
  128. const majorName = first?.majorName || ''
  129. if (universityName || majorName) {
  130. const parts = []
  131. if (universityName) parts.push(universityName)
  132. if (majorName) parts.push(majorName)
  133. return parts.join(' - ')
  134. }
  135. }
  136. } catch (e) {
  137. console.error('解析directedStudy失败:', e)
  138. }
  139. return null
  140. }
  141. // 解析directedStudy JSON并获取第一个的信息对象
  142. const getFirstDirectedStudyInfo = (direct) => {
  143. if (!direct) {
  144. return null
  145. }
  146. try {
  147. const directedStudy = typeof direct === 'string'
  148. ? JSON.parse(direct)
  149. : direct
  150. if (Array.isArray(directedStudy) && directedStudy.length > 0) {
  151. const first = directedStudy[0]
  152. const universityName = first?.universityName || ''
  153. const majorName = first?.majorName || ''
  154. if (universityName || majorName) {
  155. return {
  156. universityName: universityName,
  157. majorName: majorName
  158. }
  159. }
  160. }
  161. } catch (e) {
  162. console.error('解析directedStudy失败:', e)
  163. }
  164. return null
  165. }
  166. // 获取完整的directedStudy列表
  167. const getDirectedStudyList = (direct) => {
  168. if (!direct) {
  169. return []
  170. }
  171. try {
  172. const directedStudy = typeof direct === 'string'
  173. ? JSON.parse(direct)
  174. : direct
  175. if (Array.isArray(directedStudy)) {
  176. return directedStudy.map(item => ({
  177. code: item?.code || '-',
  178. majorName: item?.majorName || '-',
  179. majorId: item?.majorId || null,
  180. universityName: item?.universityName || '-',
  181. universityId: item?.universityId || null,
  182. majorAncestors: item?.majorAncestors || '-'
  183. }))
  184. }
  185. } catch (e) {
  186. console.error('解析directedStudy失败:', e)
  187. }
  188. return []
  189. }
  190. // 显示定向信息弹窗
  191. const handleShowDirectedStudy = (direct) => {
  192. directDetailData.value = getDirectedStudyList(direct)
  193. directDetailVisible.value = true
  194. }
  195. // 加载学生列表
  196. const loadStudentList = async () => {
  197. if (!currentParams.value) {
  198. console.error('参数不完整')
  199. return
  200. }
  201. loading.value = true
  202. try {
  203. const res = await getClassesBuildStatsDetail(currentParams.value)
  204. const allStudents = res.data || []
  205. total.value = allStudents.length
  206. // 前端分页
  207. const start = (pageNum.value - 1) * pageSize.value
  208. const end = start + pageSize.value
  209. studentList.value = allStudents.slice(start, end)
  210. } catch (error) {
  211. console.error('加载学生列表失败:', error)
  212. ElMessage.error('加载学生列表失败: ' + (error.message || '未知错误'))
  213. } finally {
  214. loading.value = false
  215. }
  216. }
  217. // 分页大小改变
  218. const handlePageSizeChange = (size) => {
  219. pageSize.value = size
  220. pageNum.value = 1
  221. loadStudentList()
  222. }
  223. // 页码改变
  224. const handlePageChange = (page) => {
  225. pageNum.value = page
  226. loadStudentList()
  227. }
  228. // 打开弹窗
  229. const open = (row, statType, buildType, batchId, queryParams = {}) => {
  230. if (!row || !statType || !buildType || !batchId) {
  231. console.error('参数错误:', { row, statType, buildType, batchId })
  232. return
  233. }
  234. // 构建查询参数
  235. const params = {
  236. buildType,
  237. batchId,
  238. classId: row.classId,
  239. statType
  240. }
  241. // 添加额外的查询条件
  242. if (queryParams.examType) params.examType = queryParams.examType
  243. if (queryParams.subjectId) params.subjectId = queryParams.subjectId
  244. if (queryParams.universityId) params.universityId = queryParams.universityId
  245. if (queryParams.majorGroup) params.majorGroup = queryParams.majorGroup
  246. if (queryParams.majorPlanId) params.majorPlanId = queryParams.majorPlanId
  247. currentParams.value = params
  248. // 设置标题
  249. dialogTitle.value = `${row.className || ''} - ${props.statTypeMap[statType] || statType}`
  250. // 重置分页
  251. pageNum.value = 1
  252. pageSize.value = 10
  253. // 显示弹窗并加载数据
  254. dialogVisible.value = true
  255. loadStudentList()
  256. }
  257. // 暴露方法给父组件
  258. defineExpose({
  259. open
  260. })
  261. </script>
  262. <style scoped>
  263. .student-table-container {
  264. display: flex;
  265. flex-direction: column;
  266. height: calc(100vh - 300px);
  267. min-height: 500px;
  268. max-height: calc(100vh - 300px);
  269. }
  270. .student-pagination {
  271. margin-top: 20px;
  272. text-align: right;
  273. flex-shrink: 0;
  274. padding: 10px 0;
  275. background-color: #fff;
  276. }
  277. .directed-study-cell {
  278. text-align: center;
  279. line-height: 1.5;
  280. }
  281. .directed-study-line {
  282. overflow: hidden;
  283. text-overflow: ellipsis;
  284. white-space: nowrap;
  285. font-size: 12px;
  286. }
  287. </style>