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.
534 lines
12 KiB
534 lines
12 KiB
<!-- src/components/WrongQuestion/WrongQuestionTable.vue -->
|
|
<template>
|
|
<div class="table-container">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>题干</th>
|
|
<th>题目类型</th>
|
|
<th>出错次数</th>
|
|
<th>出错率</th>
|
|
<th>推荐课程</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(item,index) in wrongQuestions" :key="item.id">
|
|
<td>{{ (page- 1) * pageSize + index + 1 }}</td>
|
|
<td>{{ item.stem }}</td>
|
|
<td>{{ item.questionTypeName }}</td>
|
|
<td>{{ item.errorCount }}</td>
|
|
<td>{{ item.errorRate }}%</td>
|
|
<td>{{ item.CrName }}</td>
|
|
<td class="operation-cell">
|
|
<button class="btn-red small" @click="viewUser(item)">出错用户</button>
|
|
<button class="btn-red small" @click="viewQuestion(item)">查看题目</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- 分页控件 -->
|
|
<div class="pagination-container">
|
|
<div class="pagination-info">
|
|
共 {{ total }} 条记录,第 {{ page }} 页
|
|
</div>
|
|
<div class="pagination-controls">
|
|
<button
|
|
class="btn-pagination"
|
|
:disabled="page <= 1"
|
|
@click="changePage(1)"
|
|
>
|
|
首页
|
|
</button>
|
|
<button
|
|
class="btn-pagination"
|
|
:disabled="page <= 1"
|
|
@click="changePage(page - 1)"
|
|
>
|
|
上一页
|
|
</button>
|
|
|
|
<input
|
|
type="number"
|
|
class="page-input"
|
|
:value="page"
|
|
@keyup.enter="jumpToPage"
|
|
min="1"
|
|
:max="totalPages"
|
|
/>
|
|
|
|
<span class="page-info">/ {{ totalPages }}</span>
|
|
|
|
<button
|
|
class="btn-pagination"
|
|
:disabled="page >= totalPages"
|
|
@click="changePage(page + 1)"
|
|
>
|
|
下一页
|
|
</button>
|
|
<button
|
|
class="btn-pagination"
|
|
:disabled="page >= totalPages"
|
|
@click="changePage(totalPages)"
|
|
>
|
|
尾页
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 用户弹窗 -->
|
|
<div v-if="showUserModal" class="modal-overlay">
|
|
<div class="modal-content">
|
|
<h3>出错用户列表</h3>
|
|
<table class="user-table">
|
|
<thead>
|
|
<tr>
|
|
<th>用户名称</th>
|
|
<th>用户身份</th>
|
|
<th>出错次数</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="user in errorUsers" :key="user.user_name">
|
|
<td>{{ user.user_name }}</td>
|
|
<td>{{ user.user_identity }}</td>
|
|
<td>{{ user.error_count }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<button class="btn-close" @click="closeModal">关闭</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 查看题目详情弹窗 -->
|
|
<div v-if="showViewModal" class="modal-overlay" @click.self="closeViewModal">
|
|
<div class="view-modal-content" @click.stop>
|
|
<div class="modal-header">
|
|
<h3>题目详情</h3>
|
|
<button class="close-btn" @click="closeViewModal">×</button>
|
|
</div>
|
|
<div class="view-modal-body">
|
|
<p class="question-text">{{ currentQuestion.stem }}</p>
|
|
|
|
<div class="options-container">
|
|
<div
|
|
v-for="option in ['A', 'B', 'C', 'D']"
|
|
:key="option"
|
|
:class="[option === currentQuestion.correctAnswer ? 'option-item-correct' : 'option-item']"
|
|
>
|
|
{{ `${option}. ${currentQuestion[`option${option}`]}` }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn-red" @click="closeViewModal">退出</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { getQuestions } from '@/api/question.js';
|
|
|
|
export default {
|
|
name: 'WrongQuestionTable',
|
|
data() {
|
|
return {
|
|
wrongQuestions: [],
|
|
total: 0,
|
|
page: 1,
|
|
pageSize: 20,
|
|
showUserModal: false,
|
|
errorUsers: [],
|
|
showViewModal: false,
|
|
currentQuestion: {},
|
|
currentFilters: {}
|
|
}
|
|
},
|
|
computed: {
|
|
totalPages() {
|
|
try {
|
|
return this.total !== 0 ? Math.ceil(this.total / this.pageSize) : 1;
|
|
} catch(error) {
|
|
console.error('计算总页数时出错:', error);
|
|
return 1;
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
setFilters(filters) {
|
|
this.currentFilters = filters;
|
|
this.page = 1;
|
|
this.fetchWrongQuestions();
|
|
},
|
|
|
|
async fetchWrongQuestions() {
|
|
try {
|
|
const params = new URLSearchParams();
|
|
params.append('page', this.page);
|
|
params.append('page_size', this.pageSize);
|
|
|
|
// 处理过滤条件
|
|
const questionTypeIdMap = {
|
|
'股票知识': 1,
|
|
'企业文化': 2
|
|
};
|
|
if (this.currentFilters.questionType) {
|
|
params.append('question_type_id', questionTypeIdMap[this.currentFilters.questionType]);
|
|
}
|
|
|
|
const courseRecommendationIdMap = {
|
|
'量能擒牛': 1,
|
|
'价格破译': 2,
|
|
'量价时空综合': 3
|
|
};
|
|
if (this.currentFilters.course) {
|
|
params.append('course_recommendation_id', courseRecommendationIdMap[this.currentFilters.course]);
|
|
}
|
|
|
|
if (this.currentFilters.keyword) {
|
|
params.append('stem', this.currentFilters.keyword);
|
|
}
|
|
|
|
const response = await getQuestions(params);
|
|
|
|
if (response.data.code === 200) {
|
|
this.wrongQuestions = response.data.data.list
|
|
this.total = response.data.data.total;
|
|
} else {
|
|
console.error('接口返回错误:', response.data.msg)
|
|
}
|
|
} catch (error) {
|
|
console.error('获取错题数据失败:', error)
|
|
}
|
|
},
|
|
|
|
viewUser(item) {
|
|
this.$emit('view-user', item)
|
|
this.fetchErrorUsers(item.id)
|
|
},
|
|
|
|
async fetchErrorUsers(questionId) {
|
|
try {
|
|
const { getUsersByQuestionId } = await import('@/api/wrongQuestion')
|
|
const response = await getUsersByQuestionId(questionId)
|
|
|
|
if (response.data.code === 200) {
|
|
this.errorUsers = response.data.data.list || []
|
|
this.showUserModal = true
|
|
} else {
|
|
console.error('获取用户失败:', response.data.msg)
|
|
}
|
|
} catch (error) {
|
|
console.error('请求失败:', error)
|
|
}
|
|
},
|
|
|
|
async viewQuestion(item) {
|
|
const row = document.querySelector(`[data-id="${item.id}"]`)
|
|
if (row) {
|
|
row.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
}
|
|
|
|
try {
|
|
// 使用统一的API调用方式
|
|
const params = {
|
|
Page: 1,
|
|
PageSize: 100,
|
|
id: item.id
|
|
}
|
|
|
|
const response = await getQuestions(params)
|
|
|
|
if (response.data.code === 200 && response.data.data.list && response.data.data.list.length > 0) {
|
|
const rawQuestion = response.data.data.list.find(q => q.id === item.id)
|
|
|
|
if (!rawQuestion) {
|
|
alert('未找到该题目!')
|
|
return
|
|
}
|
|
|
|
this.currentQuestion = {
|
|
id: rawQuestion.id,
|
|
stem: rawQuestion.stem,
|
|
optionA: rawQuestion.A,
|
|
optionB: rawQuestion.B,
|
|
optionC: rawQuestion.C,
|
|
optionD: rawQuestion.D,
|
|
correctAnswer: rawQuestion.correctAnswer,
|
|
questionTypeName: rawQuestion.questionTypeName,
|
|
CrName: rawQuestion.CrName
|
|
}
|
|
|
|
this.showViewModal = true
|
|
} else {
|
|
alert('未找到该题目!')
|
|
}
|
|
} catch (error) {
|
|
console.error('获取题目详情失败:', error)
|
|
alert('网络错误,请检查连接!')
|
|
}
|
|
},
|
|
|
|
closeViewModal() {
|
|
this.showViewModal = false
|
|
},
|
|
|
|
changePage(newPage) {
|
|
if (newPage >= 1 && newPage <= this.totalPages) {
|
|
this.page = newPage;
|
|
this.fetchWrongQuestions();
|
|
}
|
|
},
|
|
|
|
jumpToPage(event) {
|
|
const targetPage = parseInt(event.target.value)
|
|
if (targetPage >= 1 && targetPage <= this.totalPages) {
|
|
this.page = targetPage;
|
|
this.fetchWrongQuestions();
|
|
} else {
|
|
event.target.value = this.page
|
|
}
|
|
},
|
|
|
|
closeModal() {
|
|
this.showUserModal = false
|
|
this.errorUsers = []
|
|
}
|
|
},
|
|
mounted() {
|
|
this.fetchWrongQuestions();
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.table-container {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
background-color: white;
|
|
}
|
|
|
|
th,td {
|
|
padding: 12px;
|
|
text-align: left;
|
|
border-bottom: 1px solid #ddd;
|
|
}
|
|
|
|
th {
|
|
background-color: #f2f2f2;
|
|
font-weight: normal;
|
|
color: #333;
|
|
display: table-cell !important;
|
|
vertical-align: middle !important;
|
|
}
|
|
|
|
tr:hover {
|
|
background-color: #f9f9f9;
|
|
}
|
|
|
|
.operation-cell {
|
|
display: flex;
|
|
gap: 16px;
|
|
}
|
|
|
|
/* 分页样式 */
|
|
.pagination-container {
|
|
display: flex;
|
|
justify-content: flex-start;
|
|
align-items: center;
|
|
margin-top: 20px;
|
|
padding: 0 10px;
|
|
}
|
|
|
|
.pagination-info {
|
|
font-size: 14px;
|
|
color: #666;
|
|
margin-right: 20px;
|
|
}
|
|
|
|
.pagination-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.btn-pagination {
|
|
padding: 6px 12px;
|
|
background-color: #f2f2f2;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.btn-pagination:hover:not(:disabled) {
|
|
background-color: #e0e0e0;
|
|
}
|
|
|
|
.btn-pagination:disabled {
|
|
background-color: #f5f5f5;
|
|
color: #ccc;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.page-input {
|
|
width: 50px;
|
|
padding: 6px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
text-align: center;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.page-info {
|
|
font-size: 14px;
|
|
color: #666;
|
|
}
|
|
|
|
/* 弹窗样式 */
|
|
.modal-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.modal-content {
|
|
background: white;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
width: 600px;
|
|
max-width: 90%;
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.view-modal-content {
|
|
background-color: white;
|
|
border-radius: 8px;
|
|
width: 750px;
|
|
max-width: 90%;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.view-modal-body {
|
|
padding: 20px;
|
|
max-height: 600px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.question-text {
|
|
font-size: 16px;
|
|
margin-bottom: 20px;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.options-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.option-item {
|
|
padding: 10px;
|
|
border-radius: 6px;
|
|
background-color: #f8d7da;
|
|
color: #721c24;
|
|
border: 1px solid #f5c6cb;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.option-item-correct {
|
|
padding: 10px;
|
|
border-radius: 6px;
|
|
background-color: #f8d7da;
|
|
color: #721c24;
|
|
border: 1px solid #f5c6cb;
|
|
transition: all 0.2s;
|
|
background-color: #dc3545 !important;
|
|
color: white !important;
|
|
border-color: #c82333 !important;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.modal-header {
|
|
padding: 20px;
|
|
border-bottom: 1px solid #eee;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.modal-header h3 {
|
|
margin: 0;
|
|
font-size: 18px;
|
|
color: #333;
|
|
}
|
|
|
|
.close-btn {
|
|
background: none;
|
|
border: none;
|
|
font-size: 24px;
|
|
cursor: pointer;
|
|
color: #666;
|
|
padding: 5px;
|
|
border-radius: 50%;
|
|
transition: color 0.2s;
|
|
}
|
|
|
|
.close-btn:hover {
|
|
color: #e74c3c;
|
|
}
|
|
|
|
.modal-footer {
|
|
padding: 20px;
|
|
border-top: 1px solid #eee;
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 16px;
|
|
}
|
|
|
|
.user-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.user-table th,
|
|
.user-table td {
|
|
padding: 10px;
|
|
text-align: left;
|
|
border-bottom: 1px solid #ddd;
|
|
}
|
|
|
|
.user-table th {
|
|
background-color: #f2f2f2;
|
|
}
|
|
|
|
.btn-close {
|
|
margin-top: 15px;
|
|
padding: 8px 16px;
|
|
background-color: #e74c3c;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
}
|
|
</style>
|