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

<!-- 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>