|
|
|
@ -1,122 +1,310 @@ |
|
|
|
<!-- src/components/Question/QuestionTable.vue --> |
|
|
|
<template> |
|
|
|
<!-- 表格容器 --> |
|
|
|
<div class="table-container"> |
|
|
|
<!-- 题目数据表格 --> |
|
|
|
<table> |
|
|
|
<!-- 表头部分 --> |
|
|
|
<thead> |
|
|
|
<tr> |
|
|
|
<th>ID</th> |
|
|
|
<th>题干</th> |
|
|
|
<th>题目类型</th> |
|
|
|
<th @click="sort('errorCount')" class="sortable"> |
|
|
|
<div class="sort-header"> |
|
|
|
出错次数 |
|
|
|
<span v-if="sortField === 'errorCount'" class="sort-icon"> |
|
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> |
|
|
|
<path d="M7 10L12 15L17 10" stroke="#e74c3c" stroke-width="2" /> |
|
|
|
<path d="M12 15V3" stroke="#e74c3c" stroke-width="2" /> |
|
|
|
</svg> |
|
|
|
</span> |
|
|
|
<div class="question-table-container"> |
|
|
|
<!-- 表格容器 --> |
|
|
|
<div class="table-container"> |
|
|
|
<!-- 题目数据表格 --> |
|
|
|
<table> |
|
|
|
<!-- 表头部分 --> |
|
|
|
<thead> |
|
|
|
<tr> |
|
|
|
<!-- ID列,支持排序 --> |
|
|
|
<th @click="sort('id')" class="sortable"> |
|
|
|
<div class="sort-header"> |
|
|
|
ID |
|
|
|
<!-- 排序图标,当按照ID排序时显示 --> |
|
|
|
<span v-if="sortField === 'id'" class="sort-icon"> |
|
|
|
<svg :style="{ transform: sortDirection === 'desc' ? 'rotate(180deg)' : 'rotate(0deg)' }" |
|
|
|
width="12" height="12" viewBox="0 0 24 24" fill="none"> |
|
|
|
<path d="M7 10L12 15L17 10" stroke="#e74c3c" stroke-width="2" /> |
|
|
|
<path d="M12 15V3" stroke="#e74c3c" stroke-width="2" /> |
|
|
|
</svg> |
|
|
|
</span> |
|
|
|
</div> |
|
|
|
</th> |
|
|
|
|
|
|
|
<!-- 题干列 --> |
|
|
|
<th>题干</th> |
|
|
|
<!-- 题目类型列 --> |
|
|
|
<th>题目类型</th> |
|
|
|
|
|
|
|
<!-- 出错次数列,支持排序 --> |
|
|
|
<th @click="sort('errorCount')" class="sortable"> |
|
|
|
<div class="sort-header"> |
|
|
|
出错次数 |
|
|
|
<!-- 排序图标,当按照出错次数排序时显示 --> |
|
|
|
<span v-if="sortField === 'errorCount'" class="sort-icon"> |
|
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> |
|
|
|
<path d="M7 10L12 15L17 10" stroke="#e74c3c" stroke-width="2" /> |
|
|
|
<path d="M12 15V3" stroke="#e74c3c" stroke-width="2" /> |
|
|
|
</svg> |
|
|
|
</span> |
|
|
|
</div> |
|
|
|
</th> |
|
|
|
|
|
|
|
<!-- 出错率列,支持排序 --> |
|
|
|
<th @click="sort('errorRate')" class="sortable"> |
|
|
|
<div class="sort-header"> |
|
|
|
出错率 |
|
|
|
<!-- 排序图标,当按照出错率排序时显示 --> |
|
|
|
<span v-if="sortField === 'errorRate'" class="sort-icon"> |
|
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> |
|
|
|
<path d="M7 10L12 15L17 10" stroke="#e74c3c" stroke-width="2" /> |
|
|
|
<path d="M12 15V3" stroke="#e74c3c" stroke-width="2" /> |
|
|
|
</svg> |
|
|
|
</span> |
|
|
|
</div> |
|
|
|
</th> |
|
|
|
|
|
|
|
<!-- 推荐系列列 --> |
|
|
|
<th>推荐系列</th> |
|
|
|
<!-- 操作列 --> |
|
|
|
<th>操作</th> |
|
|
|
</tr> |
|
|
|
</thead> |
|
|
|
<!-- 表格主体部分 --> |
|
|
|
<tbody> |
|
|
|
<!-- 数据行,遍历sortedItems数组渲染每一条题目数据 --> |
|
|
|
<tr v-for="item in sortedItems" :key="item.id" :data-id="item.id"> |
|
|
|
<td>{{ item.id }}</td> |
|
|
|
<td>{{ item.stem }}</td> |
|
|
|
<td>{{ item.questionTypeName }}</td> |
|
|
|
<td>{{ item.errorCount }}</td> |
|
|
|
<td>{{ item.errorRate }}%</td> |
|
|
|
<td>{{ item.CrName }}</td> |
|
|
|
<!-- 操作按钮 --> |
|
|
|
<td> |
|
|
|
<button class="btn-red small" @click="viewQuestion(item)">查看</button> |
|
|
|
<button class="btn-red small" @click="editQuestion(item)">修改</button> |
|
|
|
<button class="btn-red small" @click="deleteQuestion(item)">删除</button> |
|
|
|
</td> |
|
|
|
</tr> |
|
|
|
</tbody> |
|
|
|
</table> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 分页控件 --> |
|
|
|
<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="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"> |
|
|
|
<!-- 遍历ABCD四个选项 --> |
|
|
|
<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 v-if="showEditModal" class="modal-overlay" @click.self="closeEditModal"> |
|
|
|
<div class="modal-content" @click.stop> |
|
|
|
<div class="modal-header"> |
|
|
|
<h3>编辑题目</h3> |
|
|
|
<button class="close-btn" @click="closeEditModal">×</button> |
|
|
|
</div> |
|
|
|
<div class="modal-body"> |
|
|
|
<div class="form-row"> |
|
|
|
<label>题目类型</label> |
|
|
|
<select v-model="editingQuestion.questionTypeName"> |
|
|
|
<option value="股票知识">股票知识</option> |
|
|
|
<option value="企业文化">企业文化</option> |
|
|
|
</select> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="form-row"> |
|
|
|
<label>题干</label> |
|
|
|
<textarea v-model="editingQuestion.stem" placeholder="请输入题目内容" rows="4" style="width: 545px; height: 120px;" |
|
|
|
></textarea> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="form-row-options"> |
|
|
|
<div class="option-group"> |
|
|
|
<label>选项A</label> |
|
|
|
<input |
|
|
|
type="text" |
|
|
|
v-model="editingQuestion.optionA" |
|
|
|
placeholder="请输入选项A" style="width: 280px; height: 40px;" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
<div class="option-group"> |
|
|
|
<label>选项B</label> |
|
|
|
<input type="text" v-model="editingQuestion.optionB" placeholder="请输入选项B" style="width: 280px; height: 40px;"/> |
|
|
|
</div> |
|
|
|
<div class="option-group"> |
|
|
|
<label>选项C</label> |
|
|
|
<input type="text" v-model="editingQuestion.optionC" placeholder="请输入选项C" style="width: 280px; height: 40px;"/> |
|
|
|
</div> |
|
|
|
<div class="option-group"> |
|
|
|
<label>选项D</label> |
|
|
|
<input type="text" v-model="editingQuestion.optionD" placeholder="请输入选项D" style="width: 280px; height: 40px;"/> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="form-row"> |
|
|
|
<label>正确答案</label> |
|
|
|
<select v-model="editingQuestion.correctAnswer"> |
|
|
|
<option value="A">A</option> |
|
|
|
<option value="B">B</option> |
|
|
|
<option value="C">C</option> |
|
|
|
<option value="D">D</option> |
|
|
|
</select> |
|
|
|
</div> |
|
|
|
</th> |
|
|
|
<th @click="sort('errorRate')" class="sortable"> |
|
|
|
<div class="sort-header"> |
|
|
|
出错率 |
|
|
|
<span v-if="sortField === 'errorRate'" class="sort-icon"> |
|
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> |
|
|
|
<path d="M7 10L12 15L17 10" stroke="#e74c3c" stroke-width="2" /> |
|
|
|
<path d="M12 15V3" stroke="#e74c3c" stroke-width="2" /> |
|
|
|
</svg> |
|
|
|
</span> |
|
|
|
|
|
|
|
<div class="form-row"> |
|
|
|
<label>推荐课程</label> |
|
|
|
<select v-model="editingQuestion.recommendedCourse"> |
|
|
|
<option value="">请选择</option> |
|
|
|
<option value="量能擒牛">量能擒牛</option> |
|
|
|
<option value="价格破译">价格破译</option> |
|
|
|
<option value="量价时空综合">量价时空综合</option> |
|
|
|
</select> |
|
|
|
</div> |
|
|
|
</th> |
|
|
|
|
|
|
|
<th>推荐课程</th> |
|
|
|
<th>操作</th> |
|
|
|
</tr> |
|
|
|
</thead> |
|
|
|
<!-- 表格主体部分 --> |
|
|
|
<tbody> |
|
|
|
<!-- 示例数据行 --> |
|
|
|
<tr v-for="item in sortedItems" :key="item.id"> |
|
|
|
<td>{{ item.id }}</td> |
|
|
|
<td>{{ item.questionText }}</td> |
|
|
|
<td>{{ item.type }}</td> |
|
|
|
<td>{{ item.errorCount }}</td> |
|
|
|
<td>{{ item.errorRate }}</td> |
|
|
|
<td>{{ item.recommendedCourse }}</td> |
|
|
|
<td> |
|
|
|
<button class="btn-red small">查看</button> |
|
|
|
<button class="btn-red small">修改</button> |
|
|
|
<button class="btn-red small">删除</button> |
|
|
|
</td> |
|
|
|
</tr> |
|
|
|
</tbody> |
|
|
|
</table> |
|
|
|
</div> |
|
|
|
<div class="modal-footer"> |
|
|
|
<button class="btn-red" @click="updateQuestion">确定</button> |
|
|
|
<button class="btn-red" @click="closeEditModal">取消</button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 删除确认对话框 --> |
|
|
|
<div v-if="showDeleteModal" class="modal-overlay" @click.self="closeDeleteModal"> |
|
|
|
<div class="delete-modal-content" @click.stop> |
|
|
|
<div class="modal-header"> |
|
|
|
<h3>您确定要删除吗</h3> |
|
|
|
<button class="close-btn" @click="closeDeleteModal">×</button> |
|
|
|
</div> |
|
|
|
<div class="modal-footer"> |
|
|
|
<button class="btn-red" @click="confirmDelete">确定</button> |
|
|
|
<button class="btn-red" @click="closeDeleteModal">取消</button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
</div> |
|
|
|
</template> |
|
|
|
|
|
|
|
|
|
|
|
<script> |
|
|
|
// 导入获取题目数据的API方法 |
|
|
|
import {deleteQuestion, getQuestions} from '@/api/question.js' |
|
|
|
import axios from "axios"; |
|
|
|
|
|
|
|
export default { |
|
|
|
name: 'QuestionTable', |
|
|
|
data() { |
|
|
|
return { |
|
|
|
sortField: '', // 当前排序字段 |
|
|
|
sortDirection: 'asc', // 排序方向:asc 或 desc |
|
|
|
items: [ |
|
|
|
{ |
|
|
|
id: 1, |
|
|
|
questionText: '以下哪项不是股票的基本特征?', |
|
|
|
type: '股票知识', |
|
|
|
errorCount: 50, |
|
|
|
errorRate: '50%', |
|
|
|
recommendedCourse: '量能擒牛' |
|
|
|
}, |
|
|
|
{ |
|
|
|
id: 2, |
|
|
|
questionText: '基金的风险主要来源于?', |
|
|
|
type: '基金知识', |
|
|
|
errorCount: 30, |
|
|
|
errorRate: '30%', |
|
|
|
recommendedCourse: '基本面分析' |
|
|
|
} |
|
|
|
], |
|
|
|
sortedItems: [] // 用于存储排序后的结果 |
|
|
|
sortField: '', // 当前排序字段 |
|
|
|
sortDirection: 'asc', // 排序方向(升序/降序) |
|
|
|
items: [], // 从后端获取的原始题目数据 |
|
|
|
sortedItems: [], // 排序后的题目数据 |
|
|
|
showViewModal: false, // 是否显示查看弹窗 |
|
|
|
currentQuestion: {}, // 当前查看的题目详情 |
|
|
|
page: 1, // 当前页码 |
|
|
|
pageSize: 20, // 每页显示条数 |
|
|
|
total: 0 , // 总记录数 |
|
|
|
showEditModal: false, // 控制编辑弹窗显示 |
|
|
|
editingQuestion: {}, // 正在编辑的题目 |
|
|
|
showDeleteModal: false, // 控制删除确认对话框显示 |
|
|
|
deleteId: null, // 要删除的题目ID |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
computed: { |
|
|
|
// 计算总页数 |
|
|
|
totalPages() { |
|
|
|
return Math.ceil(this.total / this.pageSize); |
|
|
|
} |
|
|
|
}, |
|
|
|
// computed: { |
|
|
|
// sortedItems() { |
|
|
|
// if (!this.sortField) { |
|
|
|
// // 默认按 ID 升序 |
|
|
|
// return this.items.sort((a, b) => a.id - b.id) |
|
|
|
// } |
|
|
|
// |
|
|
|
// return this.items.sort((a, b) => { |
|
|
|
// const aValue = a[this.sortField] |
|
|
|
// const bValue = b[this.sortField] |
|
|
|
// |
|
|
|
// // 处理数字和字符串 |
|
|
|
// if (typeof aValue === 'number' && typeof bValue === 'number') { |
|
|
|
// return this.sortDirection === 'asc' ? aValue - bValue : bValue - aValue |
|
|
|
// } else { |
|
|
|
// const strA = String(aValue).toLowerCase() |
|
|
|
// const strB = String(bValue).toLowerCase() |
|
|
|
// return this.sortDirection === 'asc' |
|
|
|
// ? strA.localeCompare(strB) |
|
|
|
// : strB.localeCompare(strA) |
|
|
|
// } |
|
|
|
// }) |
|
|
|
// } |
|
|
|
// }, |
|
|
|
|
|
|
|
async mounted() { |
|
|
|
// 组件挂载时获取题目数据 |
|
|
|
await this.fetchQuestions() |
|
|
|
}, |
|
|
|
|
|
|
|
watch: { |
|
|
|
// 监听排序字段变化,更新排序结果 |
|
|
|
sortField: { |
|
|
|
handler() { |
|
|
|
this.updateSortedItems() |
|
|
|
}, |
|
|
|
immediate: true // 初始时执行一次 |
|
|
|
immediate: true |
|
|
|
}, |
|
|
|
// 监听排序方向变化,更新排序结果 |
|
|
|
sortDirection: { |
|
|
|
handler() { |
|
|
|
this.updateSortedItems() |
|
|
|
@ -126,19 +314,55 @@ export default { |
|
|
|
}, |
|
|
|
|
|
|
|
methods: { |
|
|
|
// 新增:外部设置数据的方法 |
|
|
|
// 修改setData方法,同时设置分页信息 |
|
|
|
setData(data) { |
|
|
|
this.items = data.list || data; |
|
|
|
this.total = data.total || (data.list ? data.list.length : data.length); |
|
|
|
this.updateSortedItems(); |
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
// 获取题目数据 |
|
|
|
async fetchQuestions() { |
|
|
|
try { |
|
|
|
// 构造请求参数 |
|
|
|
const params = { |
|
|
|
Page: this.page, |
|
|
|
PageSize: this.pageSize |
|
|
|
} |
|
|
|
// 调用API获取数据 |
|
|
|
const response = await getQuestions(params) |
|
|
|
|
|
|
|
// 处理成功响应 |
|
|
|
if (response.data.code === 200) { |
|
|
|
this.items = response.data.data.list |
|
|
|
this.total = response.data.data.total || 0 |
|
|
|
this.updateSortedItems() |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error('获取题目数据失败:', error) |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
// 更新排序后的数据 |
|
|
|
updateSortedItems() { |
|
|
|
// 如果没有指定排序字段,则按ID升序排列 |
|
|
|
if (!this.sortField) { |
|
|
|
this.sortedItems = [...this.items].sort((a, b) => a.id - b.id) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 根据当前排序字段和方向进行排序 |
|
|
|
const sorted = [...this.items].sort((a, b) => { |
|
|
|
const aValue = a[this.sortField] |
|
|
|
const bValue = b[this.sortField] |
|
|
|
|
|
|
|
// 数字类型排序 |
|
|
|
if (typeof aValue === 'number' && typeof bValue === 'number') { |
|
|
|
return this.sortDirection === 'asc' ? aValue - bValue : bValue - aValue |
|
|
|
} else { |
|
|
|
// 字符串类型排序 |
|
|
|
const strA = String(aValue).toLowerCase() |
|
|
|
const strB = String(bValue).toLowerCase() |
|
|
|
return this.sortDirection === 'asc' |
|
|
|
@ -149,22 +373,205 @@ export default { |
|
|
|
|
|
|
|
this.sortedItems = sorted |
|
|
|
}, |
|
|
|
|
|
|
|
// 处理列排序点击事件 |
|
|
|
sort(field) { |
|
|
|
if (this.sortField === field) { |
|
|
|
// 切换排序方向 |
|
|
|
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc' |
|
|
|
} else { |
|
|
|
// 设置新的排序字段,默认升序 |
|
|
|
this.sortField = field |
|
|
|
this.sortDirection = 'asc' |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
// 切换页面 |
|
|
|
// 修改changePage方法 |
|
|
|
changePage(newPage) { |
|
|
|
if (newPage >= 1 && newPage <= this.totalPages) { |
|
|
|
this.page = newPage; |
|
|
|
// 发出分页变更事件,让父组件处理 |
|
|
|
this.$emit('page-changed', newPage); |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
// 跳转到指定页码 |
|
|
|
// 修改jumpToPage方法 |
|
|
|
jumpToPage(event) { |
|
|
|
const targetPage = parseInt(event.target.value); |
|
|
|
if (targetPage >= 1 && targetPage <= this.totalPages) { |
|
|
|
this.page = targetPage; |
|
|
|
// 发出分页变更事件,让父组件处理 |
|
|
|
this.$emit('page-changed', targetPage); |
|
|
|
} else { |
|
|
|
// 重置输入框值 |
|
|
|
event.target.value = this.page; |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
// 查看题目详情 |
|
|
|
async viewQuestion(item) { |
|
|
|
// 1. 滚动到对应行位置 |
|
|
|
const row = document.querySelector(`[data-id="${item.id}"]`); |
|
|
|
if (row) { |
|
|
|
row.scrollIntoView({ behavior: 'smooth', block: 'center' }); |
|
|
|
} |
|
|
|
|
|
|
|
// 2. 调用API获取单个题目的详细信息 |
|
|
|
try { |
|
|
|
// 构造请求参数,只查询指定ID的题目 |
|
|
|
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) { |
|
|
|
// 查找ID匹配的题目,而不是直接使用第一个 |
|
|
|
const rawQuestion = response.data.data.list.find(question => question.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; |
|
|
|
}, |
|
|
|
|
|
|
|
// 编辑题目 |
|
|
|
editQuestion(item) { |
|
|
|
// 将要编辑的题目数据填充到编辑表单中 |
|
|
|
this.editingQuestion = { |
|
|
|
id: item.id, |
|
|
|
stem: item.stem, |
|
|
|
optionA: item['A'], |
|
|
|
optionB: item['B'], |
|
|
|
optionC: item['C'], |
|
|
|
optionD: item['D'], |
|
|
|
correctAnswer: item.correctAnswer, |
|
|
|
questionTypeName: item.questionTypeName, |
|
|
|
recommendedCourse: item.CrName |
|
|
|
}; |
|
|
|
|
|
|
|
// 显示编辑弹窗 |
|
|
|
this.showEditModal = true; |
|
|
|
}, |
|
|
|
|
|
|
|
// 更新题目 |
|
|
|
async updateQuestion() { |
|
|
|
// 表单验证 |
|
|
|
if (!this.editingQuestion.stem || !this.editingQuestion.optionA || |
|
|
|
!this.editingQuestion.optionB || !this.editingQuestion.optionC || |
|
|
|
!this.editingQuestion.optionD || !this.editingQuestion.correctAnswer) { |
|
|
|
alert('请填写所有必填项!'); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
try { |
|
|
|
// 构造请求参数 |
|
|
|
const params = new URLSearchParams(); |
|
|
|
params.append('id', this.editingQuestion.id); |
|
|
|
params.append('stem', this.editingQuestion.stem); |
|
|
|
params.append('A', this.editingQuestion.optionA); |
|
|
|
params.append('B', this.editingQuestion.optionB); |
|
|
|
params.append('C', this.editingQuestion.optionC); |
|
|
|
params.append('D', this.editingQuestion.optionD); |
|
|
|
params.append('correct_answer', this.editingQuestion.correctAnswer); |
|
|
|
params.append('question_type_id', this.editingQuestion.questionTypeName === '股票知识' ? 1 : 2); |
|
|
|
params.append('course_recommendation_id', |
|
|
|
this.editingQuestion.recommendedCourse === '量能擒牛' ? 1 : |
|
|
|
this.editingQuestion.recommendedCourse === '价格破译' ? 2 : 3); |
|
|
|
|
|
|
|
// 发送请求 |
|
|
|
const response = await axios.post('/admin/questions/update', params, { |
|
|
|
headers: { |
|
|
|
'Content-Type': 'application/x-www-form-urlencoded' |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
if (response.data.code === 200) { |
|
|
|
this.closeEditModal(); |
|
|
|
// 刷新题目列表 |
|
|
|
await this.fetchQuestions(); |
|
|
|
alert('修改题目成功!'); |
|
|
|
} else { |
|
|
|
alert('修改题目失败:' + response.data.msg); |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error('修改题目失败:', error); |
|
|
|
alert('网络错误,请检查连接!'); |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
// 关闭编辑弹窗 |
|
|
|
closeEditModal() { |
|
|
|
this.showEditModal = false; |
|
|
|
this.editingQuestion = {}; |
|
|
|
}, |
|
|
|
|
|
|
|
// 删除题目(点击删除按钮) |
|
|
|
async deleteQuestion(item) { |
|
|
|
this.deleteId = item.id; |
|
|
|
this.showDeleteModal = true; |
|
|
|
}, |
|
|
|
|
|
|
|
// 确认删除 |
|
|
|
async confirmDelete() { |
|
|
|
try { |
|
|
|
const response = await deleteQuestion({ id: this.deleteId }) |
|
|
|
|
|
|
|
if (response.data.code === 200) { |
|
|
|
this.closeDeleteModal() |
|
|
|
await this.fetchQuestions() |
|
|
|
alert('删除成功!') |
|
|
|
} else { |
|
|
|
alert('删除失败:' + response.data.msg) |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error('删除失败:', error) |
|
|
|
alert('网络错误,请检查连接!') |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
// 关闭删除确认对话框 |
|
|
|
closeDeleteModal() { |
|
|
|
this.showDeleteModal = false; |
|
|
|
this.deleteId = null; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
</script> |
|
|
|
|
|
|
|
|
|
|
|
<style scoped> |
|
|
|
|
|
|
|
/* 修改操作列按钮间距 */ |
|
|
|
td:last-child { |
|
|
|
display: flex; |
|
|
|
@ -176,6 +583,55 @@ th { |
|
|
|
vertical-align: middle !important; |
|
|
|
} |
|
|
|
|
|
|
|
.form-row-options { |
|
|
|
display: grid; |
|
|
|
grid-template-columns: 1fr 1fr; |
|
|
|
gap: 16px; |
|
|
|
margin-bottom: 16px; |
|
|
|
} |
|
|
|
|
|
|
|
.form-row { |
|
|
|
margin-bottom: 16px; |
|
|
|
} |
|
|
|
|
|
|
|
.form-row label { |
|
|
|
display: block; |
|
|
|
margin-bottom: 8px; |
|
|
|
font-weight: 500; |
|
|
|
color: #333; |
|
|
|
} |
|
|
|
|
|
|
|
.form-row select, |
|
|
|
.form-row input[type="text"], |
|
|
|
.form-row textarea { |
|
|
|
width: 100%; |
|
|
|
padding: 10px; |
|
|
|
border: 1px solid #ddd; |
|
|
|
border-radius: 4px; |
|
|
|
box-sizing: border-box; |
|
|
|
} |
|
|
|
|
|
|
|
.form-row textarea { |
|
|
|
resize: vertical; |
|
|
|
} |
|
|
|
|
|
|
|
.option-group { |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
} |
|
|
|
|
|
|
|
/* 响应式设计 */ |
|
|
|
@media (max-width: 768px) { |
|
|
|
.modal-content { |
|
|
|
width: 90%; |
|
|
|
} |
|
|
|
|
|
|
|
.form-row-options { |
|
|
|
grid-template-columns: 1fr; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* sort-header 样式 */ |
|
|
|
.sort-header { |
|
|
|
display: flex; |
|
|
|
@ -192,6 +648,7 @@ th { |
|
|
|
stroke-width: 2; |
|
|
|
transition: transform 0.2s; |
|
|
|
} |
|
|
|
|
|
|
|
/* 表格容器样式 */ |
|
|
|
.table-container { |
|
|
|
width: 100%; |
|
|
|
@ -250,6 +707,226 @@ tr:hover { |
|
|
|
background-color: #f9f9f9; |
|
|
|
} |
|
|
|
|
|
|
|
/* 弹窗样式 */ |
|
|
|
.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; |
|
|
|
} |
|
|
|
|
|
|
|
/* 查看弹窗专用样式 */ |
|
|
|
.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; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* 删除确认对话框样式 */ |
|
|
|
.delete-modal-content { |
|
|
|
background-color: white; |
|
|
|
border-radius: 8px; |
|
|
|
width: 500px; |
|
|
|
max-width: 90%; |
|
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); |
|
|
|
overflow: hidden; |
|
|
|
} |
|
|
|
|
|
|
|
.delete-modal-content .modal-header { |
|
|
|
padding: 20px; |
|
|
|
border-bottom: 1px solid #eee; |
|
|
|
display: flex; |
|
|
|
justify-content: space-between; |
|
|
|
align-items: center; |
|
|
|
} |
|
|
|
|
|
|
|
.delete-modal-content .modal-header h3 { |
|
|
|
margin: 0; |
|
|
|
font-size: 18px; |
|
|
|
color: #333; |
|
|
|
} |
|
|
|
|
|
|
|
.delete-modal-content .close-btn { |
|
|
|
background: none; |
|
|
|
border: none; |
|
|
|
font-size: 24px; |
|
|
|
cursor: pointer; |
|
|
|
color: #666; |
|
|
|
padding: 5px; |
|
|
|
border-radius: 50%; |
|
|
|
transition: color 0.2s; |
|
|
|
} |
|
|
|
|
|
|
|
.delete-modal-content .close-btn:hover { |
|
|
|
color: #e74c3c; |
|
|
|
} |
|
|
|
|
|
|
|
.delete-modal-content .modal-footer { |
|
|
|
padding: 20px; |
|
|
|
border-top: 1px solid #eee; |
|
|
|
display: flex; |
|
|
|
justify-content: flex-end; |
|
|
|
gap: 16px; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.modal-content { |
|
|
|
background-color: white; |
|
|
|
border-radius: 8px; |
|
|
|
width: 600px; /* 从500px改为620px */ |
|
|
|
height: 760px; /* 增加高度 */ |
|
|
|
max-width: 90%; |
|
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); |
|
|
|
overflow: hidden; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.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-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-footer { |
|
|
|
padding: 20px; |
|
|
|
border-top: 1px solid #eee; |
|
|
|
display: flex; |
|
|
|
justify-content: flex-end; |
|
|
|
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; |
|
|
|
} |
|
|
|
|
|
|
|
.pagination-controls { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
gap: 10px; |
|
|
|
margin-right: 20px; /* 明确设置右侧间距 */ |
|
|
|
} |
|
|
|
|
|
|
|
.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; |
|
|
|
} |
|
|
|
</style> |