You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

533 lines
12 KiB

2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
  1. <!-- src/components/WrongQuestion/WrongQuestionTable.vue -->
  2. <template>
  3. <div class="table-container">
  4. <table>
  5. <thead>
  6. <tr>
  7. <th>ID</th>
  8. <th>题干</th>
  9. <th>题目类型</th>
  10. <th>出错次数</th>
  11. <th>出错率</th>
  12. <th>推荐课程</th>
  13. <th>操作</th>
  14. </tr>
  15. </thead>
  16. <tbody>
  17. <tr v-for="(item,index) in wrongQuestions" :key="item.id">
  18. <td>{{ (page- 1) * pageSize + index + 1 }}</td>
  19. <td>{{ item.stem }}</td>
  20. <td>{{ item.questionTypeName }}</td>
  21. <td>{{ item.errorCount }}</td>
  22. <td>{{ item.errorRate }}%</td>
  23. <td>{{ item.CrName }}</td>
  24. <td class="operation-cell">
  25. <button class="btn-red small" @click="viewUser(item)">出错用户</button>
  26. <button class="btn-red small" @click="viewQuestion(item)">查看题目</button>
  27. </td>
  28. </tr>
  29. </tbody>
  30. </table>
  31. <!-- 分页控件 -->
  32. <div class="pagination-container">
  33. <div class="pagination-info">
  34. {{ total }} 条记录 {{ page }}
  35. </div>
  36. <div class="pagination-controls">
  37. <button
  38. class="btn-pagination"
  39. :disabled="page <= 1"
  40. @click="changePage(1)"
  41. >
  42. 首页
  43. </button>
  44. <button
  45. class="btn-pagination"
  46. :disabled="page <= 1"
  47. @click="changePage(page - 1)"
  48. >
  49. 上一页
  50. </button>
  51. <input
  52. type="number"
  53. class="page-input"
  54. :value="page"
  55. @keyup.enter="jumpToPage"
  56. min="1"
  57. :max="totalPages"
  58. />
  59. <span class="page-info">/ {{ totalPages }}</span>
  60. <button
  61. class="btn-pagination"
  62. :disabled="page >= totalPages"
  63. @click="changePage(page + 1)"
  64. >
  65. 下一页
  66. </button>
  67. <button
  68. class="btn-pagination"
  69. :disabled="page >= totalPages"
  70. @click="changePage(totalPages)"
  71. >
  72. 尾页
  73. </button>
  74. </div>
  75. </div>
  76. <!-- 用户弹窗 -->
  77. <div v-if="showUserModal" class="modal-overlay">
  78. <div class="modal-content">
  79. <h3>出错用户列表</h3>
  80. <table class="user-table">
  81. <thead>
  82. <tr>
  83. <th>用户名称</th>
  84. <th>用户身份</th>
  85. <th>出错次数</th>
  86. </tr>
  87. </thead>
  88. <tbody>
  89. <tr v-for="user in errorUsers" :key="user.user_name">
  90. <td>{{ user.user_name }}</td>
  91. <td>{{ user.user_identity }}</td>
  92. <td>{{ user.error_count }}</td>
  93. </tr>
  94. </tbody>
  95. </table>
  96. <button class="btn-close" @click="closeModal">关闭</button>
  97. </div>
  98. </div>
  99. <!-- 查看题目详情弹窗 -->
  100. <div v-if="showViewModal" class="modal-overlay" @click.self="closeViewModal">
  101. <div class="view-modal-content" @click.stop>
  102. <div class="modal-header">
  103. <h3>题目详情</h3>
  104. <button class="close-btn" @click="closeViewModal">×</button>
  105. </div>
  106. <div class="view-modal-body">
  107. <p class="question-text">{{ currentQuestion.stem }}</p>
  108. <div class="options-container">
  109. <div
  110. v-for="option in ['A', 'B', 'C', 'D']"
  111. :key="option"
  112. :class="[option === currentQuestion.correctAnswer ? 'option-item-correct' : 'option-item']"
  113. >
  114. {{ `${option}. ${currentQuestion[`option${option}`]}` }}
  115. </div>
  116. </div>
  117. </div>
  118. <div class="modal-footer">
  119. <button class="btn-red" @click="closeViewModal">退出</button>
  120. </div>
  121. </div>
  122. </div>
  123. </div>
  124. </template>
  125. <script>
  126. import { getQuestions } from '@/api/question.js';
  127. export default {
  128. name: 'WrongQuestionTable',
  129. data() {
  130. return {
  131. wrongQuestions: [],
  132. total: 0,
  133. page: 1,
  134. pageSize: 20,
  135. showUserModal: false,
  136. errorUsers: [],
  137. showViewModal: false,
  138. currentQuestion: {},
  139. currentFilters: {}
  140. }
  141. },
  142. computed: {
  143. totalPages() {
  144. try {
  145. return this.total !== 0 ? Math.ceil(this.total / this.pageSize) : 1;
  146. } catch(error) {
  147. console.error('计算总页数时出错:', error);
  148. return 1;
  149. }
  150. }
  151. },
  152. methods: {
  153. setFilters(filters) {
  154. this.currentFilters = filters;
  155. this.page = 1;
  156. this.fetchWrongQuestions();
  157. },
  158. async fetchWrongQuestions() {
  159. try {
  160. const params = new URLSearchParams();
  161. params.append('page', this.page);
  162. params.append('page_size', this.pageSize);
  163. // 处理过滤条件
  164. const questionTypeIdMap = {
  165. '股票知识': 1,
  166. '企业文化': 2
  167. };
  168. if (this.currentFilters.questionType) {
  169. params.append('question_type_id', questionTypeIdMap[this.currentFilters.questionType]);
  170. }
  171. const courseRecommendationIdMap = {
  172. '量能擒牛': 1,
  173. '价格破译': 2,
  174. '量价时空综合': 3
  175. };
  176. if (this.currentFilters.course) {
  177. params.append('course_recommendation_id', courseRecommendationIdMap[this.currentFilters.course]);
  178. }
  179. if (this.currentFilters.keyword) {
  180. params.append('stem', this.currentFilters.keyword);
  181. }
  182. const response = await getQuestions(params);
  183. if (response.data.code === 200) {
  184. this.wrongQuestions = response.data.data.list
  185. this.total = response.data.data.total;
  186. } else {
  187. console.error('接口返回错误:', response.data.msg)
  188. }
  189. } catch (error) {
  190. console.error('获取错题数据失败:', error)
  191. }
  192. },
  193. viewUser(item) {
  194. this.$emit('view-user', item)
  195. this.fetchErrorUsers(item.id)
  196. },
  197. async fetchErrorUsers(questionId) {
  198. try {
  199. const { getUsersByQuestionId } = await import('@/api/wrongQuestion')
  200. const response = await getUsersByQuestionId(questionId)
  201. if (response.data.code === 200) {
  202. this.errorUsers = response.data.data.list || []
  203. this.showUserModal = true
  204. } else {
  205. console.error('获取用户失败:', response.data.msg)
  206. }
  207. } catch (error) {
  208. console.error('请求失败:', error)
  209. }
  210. },
  211. async viewQuestion(item) {
  212. const row = document.querySelector(`[data-id="${item.id}"]`)
  213. if (row) {
  214. row.scrollIntoView({ behavior: 'smooth', block: 'center' })
  215. }
  216. try {
  217. // 使用统一的API调用方式
  218. const params = {
  219. Page: 1,
  220. PageSize: 100,
  221. id: item.id
  222. }
  223. const response = await getQuestions(params)
  224. if (response.data.code === 200 && response.data.data.list && response.data.data.list.length > 0) {
  225. const rawQuestion = response.data.data.list.find(q => q.id === item.id)
  226. if (!rawQuestion) {
  227. alert('未找到该题目!')
  228. return
  229. }
  230. this.currentQuestion = {
  231. id: rawQuestion.id,
  232. stem: rawQuestion.stem,
  233. optionA: rawQuestion.A,
  234. optionB: rawQuestion.B,
  235. optionC: rawQuestion.C,
  236. optionD: rawQuestion.D,
  237. correctAnswer: rawQuestion.correctAnswer,
  238. questionTypeName: rawQuestion.questionTypeName,
  239. CrName: rawQuestion.CrName
  240. }
  241. this.showViewModal = true
  242. } else {
  243. alert('未找到该题目!')
  244. }
  245. } catch (error) {
  246. console.error('获取题目详情失败:', error)
  247. alert('网络错误,请检查连接!')
  248. }
  249. },
  250. closeViewModal() {
  251. this.showViewModal = false
  252. },
  253. changePage(newPage) {
  254. if (newPage >= 1 && newPage <= this.totalPages) {
  255. this.page = newPage;
  256. this.fetchWrongQuestions();
  257. }
  258. },
  259. jumpToPage(event) {
  260. const targetPage = parseInt(event.target.value)
  261. if (targetPage >= 1 && targetPage <= this.totalPages) {
  262. this.page = targetPage;
  263. this.fetchWrongQuestions();
  264. } else {
  265. event.target.value = this.page
  266. }
  267. },
  268. closeModal() {
  269. this.showUserModal = false
  270. this.errorUsers = []
  271. }
  272. },
  273. mounted() {
  274. this.fetchWrongQuestions();
  275. }
  276. }
  277. </script>
  278. <style scoped>
  279. .table-container {
  280. width: 100%;
  281. border-collapse: collapse;
  282. margin-top: 10px;
  283. }
  284. table {
  285. width: 100%;
  286. border-collapse: collapse;
  287. background-color: white;
  288. }
  289. th,td {
  290. padding: 12px;
  291. text-align: left;
  292. border-bottom: 1px solid #ddd;
  293. }
  294. th {
  295. background-color: #f2f2f2;
  296. font-weight: normal;
  297. color: #333;
  298. display: table-cell !important;
  299. vertical-align: middle !important;
  300. }
  301. tr:hover {
  302. background-color: #f9f9f9;
  303. }
  304. .operation-cell {
  305. display: flex;
  306. gap: 16px;
  307. }
  308. /* 分页样式 */
  309. .pagination-container {
  310. display: flex;
  311. justify-content: flex-start;
  312. align-items: center;
  313. margin-top: 20px;
  314. padding: 0 10px;
  315. }
  316. .pagination-info {
  317. font-size: 14px;
  318. color: #666;
  319. margin-right: 20px;
  320. }
  321. .pagination-controls {
  322. display: flex;
  323. align-items: center;
  324. gap: 10px;
  325. }
  326. .btn-pagination {
  327. padding: 6px 12px;
  328. background-color: #f2f2f2;
  329. border: 1px solid #ddd;
  330. border-radius: 4px;
  331. cursor: pointer;
  332. font-size: 14px;
  333. transition: all 0.2s;
  334. }
  335. .btn-pagination:hover:not(:disabled) {
  336. background-color: #e0e0e0;
  337. }
  338. .btn-pagination:disabled {
  339. background-color: #f5f5f5;
  340. color: #ccc;
  341. cursor: not-allowed;
  342. }
  343. .page-input {
  344. width: 50px;
  345. padding: 6px;
  346. border: 1px solid #ddd;
  347. border-radius: 4px;
  348. text-align: center;
  349. font-size: 14px;
  350. }
  351. .page-info {
  352. font-size: 14px;
  353. color: #666;
  354. }
  355. /* 弹窗样式 */
  356. .modal-overlay {
  357. position: fixed;
  358. top: 0;
  359. left: 0;
  360. right: 0;
  361. bottom: 0;
  362. background-color: rgba(0, 0, 0, 0.5);
  363. display: flex;
  364. justify-content: center;
  365. align-items: center;
  366. z-index: 1000;
  367. }
  368. .modal-content {
  369. background: white;
  370. padding: 20px;
  371. border-radius: 8px;
  372. width: 600px;
  373. max-width: 90%;
  374. box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  375. }
  376. .view-modal-content {
  377. background-color: white;
  378. border-radius: 8px;
  379. width: 750px;
  380. max-width: 90%;
  381. box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
  382. overflow: hidden;
  383. }
  384. .view-modal-body {
  385. padding: 20px;
  386. max-height: 600px;
  387. overflow-y: auto;
  388. }
  389. .question-text {
  390. font-size: 16px;
  391. margin-bottom: 20px;
  392. line-height: 1.5;
  393. }
  394. .options-container {
  395. display: flex;
  396. flex-direction: column;
  397. gap: 10px;
  398. }
  399. .option-item {
  400. padding: 10px;
  401. border-radius: 6px;
  402. background-color: #f8d7da;
  403. color: #721c24;
  404. border: 1px solid #f5c6cb;
  405. transition: all 0.2s;
  406. }
  407. .option-item-correct {
  408. padding: 10px;
  409. border-radius: 6px;
  410. background-color: #f8d7da;
  411. color: #721c24;
  412. border: 1px solid #f5c6cb;
  413. transition: all 0.2s;
  414. background-color: #dc3545 !important;
  415. color: white !important;
  416. border-color: #c82333 !important;
  417. font-weight: bold;
  418. }
  419. .modal-header {
  420. padding: 20px;
  421. border-bottom: 1px solid #eee;
  422. display: flex;
  423. justify-content: space-between;
  424. align-items: center;
  425. }
  426. .modal-header h3 {
  427. margin: 0;
  428. font-size: 18px;
  429. color: #333;
  430. }
  431. .close-btn {
  432. background: none;
  433. border: none;
  434. font-size: 24px;
  435. cursor: pointer;
  436. color: #666;
  437. padding: 5px;
  438. border-radius: 50%;
  439. transition: color 0.2s;
  440. }
  441. .close-btn:hover {
  442. color: #e74c3c;
  443. }
  444. .modal-footer {
  445. padding: 20px;
  446. border-top: 1px solid #eee;
  447. display: flex;
  448. justify-content: flex-end;
  449. gap: 16px;
  450. }
  451. .user-table {
  452. width: 100%;
  453. border-collapse: collapse;
  454. margin-top: 10px;
  455. }
  456. .user-table th,
  457. .user-table td {
  458. padding: 10px;
  459. text-align: left;
  460. border-bottom: 1px solid #ddd;
  461. }
  462. .user-table th {
  463. background-color: #f2f2f2;
  464. }
  465. .btn-close {
  466. margin-top: 15px;
  467. padding: 8px 16px;
  468. background-color: #e74c3c;
  469. color: white;
  470. border: none;
  471. border-radius: 4px;
  472. cursor: pointer;
  473. }
  474. </style>