CardTable.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. <template>
  2. <div class="mt-4 flex-1 min-h-1 relative" :class="{ 'card-table-in-dialog': inDialog }">
  3. <div class="absolute top-0 left-0 w-full h-full" :class="{ 'card-table-in-dialog-inner': inDialog }">
  4. <el-table
  5. :data="data"
  6. class="w-full"
  7. :height="inDialog ? undefined : '100%'"
  8. :max-height="inDialog ? '60vh' : undefined"
  9. @selection-change="handleSelectionChange"
  10. v-loading="loading"
  11. >
  12. <el-table-column label="" type="selection" min-width="60" fixed="left"></el-table-column>
  13. <el-table-column label="卡号" prop="cardNo" align="center" min-width="100" fixed="left"></el-table-column>
  14. <el-table-column label="密码" prop="password" align="center" min-width="100"></el-table-column>
  15. <el-table-column label="姓名—手机" prop="studentInfo" align="center" min-width="150">
  16. <template #default="scope">
  17. <div class="text-[13px]">{{ scope.row.nickName || '-' }}</div>
  18. <div v-if="scope.row.phonenumber" class="text-[12px] text-gray-600">{{ scope.row.phonenumber }}</div>
  19. </template>
  20. </el-table-column>
  21. <el-table-column label="机构" prop="deptName" align="center" min-width="120">
  22. <template #default="scope">
  23. <div>{{ scope.row.deptName || '-' }}</div>
  24. </template>
  25. </el-table-column>
  26. <el-table-column label="省份" prop="location" align="center" min-width="120">
  27. <template #default="scope">
  28. <div>{{ scope.row.location || '-' }}</div>
  29. </template>
  30. </el-table-column>
  31. <el-table-column label="代理商" prop="agentName" align="center" min-width="120">
  32. <template #default="scope">
  33. <div>{{ scope.row.agentName || '-' }}</div>
  34. </template>
  35. </el-table-column>
  36. <el-table-column label="单招年份" prop="endYear" align="center" min-width="120">
  37. <template #default="scope">
  38. <div>{{ scope.row.endYear || '-' }}</div>
  39. </template>
  40. </el-table-column>
  41. <el-table-column label="注册学校" prop="schoolName" align="center" min-width="120" >
  42. <template #default="scope">
  43. <div class="text-[13px] ellipsis-2">{{ scope.row.schoolName || '-' }}</div>
  44. </template>
  45. </el-table-column>
  46. <el-table-column label="注册班级" prop="className" align="center" min-width="110">
  47. <template #default="scope">
  48. <div class="text-[13px] ellipsis-2">{{ scope.row.className || '-' }}</div>
  49. </template>
  50. </el-table-column>
  51. <el-table-column label="培训学校" prop="campusName" align="center" min-width="100">
  52. <template #default="scope">
  53. <div class="text-[13px] ellipsis-2">{{ scope.row.campusName || '-' }}</div>
  54. </template>
  55. </el-table-column>
  56. <el-table-column label="培训班级" prop="campusClassName" align="center" min-width="120">
  57. <template #default="scope">
  58. <div class="text-[13px] ellipsis-2">{{ scope.row.campusClassName || '-' }}</div>
  59. </template>
  60. </el-table-column>
  61. <el-table-column label="考生类型(注册)" prop="examType" align="center" min-width="140">
  62. <template #default="scope">
  63. <div v-if="scope.row && scope.row.examType && exam_type">
  64. <dict-tag :options="exam_type" :value="scope.row.examType" />
  65. <div v-if="scope.row.examMajorName || scope.row.examMajor" class="text-[12px] text-gray-600 mt-1">
  66. <span v-if="scope.row.examMajor">{{ scope.row.examMajor }}</span>
  67. <span v-if="scope.row.examMajor && scope.row.examMajorName"> - </span>
  68. <span v-if="scope.row.examMajorName">{{ scope.row.examMajorName }}</span>
  69. </div>
  70. </div>
  71. <span v-else>-</span>
  72. </template>
  73. </el-table-column>
  74. <el-table-column label="定向" prop="directedStudy" align="center" width="140">
  75. <template #default="scope">
  76. <div v-if="getFirstDirectedStudyInfo(scope.row)" class="cursor-pointer text-blue-500 hover:text-blue-700" @click="handleShowDirectedStudy(scope.row)">
  77. <el-tooltip :content="getFirstDirectedStudy(scope.row)" placement="top" :disabled="!getFirstDirectedStudy(scope.row)">
  78. <div class="directed-study-cell">
  79. <div v-if="getFirstDirectedStudyInfo(scope.row).universityName" class="directed-study-line">
  80. {{ getFirstDirectedStudyInfo(scope.row).universityName }}
  81. </div>
  82. <div v-if="getFirstDirectedStudyInfo(scope.row).majorName" class="directed-study-line">
  83. {{ getFirstDirectedStudyInfo(scope.row).majorName }}
  84. </div>
  85. </div>
  86. </el-tooltip>
  87. </div>
  88. <span v-else>-</span>
  89. </template>
  90. </el-table-column>
  91. <el-table-column label="卡类型" prop="type" align="center" min-width="120">
  92. <template #default="scope">
  93. <dict-tag :options="card_type" :value="scope.row.type" />
  94. </template>
  95. </el-table-column>
  96. <el-table-column label="分配状态" prop="distributeStatus" align="center" min-width="100">
  97. <template #default="scope">
  98. <dict-tag :options="card_distribute_status" :value="scope.row.distributeStatus" />
  99. </template>
  100. </el-table-column>
  101. <el-table-column label="使用状态" prop="status" align="center" min-width="100">
  102. <template #default="scope">
  103. <dict-tag :options="card_status" :value="scope.row.status" />
  104. </template>
  105. </el-table-column>
  106. <el-table-column label="过期状态" prop="timeStatus" align="center" min-width="100">
  107. <template #default="scope">
  108. <dict-tag :options="card_time_status" :value="scope.row.timeStatus" />
  109. </template>
  110. </el-table-column>
  111. <el-table-column label="缴费状态" prop="payStatus" align="center" min-width="100">
  112. <template #default="scope">
  113. <dict-tag :options="card_pay_status" :value="scope.row.payStatus" />
  114. </template>
  115. </el-table-column>
  116. <el-table-column label="结算状态" prop="isSettlement" align="center" min-width="100">
  117. <template #default="scope">
  118. <dict-tag :options="card_settlement_status" :value="scope.row.isSettlement" />
  119. </template>
  120. </el-table-column>
  121. <el-table-column label="分配时间" prop="distributeTime" align="center" min-width="120">
  122. <template #default="scope">
  123. <span>{{ scope.row.distributeTime ? parseTime(scope.row.distributeTime, '{y}-{m}-{d}') : '-' }}</span>
  124. </template>
  125. </el-table-column>
  126. <el-table-column label="激活时间" prop="activeTime" align="center" min-width="120">
  127. <template #default="scope">
  128. <span>{{ scope.row.activeTime ? parseTime(scope.row.activeTime, '{y}-{m}-{d}') : '-' }}</span>
  129. </template>
  130. </el-table-column>
  131. <el-table-column label="到期时间" prop="outDate" align="center" min-width="120">
  132. <template #default="scope">
  133. <span>{{ scope.row.outDate ? parseTime(scope.row.outDate, '{y}-{m}-{d}') : '-' }}</span>
  134. </template>
  135. </el-table-column>
  136. <el-table-column label="缴费时间" prop="payTime" align="center" min-width="120" :fixed="hideActions ? 'right' : undefined">
  137. <template #default="scope">
  138. <span>{{ scope.row.payTime ? parseTime(scope.row.payTime, '{y}-{m}-{d}') : '-' }}</span>
  139. </template>
  140. </el-table-column>
  141. <el-table-column label="模考次数" prop="simulate" align="center" min-width="100" :fixed="hideActions ? 'right' : undefined">
  142. <template #default="scope">
  143. <el-button type="primary" link @click="handleShowSimulate(scope.row.cardId)">查看</el-button>
  144. <el-button type="warning" link @click="handleClearDirectedStudy(scope.row)" v-if="scope.row.userId && scope.row.directedStudy">清空定向</el-button>
  145. </template>
  146. </el-table-column>
  147. <el-table-column v-if="!hideActions" label="操作" min-width="110" fixed="right" align="center">
  148. <template #default="scope">
  149. <el-button type="danger" text icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
  150. </template>
  151. </el-table-column>
  152. </el-table>
  153. </div>
  154. <!-- 定向信息弹窗 -->
  155. <el-dialog v-model="directedStudyDialogVisible" title="定向信息" width="900px">
  156. <el-table :data="directedStudyList" class="w-full" style="width: 100%">
  157. <el-table-column label="序号" type="index" width="60" align="center"></el-table-column>
  158. <el-table-column label="编码" prop="code" min-width="120" align="center"></el-table-column>
  159. <el-table-column label="学校" prop="universityName" min-width="200" align="center">
  160. <template #default="scope">
  161. <span>{{ scope.row.universityName }}{{ scope.row.universityId ? `(${scope.row.universityId})` : '' }}</span>
  162. </template>
  163. </el-table-column>
  164. <el-table-column label="专业" prop="majorName" min-width="200" align="center">
  165. <template #default="scope">
  166. <span>{{ scope.row.majorName }}{{ scope.row.majorId ? `(${scope.row.majorId})` : '' }}</span>
  167. </template>
  168. </el-table-column>
  169. <el-table-column label="专业类" prop="majorAncestors" min-width="200" align="center"></el-table-column>
  170. </el-table>
  171. </el-dialog>
  172. <!-- 模拟考试弹窗 -->
  173. <el-dialog v-model="simulateDialogVisible" title="模考次数" width="600px">
  174. <el-table :data="simulateList" class="w-full" style="width: 100%" v-loading="simulateLoading">
  175. <el-table-column label="科目ID" prop="subjectId" width="100" align="center"></el-table-column>
  176. <el-table-column label="科目名称" prop="subjectName" min-width="150" align="center"></el-table-column>
  177. <el-table-column label="次数" prop="count" width="100" align="center"></el-table-column>
  178. <el-table-column label="操作" width="100" align="center">
  179. <template #default="scope">
  180. <el-button type="primary" link @click="handleEditSimulateCount(scope.row)">修改</el-button>
  181. </template>
  182. </el-table-column>
  183. </el-table>
  184. <div v-if="!simulateLoading && simulateList.length === 0" style="text-align: center; color: #909399; padding: 20px;">
  185. 暂无模拟考试记录
  186. </div>
  187. </el-dialog>
  188. <!-- 修改次数弹窗 -->
  189. <el-dialog v-model="editCountDialogVisible" title="修改次数" width="400px">
  190. <el-form :model="editCountForm" label-width="100px">
  191. <el-form-item label="科目">
  192. <el-input v-model="editCountForm.subjectName" readonly></el-input>
  193. </el-form-item>
  194. <el-form-item label="次数" prop="count">
  195. <el-input-number v-model="editCountForm.count" :min="0" :precision="0" style="width: 100%"></el-input-number>
  196. </el-form-item>
  197. </el-form>
  198. <template #footer>
  199. <div class="dialog-footer">
  200. <el-button @click="editCountDialogVisible = false">取 消</el-button>
  201. <el-button type="primary" @click="handleSaveCount" :loading="savingCount">保 存</el-button>
  202. </div>
  203. </template>
  204. </el-dialog>
  205. </div>
  206. </template>
  207. <script setup>
  208. import DictTag from '@/components/DictTag/index.vue';
  209. import { getCurrentInstance, ref } from 'vue';
  210. import { getUserSimulateList, updateUserEvalCounts, clearUserDirectedStudy } from '@/api/dz/cards';
  211. import { ElMessage, ElMessageBox } from 'element-plus';
  212. const { proxy } = getCurrentInstance();
  213. const { card_type, exam_type, card_distribute_status, card_status, card_settlement_status, card_time_status, card_pay_status } =
  214. proxy.useDict("card_type", "exam_type", "card_distribute_status", "card_status", "card_settlement_status", "card_time_status", "card_pay_status");
  215. const props = defineProps({
  216. data: {
  217. type: Array,
  218. default: () => [],
  219. },
  220. loading: {
  221. type: Boolean,
  222. default: false,
  223. },
  224. inDialog: {
  225. type: Boolean,
  226. default: false,
  227. },
  228. hideActions: {
  229. type: Boolean,
  230. default: false,
  231. },
  232. });
  233. const emit = defineEmits(['selectionChange', 'delete', 'refresh']);
  234. const handleSelectionChange = (selection) => {
  235. emit('selectionChange', selection);
  236. };
  237. const handleDelete = (row) => {
  238. emit('delete', row);
  239. };
  240. // 安全获取 assignExamType
  241. const getAssignExamType = (row) => {
  242. return row && row.assignExamType ? row.assignExamType : null;
  243. };
  244. // 解析directedStudy JSON并获取第一个的显示文本(用于tooltip)
  245. const getFirstDirectedStudy = (row) => {
  246. if (!row || !row.directedStudy) {
  247. return null;
  248. }
  249. try {
  250. const directedStudy = typeof row.directedStudy === 'string'
  251. ? JSON.parse(row.directedStudy)
  252. : row.directedStudy;
  253. if (Array.isArray(directedStudy) && directedStudy.length > 0) {
  254. const first = directedStudy[0];
  255. const universityName = first?.universityName || '';
  256. const majorName = first?.majorName || '';
  257. if (universityName || majorName) {
  258. const parts = [];
  259. if (universityName) parts.push(universityName);
  260. if (majorName) parts.push(majorName);
  261. return parts.join(' - ');
  262. }
  263. }
  264. } catch (e) {
  265. console.error('解析directedStudy失败:', e);
  266. }
  267. return null;
  268. };
  269. // 解析directedStudy JSON并获取第一个的信息对象
  270. const getFirstDirectedStudyInfo = (row) => {
  271. if (!row || !row.directedStudy) {
  272. return null;
  273. }
  274. try {
  275. const directedStudy = typeof row.directedStudy === 'string'
  276. ? JSON.parse(row.directedStudy)
  277. : row.directedStudy;
  278. if (Array.isArray(directedStudy) && directedStudy.length > 0) {
  279. const first = directedStudy[0];
  280. const universityName = first?.universityName || '';
  281. const majorName = first?.majorName || '';
  282. if (universityName || majorName) {
  283. return {
  284. universityName: universityName,
  285. majorName: majorName
  286. };
  287. }
  288. }
  289. } catch (e) {
  290. console.error('解析directedStudy失败:', e);
  291. }
  292. return null;
  293. };
  294. // 获取完整的directedStudy列表
  295. const getDirectedStudyList = (row) => {
  296. if (!row || !row.directedStudy) {
  297. return [];
  298. }
  299. try {
  300. const directedStudy = typeof row.directedStudy === 'string'
  301. ? JSON.parse(row.directedStudy)
  302. : row.directedStudy;
  303. if (Array.isArray(directedStudy)) {
  304. return directedStudy.map(item => ({
  305. code: item?.code || '-',
  306. majorName: item?.majorName || '-',
  307. majorId: item?.majorId || null,
  308. universityName: item?.universityName || '-',
  309. universityId: item?.universityId || null,
  310. majorAncestors: item?.majorAncestors || '-'
  311. }));
  312. }
  313. } catch (e) {
  314. console.error('解析directedStudy失败:', e);
  315. }
  316. return [];
  317. };
  318. // 弹窗相关
  319. const directedStudyDialogVisible = ref(false);
  320. const directedStudyList = ref([]);
  321. // 显示定向信息弹窗
  322. const handleShowDirectedStudy = (row) => {
  323. directedStudyList.value = getDirectedStudyList(row);
  324. directedStudyDialogVisible.value = true;
  325. };
  326. // 模拟考试弹窗相关
  327. const simulateDialogVisible = ref(false);
  328. const simulateList = ref([]);
  329. const simulateLoading = ref(false);
  330. const currentCardId = ref(null);
  331. // 修改次数弹窗相关
  332. const editCountDialogVisible = ref(false);
  333. const editCountForm = ref({
  334. userId: null,
  335. subjectId: null,
  336. subjectName: '',
  337. count: 0
  338. });
  339. const savingCount = ref(false);
  340. // 显示模拟考试弹窗
  341. const handleShowSimulate = async (cardId) => {
  342. if (!cardId) {
  343. ElMessage.warning('卡号不存在')
  344. return
  345. }
  346. currentCardId.value = cardId
  347. simulateDialogVisible.value = true
  348. simulateLoading.value = true
  349. simulateList.value = []
  350. try {
  351. const response = await getUserSimulateList({ cardId })
  352. if (response && response.data) {
  353. simulateList.value = response.data
  354. } else {
  355. simulateList.value = []
  356. }
  357. } catch (error) {
  358. console.error('获取模拟考试数据失败:', error)
  359. ElMessage.error('获取模拟考试数据失败')
  360. simulateList.value = []
  361. } finally {
  362. simulateLoading.value = false
  363. }
  364. };
  365. // 显示修改次数弹窗
  366. const handleEditSimulateCount = (row) => {
  367. editCountForm.value = {
  368. userId: row.userId,
  369. subjectId: row.subjectId,
  370. subjectName: row.subjectName,
  371. count: row.count || 0
  372. };
  373. editCountDialogVisible.value = true;
  374. };
  375. // 保存次数
  376. const handleSaveCount = async () => {
  377. if (editCountForm.value.count < 0) {
  378. ElMessage.warning('次数不能为负数')
  379. return
  380. }
  381. savingCount.value = true
  382. try {
  383. await updateUserEvalCounts({
  384. userId: editCountForm.value.userId,
  385. subjectId: editCountForm.value.subjectId,
  386. count: editCountForm.value.count
  387. })
  388. ElMessage.success('修改成功')
  389. editCountDialogVisible.value = false
  390. // 更新本地列表数据
  391. const item = simulateList.value.find(item => item.subjectId === editCountForm.value.subjectId)
  392. if (item) {
  393. item.count = editCountForm.value.count
  394. }
  395. } catch (error) {
  396. console.error('修改次数失败:', error)
  397. ElMessage.error('修改次数失败')
  398. } finally {
  399. savingCount.value = false
  400. }
  401. };
  402. // 清空定向
  403. const handleClearDirectedStudy = async (row) => {
  404. if (!row || !row.userId) {
  405. ElMessage.warning('用户ID不存在')
  406. return
  407. }
  408. try {
  409. await ElMessageBox.confirm('确定要清空该用户的定向信息吗?', '提示', {
  410. confirmButtonText: '确定',
  411. cancelButtonText: '取消',
  412. type: 'warning'
  413. })
  414. await clearUserDirectedStudy(row.userId)
  415. ElMessage.success('清空定向成功')
  416. // 更新本地数据,将 directedStudy 设置为 null
  417. if (row) {
  418. row.directedStudy = null
  419. }
  420. // 触发刷新,通知父组件更新数据
  421. emit('refresh')
  422. } catch (error) {
  423. if (error !== 'cancel') {
  424. console.error('清空定向失败:', error)
  425. ElMessage.error('清空定向失败')
  426. }
  427. }
  428. };
  429. </script>
  430. <style lang="scss" scoped>
  431. /* 在弹窗中使用时的样式 */
  432. .card-table-in-dialog {
  433. position: relative !important;
  434. height: auto !important;
  435. min-height: 400px;
  436. max-height: 60vh;
  437. margin-top: 0 !important;
  438. }
  439. .card-table-in-dialog-inner {
  440. position: relative !important;
  441. height: auto !important;
  442. width: 100% !important;
  443. top: 0 !important;
  444. left: 0 !important;
  445. }
  446. .directed-study-cell {
  447. text-align: center;
  448. line-height: 1.5;
  449. }
  450. .directed-study-line {
  451. overflow: hidden;
  452. text-overflow: ellipsis;
  453. white-space: nowrap;
  454. font-size: 12px;
  455. }
  456. </style>