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.

740 lines
19 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
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/Question/QuestionTable.vue -->
  2. <template>
  3. <div class="question-table-container">
  4. <div class="table-container">
  5. <table>
  6. <thead>
  7. <tr>
  8. <th>ID</th>
  9. <th>题干</th>
  10. <th>题目类型</th>
  11. <th>出错次数</th>
  12. <th>出错率</th>
  13. <th>推荐系列</th>
  14. <th>操作</th>
  15. </tr>
  16. </thead>
  17. <tbody>
  18. <tr v-for="(item,index) in items" :key="item.id" :data-id="item.id">
  19. <td>{{ (page- 1) * pageSize + index + 1 }}</td>
  20. <td>{{ item.stem }}</td>
  21. <td>{{ item.questionTypeName }}</td>
  22. <td>{{ item.errorCount }}</td>
  23. <td>{{ item.errorRate }}%</td>
  24. <td>{{ item.CrName }}</td>
  25. <td>
  26. <button class="btn-red small" @click="viewQuestion(item)">查看</button>
  27. <button class="btn-red small" @click="editQuestion(item)">修改</button>
  28. <button class="btn-red small" @click="deleteQuestion(item)">删除</button>
  29. </td>
  30. </tr>
  31. </tbody>
  32. </table>
  33. </div>
  34. <div class="pagination-container">
  35. <div class="pagination-info">
  36. {{ total }} 条记录 {{ page }}
  37. </div>
  38. <div class="pagination-controls">
  39. <button
  40. class="btn-pagination"
  41. :disabled="page <= 1"
  42. @click="changePage(1)"
  43. >
  44. 首页
  45. </button>
  46. <button
  47. class="btn-pagination"
  48. :disabled="page <= 1"
  49. @click="changePage(page - 1)"
  50. >
  51. 上一页
  52. </button>
  53. <input type="number" class="page-input" :value="page" @keyup.enter="jumpToPage" min="1" :max="totalPages"/>
  54. <span class="page-info">/ {{ totalPages }}</span>
  55. <button class="btn-pagination" :disabled="page >= totalPages" @click="changePage(page + 1)">
  56. 下一页
  57. </button>
  58. <button class="btn-pagination" :disabled="page >= totalPages" @click="changePage(totalPages)">
  59. 尾页
  60. </button>
  61. </div>
  62. </div>
  63. <div v-if="showViewModal" class="modal-overlay" @click.self="closeViewModal">
  64. <div class="view-modal-content" @click.stop>
  65. <div class="modal-header">
  66. <h3>题目详情</h3>
  67. <button class="close-btn" @click="closeViewModal">×</button>
  68. </div>
  69. <div class="view-modal-body">
  70. <p class="question-text">{{ currentQuestion.stem }}</p>
  71. <div class="options-container">
  72. <div v-for="option in ['A', 'B', 'C', 'D']" :key="option" :class="[option === currentQuestion.correctAnswer ? 'option-item-correct' : 'option-item']">
  73. {{ `${option}. ${currentQuestion[`option${option}`]}` }}
  74. </div>
  75. </div>
  76. </div>
  77. <div class="modal-footer">
  78. <button class="btn-red" @click="closeViewModal">退出</button>
  79. </div>
  80. </div>
  81. </div>
  82. <div class="footer-btn">
  83. <button class="btn-red">应用</button>
  84. </div>
  85. <div v-if="showEditModal" class="modal-overlay" @click.self="closeEditModal">
  86. <div class="modal-content" @click.stop>
  87. <div class="modal-header">
  88. <h3>编辑题目</h3>
  89. <button class="close-btn" @click="closeEditModal">×</button>
  90. </div>
  91. <div class="modal-body">
  92. <div class="form-row">
  93. <label>题目类型</label>
  94. <select v-model="editingQuestion.questionTypeName">
  95. <option value="股票知识">股票知识</option>
  96. <option value="企业文化">企业文化</option>
  97. </select>
  98. </div>
  99. <div class="form-row">
  100. <label>题干</label>
  101. <textarea v-model="editingQuestion.stem" placeholder="请输入题目内容" rows="4" style="width: 545px; height: 120px;"
  102. ></textarea>
  103. </div>
  104. <div class="form-row-options">
  105. <div class="option-group">
  106. <label>选项A</label>
  107. <input type="text" v-model="editingQuestion.optionA" placeholder="请输入选项A" style="width: 280px; height: 40px;"/>
  108. </div>
  109. <div class="option-group">
  110. <label>选项B</label>
  111. <input type="text" v-model="editingQuestion.optionB" placeholder="请输入选项B" style="width: 280px; height: 40px;"/>
  112. </div>
  113. <div class="option-group">
  114. <label>选项C</label>
  115. <input type="text" v-model="editingQuestion.optionC" placeholder="请输入选项C" style="width: 280px; height: 40px;"/>
  116. </div>
  117. <div class="option-group">
  118. <label>选项D</label>
  119. <input type="text" v-model="editingQuestion.optionD" placeholder="请输入选项D" style="width: 280px; height: 40px;"/>
  120. </div>
  121. </div>
  122. <div class="form-row">
  123. <label>正确答案</label>
  124. <select v-model="editingQuestion.correctAnswer">
  125. <option value="A">A</option>
  126. <option value="B">B</option>
  127. <option value="C">C</option>
  128. <option value="D">D</option>
  129. </select>
  130. </div>
  131. <div class="form-row">
  132. <label>推荐课程</label>
  133. <select v-model="editingQuestion.recommendedCourse">
  134. <option value="量能擒牛">量能擒牛</option>
  135. <option value="价格破译">价格破译</option>
  136. <option value="量价时空综合">量价时空综合</option>
  137. </select>
  138. </div>
  139. </div>
  140. <div class="modal-footer">
  141. <button class="btn-red" @click="updateQuestion">确定</button>
  142. <button class="btn-red" @click="closeEditModal">取消</button>
  143. </div>
  144. </div>
  145. </div>
  146. <div v-if="showDeleteModal" class="modal-overlay" @click.self="closeDeleteModal">
  147. <div class="delete-modal-content" @click.stop>
  148. <div class="modal-header">
  149. <h3>您确定要删除吗</h3>
  150. <button class="close-btn" @click="closeDeleteModal">×</button>
  151. </div>
  152. <div class="modal-footer">
  153. <button class="btn-red" @click="confirmDelete">确定</button>
  154. <button class="btn-red" @click="closeDeleteModal">取消</button>
  155. </div>
  156. </div>
  157. </div>
  158. </div>
  159. </template>
  160. <script>
  161. // 导入获取题目数据的API方法
  162. import {deleteQuestion, getQuestions} from '@/api/question.js'
  163. import axios from "axios";
  164. export default {
  165. name: 'QuestionTable',
  166. data() {
  167. return {
  168. items: [], // 从后端获取的原始题目数据
  169. showViewModal: false, // 是否显示查看弹窗
  170. currentQuestion: {}, // 当前查看的题目详情
  171. page: 1, // 当前页码
  172. pageSize: 20, // 每页显示条数
  173. total: 0, // 总记录数
  174. showEditModal: false, // 控制编辑弹窗显示
  175. editingQuestion: {}, // 正在编辑的题目
  176. showDeleteModal: false, // 控制删除确认对话框显示
  177. deleteId: null, // 要删除的题目ID
  178. }
  179. },
  180. computed: {
  181. // 计算总页数
  182. totalPages() {
  183. if (this.total === 0) {
  184. return 1;
  185. }
  186. return Math.ceil(this.total / this.pageSize);
  187. }
  188. },
  189. async mounted() {
  190. // 组件挂载时获取题目数据
  191. await this.fetchQuestions()
  192. },
  193. methods: {
  194. // 新增:外部设置数据的方法
  195. setData(data, resetPage = false) {
  196. this.items = data.list || data;
  197. this.total = data.total || (data.list ? data.list.length : data.length);
  198. if (resetPage || this.page > this.totalPages) {
  199. this.page = 1;
  200. }
  201. },
  202. // 获取题目数据
  203. async fetchQuestions() {
  204. try {
  205. // 构造请求参数
  206. const params = {
  207. Page: this.page,
  208. PageSize: this.pageSize
  209. }
  210. // 调用API获取数据
  211. const response = await getQuestions(params)
  212. // 处理成功响应
  213. if (response.data.code === 200) {
  214. this.items = response.data.data.list
  215. this.total = response.data.data.total !== 0 ? response.data.data.total : 1
  216. }
  217. } catch (error) {
  218. console.error('获取题目数据失败:', error)
  219. }
  220. },
  221. // 切换页面
  222. changePage(newPage) {
  223. if (newPage >= 1 && newPage <= this.totalPages) {
  224. this.page = newPage;
  225. // 发出分页变更事件,让父组件处理
  226. this.$emit('page-changed', newPage);
  227. }
  228. },
  229. // 跳转到指定页码
  230. jumpToPage(event) {
  231. const targetPage = parseInt(event.target.value);
  232. if (targetPage >= 1 && targetPage <= this.totalPages) {
  233. this.page = targetPage;
  234. // 发出分页变更事件,让父组件处理
  235. this.$emit('page-changed', targetPage);
  236. } else {
  237. // 重置输入框值
  238. event.target.value = this.page;
  239. }
  240. },
  241. // 查看题目详情
  242. async viewQuestion(item) {
  243. // 1. 滚动到对应行位置
  244. const row = document.querySelector(`[data-id="${item.id}"]`);
  245. if (row) {
  246. row.scrollIntoView({ behavior: 'smooth', block: 'center' });
  247. }
  248. // 2. 调用API获取单个题目的详细信息
  249. try {
  250. // 构造请求参数,只查询指定ID的题目
  251. const params = {
  252. Page: 1,
  253. PageSize: 100, // 增加页面大小以确保能获取到数据
  254. id: item.id
  255. };
  256. const response = await getQuestions(params);
  257. if (response.data.code === 200 && response.data.data.list && response.data.data.list.length > 0) {
  258. // 查找ID匹配的题目,而不是直接使用第一个
  259. const rawQuestion = response.data.data.list.find(question => question.id === item.id);
  260. if (!rawQuestion) {
  261. alert('未找到该题目!');
  262. return;
  263. }
  264. // 创建一个新的对象,将字段名映射为前端期望的格式
  265. this.currentQuestion = {
  266. id: rawQuestion.id,
  267. stem: rawQuestion.stem,
  268. optionA: rawQuestion.A,
  269. optionB: rawQuestion.B,
  270. optionC: rawQuestion.C,
  271. optionD: rawQuestion.D,
  272. correctAnswer: rawQuestion.correctAnswer,
  273. questionTypeName: rawQuestion.questionTypeName,
  274. CrName: rawQuestion.CrName
  275. };
  276. // 显示弹窗
  277. this.showViewModal = true;
  278. } else {
  279. alert('未找到该题目!');
  280. }
  281. } catch (error) {
  282. console.error('获取题目详情失败:', error);
  283. alert('网络错误,请检查连接!');
  284. }
  285. },
  286. // 关闭查看弹窗
  287. closeViewModal() {
  288. this.showViewModal = false;
  289. },
  290. // 编辑题目
  291. editQuestion(item) {
  292. // 将要编辑的题目数据填充到编辑表单中
  293. this.editingQuestion = {
  294. id: item.id,
  295. stem: item.stem,
  296. optionA: item['A'],
  297. optionB: item['B'],
  298. optionC: item['C'],
  299. optionD: item['D'],
  300. correctAnswer: item.correctAnswer,
  301. questionTypeName: item.questionTypeName,
  302. recommendedCourse: item.CrName
  303. };
  304. // 显示编辑弹窗
  305. this.showEditModal = true;
  306. },
  307. // 更新题目
  308. async updateQuestion() {
  309. // 表单验证
  310. if (!this.editingQuestion.stem || !this.editingQuestion.optionA ||
  311. !this.editingQuestion.optionB || !this.editingQuestion.optionC ||
  312. !this.editingQuestion.optionD || !this.editingQuestion.correctAnswer) {
  313. alert('请填写所有必填项!');
  314. return;
  315. }
  316. try {
  317. // 构造请求参数
  318. const params = new URLSearchParams();
  319. params.append('id', this.editingQuestion.id);
  320. params.append('stem', this.editingQuestion.stem);
  321. params.append('A', this.editingQuestion.optionA);
  322. params.append('B', this.editingQuestion.optionB);
  323. params.append('C', this.editingQuestion.optionC);
  324. params.append('D', this.editingQuestion.optionD);
  325. params.append('correct_answer', this.editingQuestion.correctAnswer);
  326. params.append('question_type_id', this.editingQuestion.questionTypeName === '股票知识' ? 1 : 2);
  327. params.append('course_recommendation_id',
  328. this.editingQuestion.recommendedCourse === '量能擒牛' ? 1 :
  329. this.editingQuestion.recommendedCourse === '价格破译' ? 2 : 3);
  330. // 发送请求
  331. const response = await axios.post('http://192.168.40.41:8000/admin/questions/update',params,
  332. {
  333. headers: {
  334. 'Content-Type': 'application/json'
  335. }
  336. });
  337. if (response.data.code === 200) {
  338. this.closeEditModal();
  339. // 刷新题目列表
  340. await this.fetchQuestions();
  341. alert('修改题目成功!');
  342. } else {
  343. alert('修改题目失败:' + response.data.msg);
  344. }
  345. } catch (error) {
  346. console.error('修改题目失败:', error);
  347. alert('网络错误,请检查连接!');
  348. }
  349. },
  350. // 关闭编辑弹窗
  351. closeEditModal() {
  352. this.showEditModal = false;
  353. this.editingQuestion = {};
  354. },
  355. // 删除题目(点击删除按钮)
  356. async deleteQuestion(item) {
  357. this.deleteId = item.id;
  358. this.showDeleteModal = true;
  359. },
  360. // 确认删除
  361. async confirmDelete() {
  362. try {
  363. const response = await deleteQuestion({ id: this.deleteId })
  364. if (response.data.code === 200) {
  365. this.closeDeleteModal()
  366. await this.fetchQuestions()
  367. alert('删除成功!')
  368. } else {
  369. alert('删除失败:' + response.data.msg)
  370. }
  371. } catch (error) {
  372. console.error('删除失败:', error)
  373. alert('网络错误,请检查连接!')
  374. }
  375. },
  376. // 关闭删除确认对话框
  377. closeDeleteModal() {
  378. this.showDeleteModal = false;
  379. this.deleteId = null;
  380. }
  381. }
  382. }
  383. </script>
  384. <style scoped>
  385. /* 修改操作列按钮间距 */
  386. td:last-child {
  387. display: flex;
  388. gap: 16px; /* 将间距设置为16px */
  389. }
  390. th {
  391. display: table-cell !important;
  392. vertical-align: middle !important;
  393. }
  394. .form-row-options {
  395. display: grid;
  396. grid-template-columns: 1fr 1fr;
  397. gap: 16px;
  398. margin-bottom: 16px;
  399. }
  400. .form-row {
  401. margin-bottom: 16px;
  402. }
  403. .form-row label {
  404. display: block;
  405. margin-bottom: 8px;
  406. font-weight: 500;
  407. color: #333;
  408. }
  409. .form-row select,
  410. .form-row input[type="text"],
  411. .form-row textarea {
  412. width: 100%;
  413. padding: 10px;
  414. border: 1px solid #ddd;
  415. border-radius: 4px;
  416. box-sizing: border-box;
  417. }
  418. .form-row textarea {
  419. resize: vertical;
  420. }
  421. .option-group {
  422. display: flex;
  423. flex-direction: column;
  424. }
  425. /* 响应式设计 */
  426. @media (max-width: 768px) {
  427. .modal-content {
  428. width: 90%;
  429. }
  430. .form-row-options {
  431. grid-template-columns: 1fr;
  432. }
  433. }
  434. /* 表格容器样式 */
  435. .table-container {
  436. width: 100%;
  437. border-collapse: collapse;
  438. margin-top: 10px;
  439. }
  440. /* 表格样式 */
  441. table {
  442. width: 100%;
  443. border-collapse: collapse;
  444. background-color: white;
  445. }
  446. /* 表格单元格样式 */
  447. th,
  448. td {
  449. padding: 12px;
  450. text-align: left;
  451. border-bottom: 1px solid #ddd;
  452. }
  453. /* 表头样式 */
  454. th {
  455. background-color: #f2f2f2;
  456. font-weight: normal;
  457. color: #333;
  458. }
  459. /* 表格行悬停效果 */
  460. tr:hover {
  461. background-color: #f9f9f9;
  462. }
  463. /* 弹窗样式 */
  464. .modal-overlay {
  465. position: fixed;
  466. top: 0;
  467. left: 0;
  468. right: 0;
  469. bottom: 0;
  470. background-color: rgba(0, 0, 0, 0.5);
  471. display: flex;
  472. justify-content: center;
  473. align-items: center;
  474. z-index: 1000;
  475. }
  476. /* 查看弹窗专用样式 */
  477. .view-modal-content {
  478. background-color: white;
  479. border-radius: 8px;
  480. width: 750px;
  481. max-width: 90%;
  482. box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
  483. overflow: hidden;
  484. }
  485. .view-modal-body {
  486. padding: 20px;
  487. max-height: 600px;
  488. overflow-y: auto;
  489. }
  490. /* 删除确认对话框样式 */
  491. .delete-modal-content {
  492. background-color: white;
  493. border-radius: 8px;
  494. width: 500px;
  495. max-width: 90%;
  496. box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
  497. overflow: hidden;
  498. }
  499. .delete-modal-content .modal-header {
  500. padding: 20px;
  501. border-bottom: 1px solid #eee;
  502. display: flex;
  503. justify-content: space-between;
  504. align-items: center;
  505. }
  506. .delete-modal-content .modal-header h3 {
  507. margin: 0;
  508. font-size: 18px;
  509. color: #333;
  510. }
  511. .delete-modal-content .close-btn {
  512. background: none;
  513. border: none;
  514. font-size: 24px;
  515. cursor: pointer;
  516. color: #666;
  517. padding: 5px;
  518. border-radius: 50%;
  519. transition: color 0.2s;
  520. }
  521. .delete-modal-content .close-btn:hover {
  522. color: #e74c3c;
  523. }
  524. .delete-modal-content .modal-footer {
  525. padding: 20px;
  526. border-top: 1px solid #eee;
  527. display: flex;
  528. justify-content: flex-end;
  529. gap: 16px;
  530. }
  531. .modal-content {
  532. background-color: white;
  533. border-radius: 8px;
  534. width: 600px;
  535. height: 760px;
  536. max-width: 90%;
  537. box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
  538. overflow: hidden;
  539. }
  540. .modal-header {
  541. padding: 20px;
  542. border-bottom: 1px solid #eee;
  543. display: flex;
  544. justify-content: space-between;
  545. align-items: center;
  546. }
  547. .modal-header h3 {
  548. margin: 0;
  549. font-size: 18px;
  550. color: #333;
  551. }
  552. .close-btn {
  553. background: none;
  554. border: none;
  555. font-size: 24px;
  556. cursor: pointer;
  557. color: #666;
  558. padding: 5px;
  559. border-radius: 50%;
  560. transition: color 0.2s;
  561. }
  562. .close-btn:hover {
  563. color: #e74c3c;
  564. }
  565. .modal-body {
  566. padding: 20px;
  567. max-height: 600px;
  568. overflow-y: auto;
  569. }
  570. .question-text {
  571. font-size: 16px;
  572. margin-bottom: 20px;
  573. line-height: 1.5;
  574. }
  575. .options-container {
  576. display: flex;
  577. flex-direction: column;
  578. gap: 10px;
  579. }
  580. .option-item {
  581. padding: 10px;
  582. border-radius: 6px;
  583. background-color: #f8d7da;
  584. color: #721c24;
  585. border: 1px solid #f5c6cb;
  586. transition: all 0.2s;
  587. }
  588. .option-item-correct {
  589. padding: 10px;
  590. border-radius: 6px;
  591. background-color: #f8d7da;
  592. color: #721c24;
  593. border: 1px solid #f5c6cb;
  594. transition: all 0.2s;
  595. background-color: #dc3545 !important;
  596. color: white !important;
  597. border-color: #c82333 !important;
  598. font-weight: bold;
  599. }
  600. .modal-footer {
  601. padding: 20px;
  602. border-top: 1px solid #eee;
  603. display: flex;
  604. justify-content: flex-end;
  605. gap: 16px;
  606. }
  607. /* 分页样式 */
  608. .pagination-container {
  609. display: flex;
  610. justify-content: flex-start;
  611. align-items: center;
  612. margin-top: 20px;
  613. padding: 0 10px;
  614. }
  615. .pagination-info {
  616. font-size: 14px;
  617. color: #666;
  618. }
  619. .pagination-controls {
  620. display: flex;
  621. align-items: center;
  622. gap: 10px;
  623. margin-right: 20px;
  624. }
  625. .btn-pagination {
  626. padding: 6px 12px;
  627. background-color: #f2f2f2;
  628. border: 1px solid #ddd;
  629. border-radius: 4px;
  630. cursor: pointer;
  631. font-size: 14px;
  632. transition: all 0.2s;
  633. }
  634. .btn-pagination:hover:not(:disabled) {
  635. background-color: #e0e0e0;
  636. }
  637. .btn-pagination:disabled {
  638. background-color: #f5f5f5;
  639. color: #ccc;
  640. cursor: not-allowed;
  641. }
  642. .page-input {
  643. width: 50px;
  644. padding: 6px;
  645. border: 1px solid #ddd;
  646. border-radius: 4px;
  647. text-align: center;
  648. font-size: 14px;
  649. }
  650. .page-info {
  651. font-size: 14px;
  652. color: #666;
  653. }
  654. .footer-btn {
  655. position: fixed;
  656. bottom: 20px;
  657. right: 20px;
  658. z-index: 1000;
  659. }
  660. </style>