Merge branch 'dev' of http://110.42.96.65:19890/GoCo/OL-LearnPlatform-Frontend into dev
# Conflicts: # src/views/Faculty.vue
This commit is contained in:
commit
bc5d21fc76
BIN
public/images/teacher/发布人.png
Normal file
BIN
public/images/teacher/发布人.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 831 B |
BIN
public/images/teacher/起点时间.png
Normal file
BIN
public/images/teacher/起点时间.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 668 B |
0
src/components/admin/CourseDetailManagement.vue
Normal file
0
src/components/admin/CourseDetailManagement.vue
Normal file
324
src/components/admin/ExamComponents/BatchSetScoreModal.vue
Normal file
324
src/components/admin/ExamComponents/BatchSetScoreModal.vue
Normal file
@ -0,0 +1,324 @@
|
|||||||
|
<template>
|
||||||
|
<n-modal v-model:show="showModal" class="batch-score-modal" preset="card"
|
||||||
|
:mask-closable="false" :closable="false" :style="{ width: '1000px' }">
|
||||||
|
<div class="header">
|
||||||
|
<span class="header-title">批量设置分数</span>
|
||||||
|
</div>
|
||||||
|
<n-divider />
|
||||||
|
<div class="batch-score-content">
|
||||||
|
<!-- 题目列表 -->
|
||||||
|
<div class="question-list">
|
||||||
|
<div v-for="(bigQuestion, bigIndex) in questionList" :key="bigQuestion.id"
|
||||||
|
class="big-question-section">
|
||||||
|
<div v-for="(subQuestion, subIndex) in bigQuestion.subQuestions" :key="subQuestion.id"
|
||||||
|
class="question-item">
|
||||||
|
<div class="question-info">
|
||||||
|
<span class="question-number">{{ bigIndex + 1 }}.{{ subIndex + 1 }}</span>
|
||||||
|
<div class="question-content">
|
||||||
|
{{ subQuestion.title }}
|
||||||
|
</div>
|
||||||
|
<span class="question-type">{{ getQuestionTypeName(subQuestion.type) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="question-score">
|
||||||
|
<span class="score-label">分数:</span>
|
||||||
|
<n-input-number
|
||||||
|
v-model:value="subQuestion.score"
|
||||||
|
size="small"
|
||||||
|
:min="0"
|
||||||
|
:max="100"
|
||||||
|
:precision="1"
|
||||||
|
@update:value="updateQuestionScore(bigIndex, subIndex, $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 底部按钮 -->
|
||||||
|
<div class="modal-actions">
|
||||||
|
<n-button strong secondary @click="cancelBatchSet">取消</n-button>
|
||||||
|
<n-button type="info" @click="confirmBatchSet">确定</n-button>
|
||||||
|
</div>
|
||||||
|
</n-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch } from 'vue';
|
||||||
|
import { createDiscreteApi } from 'naive-ui';
|
||||||
|
|
||||||
|
// 创建独立的 message API
|
||||||
|
const { message } = createDiscreteApi(['message']);
|
||||||
|
|
||||||
|
// 题型枚举
|
||||||
|
enum QuestionType {
|
||||||
|
SINGLE_CHOICE = 'single_choice', // 单选题
|
||||||
|
MULTIPLE_CHOICE = 'multiple_choice', // 多选题
|
||||||
|
TRUE_FALSE = 'true_false', // 判断题
|
||||||
|
FILL_BLANK = 'fill_blank', // 填空题
|
||||||
|
SHORT_ANSWER = 'short_answer', // 简答题
|
||||||
|
COMPOSITE = 'composite' // 复合题
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择题选项接口
|
||||||
|
interface ChoiceOption {
|
||||||
|
id: string;
|
||||||
|
content: string;
|
||||||
|
isCorrect: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填空题答案接口
|
||||||
|
interface FillBlankAnswer {
|
||||||
|
id: string;
|
||||||
|
content: string;
|
||||||
|
position: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 小题接口
|
||||||
|
interface SubQuestion {
|
||||||
|
id: string;
|
||||||
|
type: QuestionType;
|
||||||
|
title: string;
|
||||||
|
score: number;
|
||||||
|
difficulty: 'easy' | 'medium' | 'hard';
|
||||||
|
required: boolean;
|
||||||
|
options?: ChoiceOption[];
|
||||||
|
correctAnswer?: string | string[];
|
||||||
|
fillBlanks?: FillBlankAnswer[];
|
||||||
|
trueFalseAnswer?: boolean;
|
||||||
|
textAnswer?: string;
|
||||||
|
answerKeywords?: string[];
|
||||||
|
subQuestions?: SubQuestion[];
|
||||||
|
explanation?: string;
|
||||||
|
tags?: string[];
|
||||||
|
createTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 大题接口
|
||||||
|
interface BigQuestion {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
sort: number;
|
||||||
|
totalScore: number;
|
||||||
|
subQuestions: SubQuestion[];
|
||||||
|
createTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Props 定义
|
||||||
|
interface Props {
|
||||||
|
visible: boolean;
|
||||||
|
questions: BigQuestion[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emits 定义
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:visible', value: boolean): void;
|
||||||
|
(e: 'confirm', questions: BigQuestion[]): void;
|
||||||
|
(e: 'cancel'): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
|
// 内部状态
|
||||||
|
const showModal = computed({
|
||||||
|
get: () => props.visible,
|
||||||
|
set: (value) => emit('update:visible', value)
|
||||||
|
});
|
||||||
|
|
||||||
|
// 题目列表的本地副本,避免直接修改父组件数据
|
||||||
|
const questionList = ref<BigQuestion[]>([]);
|
||||||
|
|
||||||
|
// 监听 props.questions 变化,更新本地副本
|
||||||
|
watch(() => props.questions, (newQuestions) => {
|
||||||
|
// 深拷贝题目数据,避免直接修改原数据
|
||||||
|
questionList.value = JSON.parse(JSON.stringify(newQuestions));
|
||||||
|
}, { immediate: true, deep: true });
|
||||||
|
|
||||||
|
// 获取题型名称
|
||||||
|
const getQuestionTypeName = (type: QuestionType): string => {
|
||||||
|
const typeMap = {
|
||||||
|
[QuestionType.SINGLE_CHOICE]: '单选题',
|
||||||
|
[QuestionType.MULTIPLE_CHOICE]: '多选题',
|
||||||
|
[QuestionType.TRUE_FALSE]: '判断题',
|
||||||
|
[QuestionType.FILL_BLANK]: '填空题',
|
||||||
|
[QuestionType.SHORT_ANSWER]: '简答题',
|
||||||
|
[QuestionType.COMPOSITE]: '复合题'
|
||||||
|
};
|
||||||
|
return typeMap[type] || '未知题型';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新题目分数
|
||||||
|
const updateQuestionScore = (bigIndex: number, subIndex: number, score: number) => {
|
||||||
|
if (questionList.value[bigIndex] && questionList.value[bigIndex].subQuestions[subIndex]) {
|
||||||
|
questionList.value[bigIndex].subQuestions[subIndex].score = score || 0;
|
||||||
|
|
||||||
|
// 重新计算大题总分
|
||||||
|
const bigQuestion = questionList.value[bigIndex];
|
||||||
|
bigQuestion.totalScore = bigQuestion.subQuestions.reduce((total, sub) => total + (sub.score || 0), 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消批量设置
|
||||||
|
const cancelBatchSet = () => {
|
||||||
|
showModal.value = false;
|
||||||
|
emit('cancel');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确认批量设置
|
||||||
|
const confirmBatchSet = () => {
|
||||||
|
// 验证分数设置
|
||||||
|
let hasInvalidScore = false;
|
||||||
|
for (const bigQuestion of questionList.value) {
|
||||||
|
for (const subQuestion of bigQuestion.subQuestions) {
|
||||||
|
if (subQuestion.score <= 0) {
|
||||||
|
hasInvalidScore = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasInvalidScore) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasInvalidScore) {
|
||||||
|
message.warning('请确保所有题目分数都大于0');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showModal.value = false;
|
||||||
|
emit('confirm', questionList.value);
|
||||||
|
message.success('批量设置分数成功');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.batch-score-modal {
|
||||||
|
--n-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title{
|
||||||
|
color: #000;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-score-content {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
min-height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-list {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.big-question-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 16px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-item:hover {
|
||||||
|
background-color: #f0f8ff;
|
||||||
|
border-color: #d1e7dd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-number {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-right: 8px;
|
||||||
|
min-width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-type {
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-left: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-content {
|
||||||
|
color: #062333;
|
||||||
|
font-size: 14px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
/* max-width: 500px; */
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-score {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-label {
|
||||||
|
margin-right: 8px;
|
||||||
|
color: #062333;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滚动条样式 */
|
||||||
|
.batch-score-content::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-score-content::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-score-content::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-score-content::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a8a8a8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.question-item {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-info {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-content {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-score {
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
761
src/components/admin/ExamComponents/ExamSettingsModal.vue
Normal file
761
src/components/admin/ExamComponents/ExamSettingsModal.vue
Normal file
@ -0,0 +1,761 @@
|
|||||||
|
<template>
|
||||||
|
<n-modal v-model:show="showModal" class="exam-settings-modal" preset="dialog" title="试卷设置" :mask-closable="false"
|
||||||
|
:closable="true" :style="{ width: '1000px' }">
|
||||||
|
|
||||||
|
<div class="exam-settings-content">
|
||||||
|
<!-- 试卷名称 -->
|
||||||
|
<div class="setting-row">
|
||||||
|
<label class="setting-label">试卷名称</label>
|
||||||
|
<n-input v-model:value="formData.title" placeholder="试卷名称或试卷名称" class="setting-input" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 起止时间 -->
|
||||||
|
<div class="setting-row">
|
||||||
|
<label class="setting-label">起止时间</label>
|
||||||
|
<div class="time-range">
|
||||||
|
<n-date-picker v-model:value="formData.startTime" type="datetime" placeholder="选择考试开始时间"
|
||||||
|
format="yyyy-MM-dd HH:mm" class="time-picker" />
|
||||||
|
<span class="time-separator">到</span>
|
||||||
|
<n-date-picker v-model:value="formData.endTime" type="datetime" placeholder="选择考试结束时间"
|
||||||
|
format="yyyy-MM-dd HH:mm" class="time-picker" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 试卷分类 -->
|
||||||
|
<div class="setting-row">
|
||||||
|
<label class="setting-label">试卷分类</label>
|
||||||
|
<n-radio-group v-model:value="formData.category" class="category-group">
|
||||||
|
<n-radio value="exam">考试</n-radio>
|
||||||
|
<n-radio value="practice">练习</n-radio>
|
||||||
|
</n-radio-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 答题时间 - 考试模式显示 -->
|
||||||
|
<div v-if="formData.category === 'exam'" class="setting-row">
|
||||||
|
<label class="setting-label">答题时间</label>
|
||||||
|
<div class="answer-time-setting">
|
||||||
|
<n-radio-group v-model:value="formData.timeLimit" class="time-limit-group">
|
||||||
|
<n-radio value="limited">
|
||||||
|
<template #default>
|
||||||
|
<n-input v-model:value="formData.timeLimitValue" :min="0" size="small"
|
||||||
|
style="width: 80px; margin-right: 8px;" />
|
||||||
|
分钟
|
||||||
|
</template>
|
||||||
|
</n-radio>
|
||||||
|
<n-radio value="no_limit">不限时长</n-radio>
|
||||||
|
</n-radio-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 答题次数 -->
|
||||||
|
<div class="setting-row">
|
||||||
|
<label class="setting-label">答题次数</label>
|
||||||
|
<div class="exam-times-setting">
|
||||||
|
<n-radio-group v-model:value="formData.examTimes" class="exam-times-group">
|
||||||
|
<n-radio value="unlimited">无限次</n-radio>
|
||||||
|
<n-radio value="limited">
|
||||||
|
限
|
||||||
|
<n-input v-model:value="formData.examTimesValue" :min="1" size="small"
|
||||||
|
style="width: 80px; margin-left: 8px; margin-right: 8px;" />
|
||||||
|
次
|
||||||
|
</n-radio>
|
||||||
|
</n-radio-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 所属章节 -->
|
||||||
|
<div class="setting-row">
|
||||||
|
<label class="setting-label">所属章节</label>
|
||||||
|
<n-select v-model:value="formData.chapter" placeholder="第一节 开始想着" :options="chapterOptions"
|
||||||
|
class="setting-select" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 及格分数 -->
|
||||||
|
<div class="setting-row">
|
||||||
|
<label class="setting-label">及格分数</label>
|
||||||
|
<div class="pass-score-setting">
|
||||||
|
<n-input v-model:value="formData.passScore" :min="0" :max="100" size="small" style="width: 80px" />
|
||||||
|
<span>分</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 参与学员 -->
|
||||||
|
<div class="setting-row">
|
||||||
|
<label class="setting-label">参与学员</label>
|
||||||
|
<div class="participants-setting">
|
||||||
|
<n-radio-group v-model:value="formData.participants" class="participants-group">
|
||||||
|
<n-radio value="all">全部学员</n-radio>
|
||||||
|
<n-radio value="by_school">仅部分学员</n-radio>
|
||||||
|
</n-radio-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 详细班级 - 当选择仅部分学员时显示 -->
|
||||||
|
<div v-if="formData.participants === 'by_school'" class="setting-row">
|
||||||
|
<label class="setting-label">详细班级</label>
|
||||||
|
<n-select v-model:value="formData.selectedClasses" placeholder="选择班级" multiple :options="classOptions"
|
||||||
|
class="setting-select" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 考试说明 -->
|
||||||
|
<div class="setting-row">
|
||||||
|
<label class="setting-label">考试说明</label>
|
||||||
|
<n-input v-model:value="formData.instructions" type="textarea" placeholder="请填写试卷说明"
|
||||||
|
:autosize="{ minRows: 3, maxRows: 6 }" class="setting-textarea" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 强制阅读考试说明 - 仅考试模式显示 -->
|
||||||
|
<div v-if="formData.category === 'exam'" class="setting-row">
|
||||||
|
<label class="setting-label"></label>
|
||||||
|
<div class="checkbox-setting">
|
||||||
|
<n-checkbox v-model:checked="formData.enforceInstructions">
|
||||||
|
强制阅读考试说明
|
||||||
|
</n-checkbox>
|
||||||
|
<n-input v-if="formData.enforceInstructions" v-model:value="formData.readingTime" :min="1"
|
||||||
|
size="small" style="width: 80px; margin-left: 8px; margin-right: 8px;" />
|
||||||
|
<span v-if="formData.enforceInstructions">秒</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 高级设置 -->
|
||||||
|
<n-collapse class="advanced-settings" arrow-placement="right">
|
||||||
|
<n-collapse-item title="高级设置" name="advanced">
|
||||||
|
<n-divider />
|
||||||
|
<!-- 考试模式的高级设置 -->
|
||||||
|
<div v-if="formData.category === 'exam'">
|
||||||
|
<!-- 交卷设置 -->
|
||||||
|
<div class="advanced-setting">
|
||||||
|
<label class="advanced-label">交卷设置</label>
|
||||||
|
<n-checkbox v-model:checked="formData.submitSettings.allowEarlySubmit">
|
||||||
|
考试时间结束允许延时自动提交
|
||||||
|
</n-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 限时进入 -->
|
||||||
|
<div class="advanced-setting">
|
||||||
|
<label class="advanced-label">限时进入</label>
|
||||||
|
<div class="grading-settings">
|
||||||
|
<n-input v-model:value="formData.gradingDelay" :min="0" size="small"
|
||||||
|
style="width: 80px; margin-right: 8px;" />
|
||||||
|
<span>分钟后不允许参加考试</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 查看分数 -->
|
||||||
|
<div class="advanced-setting">
|
||||||
|
<label class="advanced-label">查看分数</label>
|
||||||
|
<n-radio-group v-model:value="formData.scoreDisplay" class="score-display-group">
|
||||||
|
<n-radio value="show_all">允许教师批阅完试卷后学员查看分数</n-radio>
|
||||||
|
</n-radio-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 查看答案 -->
|
||||||
|
<div class="advanced-setting">
|
||||||
|
<label class="advanced-label">查看答案</label>
|
||||||
|
<div class="answer-view-settings">
|
||||||
|
<n-checkbox v-model:checked="formData.detailedSettings.showQuestions">
|
||||||
|
考试截止后允许查看答案
|
||||||
|
</n-checkbox>
|
||||||
|
<n-checkbox v-model:checked="formData.detailedSettings.showAnalysis">
|
||||||
|
学生提交后允许查看答案
|
||||||
|
</n-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 展示排名 -->
|
||||||
|
<div class="advanced-setting">
|
||||||
|
<label class="advanced-label">展示排名</label>
|
||||||
|
<n-checkbox v-model:checked="formData.showRanking">
|
||||||
|
考试截止后展示排名
|
||||||
|
</n-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 发送通知 -->
|
||||||
|
<div class="advanced-setting">
|
||||||
|
<label class="advanced-label">发送通知</label>
|
||||||
|
<n-flex vertical>
|
||||||
|
<n-switch v-model:value="formData.timerEnabled" />
|
||||||
|
<div v-if="formData.timerEnabled" class="timer-setting">
|
||||||
|
<span>考试结束前</span>
|
||||||
|
<n-input v-model:value="formData.timerDuration" :min="1" size="small"
|
||||||
|
style="width: 80px; margin: 0 8px;" />
|
||||||
|
<span>分钟发送通知提醒未交学生</span>
|
||||||
|
</div>
|
||||||
|
</n-flex>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 作答要求 -->
|
||||||
|
<div class="advanced-setting">
|
||||||
|
<label class="advanced-label">作答要求</label>
|
||||||
|
<n-radio-group v-model:value="formData.answerType" class="answer-type-group">
|
||||||
|
<n-radio value="auto_save">
|
||||||
|
完成当前课程进度
|
||||||
|
<n-input v-model:value="formData.courseProgress" :min="0" :max="100" size="small"
|
||||||
|
style="width: 80px; margin: 0 8px;" />
|
||||||
|
%允许考试
|
||||||
|
</n-radio>
|
||||||
|
</n-radio-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 详分设置 -->
|
||||||
|
<div class="advanced-setting">
|
||||||
|
<label class="advanced-label">详分设置</label>
|
||||||
|
<n-radio-group v-model:value="formData.detailScoreMode" class="detail-score-group">
|
||||||
|
<n-radio value="question">填空题、简答题题目设为为主观题 <span class="tip">设为主观题后需教师手动批阅</span> </n-radio>
|
||||||
|
<n-radio value="automatic">填空题、简答题不区分大小写 <span class="tip">勾选后,英文大写和小写都可以得分</span> </n-radio>
|
||||||
|
<n-radio value="show_current">填空题、简答题忽略符号 <span class="tip">勾选后,答案内符号与标准答案不同也给分</span> </n-radio>
|
||||||
|
<n-radio value="show_all">多选题未全选对时得一半分 <span class="tip">不勾选时全选对才给分</span> </n-radio>
|
||||||
|
</n-radio-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 练习模式的高级设置 -->
|
||||||
|
<div v-if="formData.category === 'practice'">
|
||||||
|
<!-- 重做设置 -->
|
||||||
|
<div class="advanced-setting">
|
||||||
|
<label class="advanced-label">重做设置</label>
|
||||||
|
<n-flex>
|
||||||
|
<n-radio-group v-model:value="formData.correctnessMode" class="correctness-group">
|
||||||
|
<div class="practice-record-settings">
|
||||||
|
<n-radio value="no_limit">允许学员练习不限次数</n-radio>
|
||||||
|
<n-radio value="limit_wrong">
|
||||||
|
允许学员重新练习
|
||||||
|
<n-input v-model:value="formData.wrongLimit" :min="1" size="small"
|
||||||
|
style="width: 80px; margin: 0 8px;" />
|
||||||
|
次
|
||||||
|
</n-radio>
|
||||||
|
</div>
|
||||||
|
</n-radio-group>
|
||||||
|
<div class="practice-record-settings2">
|
||||||
|
<n-checkbox v-model:checked="formData.practiceSettings.keepPreviousAnswers">
|
||||||
|
学员重新练习保留前一次的作答记录
|
||||||
|
</n-checkbox>
|
||||||
|
<n-checkbox v-model:checked="formData.practiceSettings.useLastScore">
|
||||||
|
最后一次练习成绩为最终成绩
|
||||||
|
</n-checkbox>
|
||||||
|
</div>
|
||||||
|
</n-flex>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 试卷设置 -->
|
||||||
|
<div class="advanced-setting">
|
||||||
|
<label class="advanced-label">试卷设置</label>
|
||||||
|
<div class="practice-settings">
|
||||||
|
<n-checkbox v-model:checked="formData.practiceSettings.showCorrectAnswer">
|
||||||
|
允许学生主题完成前查看答案
|
||||||
|
</n-checkbox>
|
||||||
|
<n-checkbox v-model:checked="formData.practiceSettings.showWrongAnswer">
|
||||||
|
每道一次练习题查看提交练习
|
||||||
|
</n-checkbox>
|
||||||
|
<n-checkbox v-model:checked="formData.practiceSettings.showAnalysis">
|
||||||
|
允许学生查看排名
|
||||||
|
</n-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-collapse-item>
|
||||||
|
</n-collapse>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部按钮 -->
|
||||||
|
<template #action>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<n-button @click="cancelSettings">取消</n-button>
|
||||||
|
<n-button type="primary" @click="confirmSettings">确定</n-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch } from 'vue';
|
||||||
|
import { createDiscreteApi } from 'naive-ui';
|
||||||
|
|
||||||
|
// 创建独立的 message API
|
||||||
|
const { message } = createDiscreteApi(['message']);
|
||||||
|
|
||||||
|
// 试卷设置表单数据接口
|
||||||
|
interface ExamSettings {
|
||||||
|
title: string;
|
||||||
|
startTime: number | null;
|
||||||
|
endTime: number | null;
|
||||||
|
category: 'exam' | 'practice';
|
||||||
|
timeLimit: 'unlimited' | 'limited' | 'no_limit';
|
||||||
|
timeLimitValue: number;
|
||||||
|
examTimes: 'unlimited' | 'limited' | 'each_day';
|
||||||
|
examTimesValue: number;
|
||||||
|
dailyLimit: number;
|
||||||
|
chapter: string;
|
||||||
|
passScore: number;
|
||||||
|
participants: 'all' | 'by_school';
|
||||||
|
selectedClasses: string[];
|
||||||
|
instructions: string;
|
||||||
|
|
||||||
|
// 考试模式专用
|
||||||
|
enforceOrder: boolean;
|
||||||
|
enforceInstructions: boolean;
|
||||||
|
readingTime: number;
|
||||||
|
submitSettings: {
|
||||||
|
allowEarlySubmit: boolean;
|
||||||
|
};
|
||||||
|
gradingDelay: number;
|
||||||
|
scoreDisplay: 'show_all' | 'show_score' | 'hide_all';
|
||||||
|
detailedSettings: {
|
||||||
|
showQuestions: boolean;
|
||||||
|
showAnalysis: boolean;
|
||||||
|
showSubmissionTime: boolean;
|
||||||
|
};
|
||||||
|
timerEnabled: boolean;
|
||||||
|
timerDuration: number;
|
||||||
|
answerType: 'auto_save' | 'manual_save' | 'multiple_submit';
|
||||||
|
detailScoreMode: 'question' | 'automatic' | 'show_current' | 'show_all';
|
||||||
|
showRanking: boolean; // 展示排名
|
||||||
|
courseProgress: number; // 作答要求的课程进度
|
||||||
|
|
||||||
|
// 练习模式专用
|
||||||
|
correctnessMode: 'no_limit' | 'limit_wrong';
|
||||||
|
wrongLimit: number;
|
||||||
|
practiceSettings: {
|
||||||
|
showCorrectAnswer: boolean;
|
||||||
|
showWrongAnswer: boolean;
|
||||||
|
showAnalysis: boolean;
|
||||||
|
keepPreviousAnswers: boolean; // 学员重新练习保留前一次的作答记录
|
||||||
|
useLastScore: boolean; // 最后一次练习成绩为最终成绩
|
||||||
|
};
|
||||||
|
paperMode: 'show_all' | 'show_current' | 'hide_all';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Props 定义
|
||||||
|
interface Props {
|
||||||
|
visible: boolean;
|
||||||
|
examData: ExamSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emits 定义
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:visible', value: boolean): void;
|
||||||
|
(e: 'confirm', settings: ExamSettings): void;
|
||||||
|
(e: 'cancel'): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
|
// 内部状态
|
||||||
|
const showModal = computed({
|
||||||
|
get: () => props.visible,
|
||||||
|
set: (value) => emit('update:visible', value)
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表单数据的本地副本
|
||||||
|
const formData = ref<ExamSettings>({
|
||||||
|
title: '',
|
||||||
|
startTime: null,
|
||||||
|
endTime: null,
|
||||||
|
category: 'exam',
|
||||||
|
timeLimit: 'limited',
|
||||||
|
timeLimitValue: 0,
|
||||||
|
examTimes: 'unlimited',
|
||||||
|
examTimesValue: 1,
|
||||||
|
dailyLimit: 1,
|
||||||
|
chapter: '',
|
||||||
|
passScore: 60,
|
||||||
|
participants: 'all',
|
||||||
|
selectedClasses: [],
|
||||||
|
instructions: '',
|
||||||
|
|
||||||
|
// 考试模式专用
|
||||||
|
enforceOrder: false,
|
||||||
|
enforceInstructions: false,
|
||||||
|
readingTime: 10,
|
||||||
|
submitSettings: {
|
||||||
|
allowEarlySubmit: true,
|
||||||
|
},
|
||||||
|
gradingDelay: 60,
|
||||||
|
scoreDisplay: 'show_all',
|
||||||
|
detailedSettings: {
|
||||||
|
showQuestions: false,
|
||||||
|
showAnalysis: false,
|
||||||
|
showSubmissionTime: false,
|
||||||
|
},
|
||||||
|
timerEnabled: false,
|
||||||
|
timerDuration: 10,
|
||||||
|
answerType: 'auto_save',
|
||||||
|
detailScoreMode: 'question',
|
||||||
|
showRanking: false,
|
||||||
|
courseProgress: 0,
|
||||||
|
|
||||||
|
// 练习模式专用
|
||||||
|
correctnessMode: 'no_limit',
|
||||||
|
wrongLimit: 10,
|
||||||
|
practiceSettings: {
|
||||||
|
showCorrectAnswer: false,
|
||||||
|
showWrongAnswer: false,
|
||||||
|
showAnalysis: false,
|
||||||
|
keepPreviousAnswers: false,
|
||||||
|
useLastScore: false,
|
||||||
|
},
|
||||||
|
paperMode: 'show_all',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听 props.examData 变化,更新本地副本
|
||||||
|
watch(() => props.examData, (newData) => {
|
||||||
|
if (newData) {
|
||||||
|
formData.value = JSON.parse(JSON.stringify(newData));
|
||||||
|
}
|
||||||
|
}, { immediate: true, deep: true });
|
||||||
|
|
||||||
|
// 章节选项
|
||||||
|
const chapterOptions = ref([
|
||||||
|
{ label: '第一节 开课前准备', value: 'chapter1' },
|
||||||
|
{ label: '第二节 课程内容', value: 'chapter2' },
|
||||||
|
{ label: '第三节 课程总结', value: 'chapter3' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 班级选项
|
||||||
|
const classOptions = ref([
|
||||||
|
{ label: '全部学员', value: 'all' },
|
||||||
|
{ label: '优班学习', value: 'class1' },
|
||||||
|
{ label: '一班', value: 'class2' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 取消设置
|
||||||
|
const cancelSettings = () => {
|
||||||
|
showModal.value = false;
|
||||||
|
emit('cancel');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确认设置
|
||||||
|
const confirmSettings = () => {
|
||||||
|
// 验证必填字段
|
||||||
|
if (!formData.value.title.trim()) {
|
||||||
|
message.warning('请输入试卷名称');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formData.value.startTime || !formData.value.endTime) {
|
||||||
|
message.warning('请设置起止时间');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.value.startTime >= formData.value.endTime) {
|
||||||
|
message.warning('结束时间必须大于开始时间');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showModal.value = false;
|
||||||
|
emit('confirm', formData.value);
|
||||||
|
message.success('试卷设置保存成功');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.exam-settings-modal {
|
||||||
|
--n-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-settings-content {
|
||||||
|
max-height: 800px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
min-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-label {
|
||||||
|
flex: 0 0 80px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
margin-right: 16px;
|
||||||
|
line-height: 32px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-input,
|
||||||
|
.setting-select,
|
||||||
|
.setting-textarea {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-range {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-picker {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-separator {
|
||||||
|
margin: 0 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-time-setting,
|
||||||
|
.exam-times-setting {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-limit-group,
|
||||||
|
.exam-times-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-limit-input,
|
||||||
|
.exam-times-input {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-times-option {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pass-score-setting {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.participants-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advanced-setting {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.practice-settings{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advanced-label {
|
||||||
|
flex: 0 0 80px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
margin-right: 16px;
|
||||||
|
line-height: 32px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-view-settings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer-setting {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 8px;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.grading-settings {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-display-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailed-settings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer-settings {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-settings {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-type-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.correctness-settings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.correctness-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrong-limit-input {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-limit-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-times-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.correctness-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-type-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-display-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-score-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip{
|
||||||
|
margin-left: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-setting {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.practice-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.practice-record-settings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 8px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.practice-record-settings2 {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper-settings {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper-mode-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滚动条样式 */
|
||||||
|
.exam-settings-content::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-settings-content::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-settings-content::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-settings-content::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a8a8a8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.setting-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-label {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-range {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-separator {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
10
src/main.ts
10
src/main.ts
@ -34,6 +34,7 @@ import {
|
|||||||
NBreadcrumbItem,
|
NBreadcrumbItem,
|
||||||
NInput,
|
NInput,
|
||||||
NInputGroup,
|
NInputGroup,
|
||||||
|
NInputNumber,
|
||||||
NForm,
|
NForm,
|
||||||
NFormItem,
|
NFormItem,
|
||||||
NSelect,
|
NSelect,
|
||||||
@ -41,6 +42,7 @@ import {
|
|||||||
NTimePicker,
|
NTimePicker,
|
||||||
NCheckbox,
|
NCheckbox,
|
||||||
NRadio,
|
NRadio,
|
||||||
|
NRadioGroup,
|
||||||
NSwitch,
|
NSwitch,
|
||||||
NSlider,
|
NSlider,
|
||||||
NRate,
|
NRate,
|
||||||
@ -82,7 +84,8 @@ import {
|
|||||||
NStep,
|
NStep,
|
||||||
NTimeline,
|
NTimeline,
|
||||||
NTimelineItem,
|
NTimelineItem,
|
||||||
NMessageProvider
|
NMessageProvider,
|
||||||
|
NPopselect
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
|
|
||||||
const naive = create({
|
const naive = create({
|
||||||
@ -104,6 +107,7 @@ const naive = create({
|
|||||||
NBreadcrumbItem,
|
NBreadcrumbItem,
|
||||||
NInput,
|
NInput,
|
||||||
NInputGroup,
|
NInputGroup,
|
||||||
|
NInputNumber,
|
||||||
NForm,
|
NForm,
|
||||||
NFormItem,
|
NFormItem,
|
||||||
NSelect,
|
NSelect,
|
||||||
@ -111,6 +115,7 @@ const naive = create({
|
|||||||
NTimePicker,
|
NTimePicker,
|
||||||
NCheckbox,
|
NCheckbox,
|
||||||
NRadio,
|
NRadio,
|
||||||
|
NRadioGroup,
|
||||||
NSwitch,
|
NSwitch,
|
||||||
NSlider,
|
NSlider,
|
||||||
NRate,
|
NRate,
|
||||||
@ -152,7 +157,8 @@ const naive = create({
|
|||||||
NStep,
|
NStep,
|
||||||
NTimeline,
|
NTimeline,
|
||||||
NTimelineItem,
|
NTimelineItem,
|
||||||
NMessageProvider
|
NMessageProvider,
|
||||||
|
NPopselect
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -43,7 +43,6 @@ import CourseCreate from '@/components/admin/CourseComponents/CourseCreate.vue'
|
|||||||
import CourseEditor from '@/views/teacher/course/CourseEditor.vue'
|
import CourseEditor from '@/views/teacher/course/CourseEditor.vue'
|
||||||
import CoursewareManagement from '@/views/teacher/course/CoursewareManagement.vue'
|
import CoursewareManagement from '@/views/teacher/course/CoursewareManagement.vue'
|
||||||
import ChapterManagement from '@/views/teacher/course/ChapterManagement.vue'
|
import ChapterManagement from '@/views/teacher/course/ChapterManagement.vue'
|
||||||
import HomeworkManagement from '@/views/teacher/course/HomeworkManagement.vue'
|
|
||||||
import PracticeManagement from '@/views/teacher/course/PracticeManagement.vue'
|
import PracticeManagement from '@/views/teacher/course/PracticeManagement.vue'
|
||||||
import QuestionBankManagement from '@/views/teacher/course/QuestionBankManagement.vue'
|
import QuestionBankManagement from '@/views/teacher/course/QuestionBankManagement.vue'
|
||||||
import CertificateManagement from '@/views/teacher/course/CertificateManagement.vue'
|
import CertificateManagement from '@/views/teacher/course/CertificateManagement.vue'
|
||||||
@ -52,6 +51,14 @@ import StatisticsManagement from '@/views/teacher/course/StatisticsManagement.vu
|
|||||||
import NotificationManagement from '@/views/teacher/course/NotificationManagement.vue'
|
import NotificationManagement from '@/views/teacher/course/NotificationManagement.vue'
|
||||||
import GeneralManagement from '@/views/teacher/course/GeneralManagement.vue'
|
import GeneralManagement from '@/views/teacher/course/GeneralManagement.vue'
|
||||||
|
|
||||||
|
// 作业子组件
|
||||||
|
import HomeworkLibrary from '@/views/teacher/course/HomeworkLibrary.vue'
|
||||||
|
import HomeworkReview from '@/views/teacher/course/HomeworkReview.vue'
|
||||||
|
// 练考通菜单组件
|
||||||
|
import ExamLibrary from '@/views/teacher/course/ExamPages/ExamLibrary.vue'
|
||||||
|
import MarkingCenter from '@/views/teacher/course/ExamPages/MarkingCenter.vue'
|
||||||
|
import AddExam from '@/views/teacher/course/ExamPages/AddExam.vue'
|
||||||
|
|
||||||
// ========== 路由配置 ==========
|
// ========== 路由配置 ==========
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
// 管理后台路由
|
// 管理后台路由
|
||||||
@ -125,14 +132,55 @@ const routes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
path: 'homework',
|
path: 'homework',
|
||||||
name: 'HomeworkManagement',
|
name: 'HomeworkManagement',
|
||||||
component: HomeworkManagement,
|
redirect: (to) => `/teacher/course-editor/${to.params.id}/homework/library`,
|
||||||
meta: { title: '作业管理' }
|
children: [
|
||||||
|
{
|
||||||
|
path: 'library',
|
||||||
|
name: 'HomeworkLibrary',
|
||||||
|
component: HomeworkLibrary,
|
||||||
|
meta: { title: '作业库' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'review',
|
||||||
|
name: 'HomeworkReview',
|
||||||
|
component: HomeworkReview,
|
||||||
|
meta: { title: '批阅作业' }
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'practice',
|
path: 'practice',
|
||||||
name: 'PracticeManagement',
|
name: 'PracticeManagement',
|
||||||
component: PracticeManagement,
|
component: PracticeManagement,
|
||||||
meta: { title: '练考通管理' }
|
meta: { title: '练考通管理' },
|
||||||
|
redirect: (to) => `/teacher/course-editor/${to.params.id}/practice/exam-library`,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'exam-library',
|
||||||
|
name: 'ExamLibrary',
|
||||||
|
component: ExamLibrary,
|
||||||
|
meta: {
|
||||||
|
title: '试卷库'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'marking-center',
|
||||||
|
name: 'MarkingCenter',
|
||||||
|
component: MarkingCenter,
|
||||||
|
meta: {
|
||||||
|
title: '阅卷中心'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'add',
|
||||||
|
name: 'AddExam',
|
||||||
|
component: AddExam,
|
||||||
|
meta: {
|
||||||
|
title: '添加试卷',
|
||||||
|
hideSidebar: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'question-bank',
|
path: 'question-bank',
|
||||||
|
@ -128,7 +128,7 @@ const navigateToTeacherDetail = (teacherId: number) => {
|
|||||||
router.push(`/teacher/${teacherId}`)
|
router.push(`/teacher/${teacherId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理箭头点击(保留以防后续需要)
|
// 处理箭头点击
|
||||||
// const toggleCourseInfo = (teacherId: number) => {
|
// const toggleCourseInfo = (teacherId: number) => {
|
||||||
// if (expandedTeacherId.value === teacherId) {
|
// if (expandedTeacherId.value === teacherId) {
|
||||||
// expandedTeacherId.value = null
|
// expandedTeacherId.value = null
|
||||||
@ -143,7 +143,7 @@ const showCourseInfo = (teacherId: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 鼠标离开隐藏课程信息
|
// 鼠标离开隐藏课程信息
|
||||||
const hideCourseInfo = (_teacherId: number) => {
|
const hideCourseInfo = (_: number) => {
|
||||||
expandedTeacherId.value = null
|
expandedTeacherId.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,36 +1,786 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="chapter-management">
|
<div class="chapter-management">
|
||||||
<div class="content-placeholder">
|
<!-- 顶部操作栏 -->
|
||||||
<h2>章节管理</h2>
|
<div class="toolbar">
|
||||||
<p>章节管理功能正在开发中...</p>
|
<h2>全部章节</h2>
|
||||||
|
<div class="toolbar-actions">
|
||||||
|
<button class="btn btn-primary" @click="addChapter">添加章节</button>
|
||||||
|
<button class="btn btn-new" @click="importChapters">导入</button>
|
||||||
|
<button class="btn btn-new" @click="exportChapters">导出</button>
|
||||||
|
<button class="btn btn-danger" @click="deleteSelected" :disabled="selectedChapters.length === 0">删除</button>
|
||||||
|
<div class="search-box">
|
||||||
|
<input type="text" placeholder="请输入想要搜索的内容" v-model="searchKeyword" />
|
||||||
|
<button class="btn btn-search" @click="searchChapters">搜索</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 章节列表表格 -->
|
||||||
|
<div class="table-box">
|
||||||
|
<n-config-provider :locale="zhCN" :date-locale="dateZhCN">
|
||||||
|
<n-data-table :columns="columns" :data="paginatedChapters" :row-key="rowKey"
|
||||||
|
:checked-row-keys="selectedChapters" @update:checked-row-keys="handleCheck" :bordered="false"
|
||||||
|
:single-line="false" size="medium" class="chapter-data-table" :row-class-name="rowClassName" />
|
||||||
|
</n-config-provider>
|
||||||
|
|
||||||
|
<!-- 自定义分页器 -->
|
||||||
|
<div class="custom-pagination">
|
||||||
|
<div class="pagination-content">
|
||||||
|
<div class="page-numbers">
|
||||||
|
<span class="page-number nav-button" :class="{ disabled: currentPage === 1 }" @click="goToPage('first')">
|
||||||
|
首页
|
||||||
|
</span>
|
||||||
|
<span class="page-number nav-button" :class="{ disabled: currentPage === 1 }" @click="goToPage('prev')">
|
||||||
|
上一页
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-for="page in visiblePages" :key="page" class="page-number page-number-bordered"
|
||||||
|
:class="{ active: page === currentPage }" @click="goToPage(page)">
|
||||||
|
{{ page }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-if="showRightEllipsis" class="page-number">...</span>
|
||||||
|
<span v-if="totalPages > 1" class="page-number page-number-bordered" @click="goToPage(totalPages)">
|
||||||
|
{{ totalPages }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="page-number nav-button" :class="{ disabled: currentPage === totalPages }"
|
||||||
|
@click="goToPage('next')">
|
||||||
|
下一页
|
||||||
|
</span>
|
||||||
|
<span class="page-number nav-button" :class="{ disabled: currentPage === totalPages }"
|
||||||
|
@click="goToPage('last')">
|
||||||
|
尾页
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// 章节管理逻辑
|
import { ref, computed, h } from 'vue'
|
||||||
|
import { NButton, useMessage, NDataTable, NConfigProvider, zhCN, dateZhCN } from 'naive-ui'
|
||||||
|
import type { DataTableColumns } from 'naive-ui'
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
// 章节类型定义
|
||||||
|
interface Chapter {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
sort: string | number
|
||||||
|
createTime: string
|
||||||
|
isParent: boolean
|
||||||
|
children?: Chapter[]
|
||||||
|
expanded?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索关键词
|
||||||
|
const searchKeyword = ref('')
|
||||||
|
|
||||||
|
// 选中的章节
|
||||||
|
const selectedChapters = ref<number[]>([])
|
||||||
|
|
||||||
|
// 章节列表数据
|
||||||
|
const chapterList = ref<Chapter[]>([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '第一章 课前准备',
|
||||||
|
type: '-',
|
||||||
|
sort: '-',
|
||||||
|
createTime: '2025.07.25 09:20',
|
||||||
|
isParent: true,
|
||||||
|
expanded: false,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '开课彩蛋:新开始新征程',
|
||||||
|
type: '视频',
|
||||||
|
sort: 1,
|
||||||
|
createTime: '2025.07.25 09:20',
|
||||||
|
isParent: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '课件准备PPT',
|
||||||
|
type: '课件',
|
||||||
|
sort: 2,
|
||||||
|
createTime: '2025.07.25 09:20',
|
||||||
|
isParent: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: '第一节 课程定位与目标',
|
||||||
|
type: '视频',
|
||||||
|
sort: 3,
|
||||||
|
createTime: '2025.07.25 09:20',
|
||||||
|
isParent: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: '第二节 教学安排及学习建议',
|
||||||
|
type: '作业',
|
||||||
|
sort: 4,
|
||||||
|
createTime: '2025.07.25 09:20',
|
||||||
|
isParent: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: '第三节 教学安排及学习建议',
|
||||||
|
type: '考试',
|
||||||
|
sort: 5,
|
||||||
|
createTime: '2025.07.25 09:20',
|
||||||
|
isParent: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: '第二章 课前准备',
|
||||||
|
type: '-',
|
||||||
|
sort: '-',
|
||||||
|
createTime: '2025.07.25 09:20',
|
||||||
|
isParent: true,
|
||||||
|
expanded: false,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
name: '第一节 新开始新征程',
|
||||||
|
type: '视频',
|
||||||
|
sort: 1,
|
||||||
|
createTime: '2025.07.25 09:20',
|
||||||
|
isParent: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
name: '第二节 教学安排及学习建议',
|
||||||
|
type: '课件',
|
||||||
|
sort: 2,
|
||||||
|
createTime: '2025.07.25 09:20',
|
||||||
|
isParent: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
name: '第三章 课前准备',
|
||||||
|
type: '-',
|
||||||
|
sort: '-',
|
||||||
|
createTime: '2025.07.25 09:20',
|
||||||
|
isParent: true,
|
||||||
|
expanded: false,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
name: '第一节 新开始新征程',
|
||||||
|
type: '视频',
|
||||||
|
sort: 1,
|
||||||
|
createTime: '2025.07.25 09:20',
|
||||||
|
isParent: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
name: '第二节 教学安排及学习建议',
|
||||||
|
type: '课件',
|
||||||
|
sort: 2,
|
||||||
|
createTime: '2025.07.25 09:20',
|
||||||
|
isParent: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
name: '第四章 课前准备',
|
||||||
|
type: '-',
|
||||||
|
sort: '-',
|
||||||
|
createTime: '2025.07.25 09:20',
|
||||||
|
isParent: true,
|
||||||
|
expanded: false,
|
||||||
|
children: []
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// 扁平化章节列表(用于显示和分页)
|
||||||
|
const flattenedChapters = computed(() => {
|
||||||
|
const result: Chapter[] = []
|
||||||
|
|
||||||
|
const flatten = (chapters: Chapter[]) => {
|
||||||
|
chapters.forEach(chapter => {
|
||||||
|
result.push(chapter)
|
||||||
|
if (chapter.children && chapter.expanded) {
|
||||||
|
flatten(chapter.children)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
flatten(chapterList.value)
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
|
||||||
|
// 过滤后的章节列表
|
||||||
|
const filteredChapters = computed(() => {
|
||||||
|
if (!searchKeyword.value) {
|
||||||
|
return flattenedChapters.value
|
||||||
|
}
|
||||||
|
return flattenedChapters.value.filter((chapter: Chapter) =>
|
||||||
|
chapter.name.toLowerCase().includes(searchKeyword.value.toLowerCase())
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 分页相关状态
|
||||||
|
const currentPage = ref(1)
|
||||||
|
const pageSize = ref(10)
|
||||||
|
const totalPages = computed(() => Math.ceil(filteredChapters.value.length / pageSize.value))
|
||||||
|
|
||||||
|
// 可见的页码范围
|
||||||
|
const visiblePages = computed(() => {
|
||||||
|
const pages = []
|
||||||
|
const current = currentPage.value
|
||||||
|
const total = totalPages.value
|
||||||
|
|
||||||
|
if (total <= 7) {
|
||||||
|
// 如果总页数小于等于7,显示所有页码
|
||||||
|
for (let i = 1; i <= total; i++) {
|
||||||
|
pages.push(i)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 显示当前页附近的页码
|
||||||
|
const start = Math.max(1, current - 2)
|
||||||
|
const end = Math.min(total, current + 2)
|
||||||
|
|
||||||
|
for (let i = start; i <= end; i++) {
|
||||||
|
pages.push(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages
|
||||||
|
})
|
||||||
|
|
||||||
|
// 是否显示右侧省略号
|
||||||
|
const showRightEllipsis = computed(() => {
|
||||||
|
return currentPage.value < totalPages.value - 3
|
||||||
|
})
|
||||||
|
|
||||||
|
// 分页后的数据
|
||||||
|
const paginatedChapters = computed(() => {
|
||||||
|
const start = (currentPage.value - 1) * pageSize.value
|
||||||
|
const end = start + pageSize.value
|
||||||
|
return filteredChapters.value.slice(start, end)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表格行键
|
||||||
|
const rowKey = (row: Chapter) => row.id
|
||||||
|
|
||||||
|
// 表格选择处理
|
||||||
|
const handleCheck = (rowKeys: number[]) => {
|
||||||
|
selectedChapters.value = rowKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
// 行样式名称
|
||||||
|
const rowClassName = () => {
|
||||||
|
return 'chapter-table-row'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取章节图标
|
||||||
|
// const getChapterIcon = (type: string) => {
|
||||||
|
// return '/images/teacher/章节.png'
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 获取类型图标
|
||||||
|
// const getTypeIcon = (type: string) => {
|
||||||
|
// const iconMap: { [key: string]: string } = {
|
||||||
|
// '视频': '/images/teacher/视频.png',
|
||||||
|
// '课件': '/images/teacher/课件.png',
|
||||||
|
// '作业': '/images/teacher/作业.png',
|
||||||
|
// '考试': '/images/teacher/考试.png'
|
||||||
|
// }
|
||||||
|
// return iconMap[type] || '/images/teacher/默认.png'
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 分页方法
|
||||||
|
const goToPage = (page: string | number) => {
|
||||||
|
if (typeof page === 'string') {
|
||||||
|
switch (page) {
|
||||||
|
case 'first':
|
||||||
|
currentPage.value = 1
|
||||||
|
break
|
||||||
|
case 'prev':
|
||||||
|
if (currentPage.value > 1) currentPage.value--
|
||||||
|
break
|
||||||
|
case 'next':
|
||||||
|
if (currentPage.value < totalPages.value) currentPage.value++
|
||||||
|
break
|
||||||
|
case 'last':
|
||||||
|
currentPage.value = totalPages.value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentPage.value = page
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 展开/收起章节
|
||||||
|
const toggleChapter = (chapter: Chapter) => {
|
||||||
|
if (chapter.isParent && chapter.children) {
|
||||||
|
chapter.expanded = !chapter.expanded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 章节操作方法
|
||||||
|
const addChapter = () => {
|
||||||
|
message.info('添加章节功能')
|
||||||
|
}
|
||||||
|
|
||||||
|
const importChapters = () => {
|
||||||
|
message.info('导入章节功能')
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportChapters = () => {
|
||||||
|
message.info('导出章节功能')
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteSelected = () => {
|
||||||
|
if (selectedChapters.value.length === 0) return
|
||||||
|
if (confirm(`确定要删除选中的 ${selectedChapters.value.length} 个章节吗?`)) {
|
||||||
|
selectedChapters.value.forEach((id: number) => {
|
||||||
|
const index = chapterList.value.findIndex((c: Chapter) => c.id === id)
|
||||||
|
if (index > -1) {
|
||||||
|
chapterList.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
selectedChapters.value = []
|
||||||
|
message.success('删除成功')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchChapters = () => {
|
||||||
|
message.info('搜索章节: ' + searchKeyword.value)
|
||||||
|
currentPage.value = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const editChapter = (chapter: Chapter) => {
|
||||||
|
message.info('编辑章节: ' + chapter.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteChapter = (chapter: Chapter) => {
|
||||||
|
if (confirm('确定要删除这个章节吗?')) {
|
||||||
|
const index = chapterList.value.findIndex((c: Chapter) => c.id === chapter.id)
|
||||||
|
if (index > -1) {
|
||||||
|
chapterList.value.splice(index, 1)
|
||||||
|
message.success('删除成功')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格列配置
|
||||||
|
const columns: DataTableColumns<Chapter> = [
|
||||||
|
{
|
||||||
|
type: 'selection'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '章节名称',
|
||||||
|
key: 'name',
|
||||||
|
minWidth: 350,
|
||||||
|
render: (row: Chapter) => {
|
||||||
|
return h('div', {
|
||||||
|
style: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '20px',
|
||||||
|
cursor: row.isParent ? 'pointer' : 'default',
|
||||||
|
marginLeft: row.isParent ? '40px' : '12px'
|
||||||
|
},
|
||||||
|
onClick: row.isParent ? () => toggleChapter(row) : undefined
|
||||||
|
}, [
|
||||||
|
row.isParent ? h('i', {
|
||||||
|
class: 'n-base-icon',
|
||||||
|
style: {
|
||||||
|
transition: 'transform 0.2s',
|
||||||
|
transform: row.expanded ? 'rotate(90deg)' : 'rotate(0deg)',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
h('svg', {
|
||||||
|
viewBox: '0 0 16 16',
|
||||||
|
fill: 'none',
|
||||||
|
xmlns: 'http://www.w3.org/2000/svg'
|
||||||
|
}, [
|
||||||
|
h('path', {
|
||||||
|
d: 'M5.64645 3.14645C5.45118 3.34171 5.45118 3.65829 5.64645 3.85355L9.79289 8L5.64645 12.1464C5.45118 12.3417 5.45118 12.6583 5.64645 12.8536C5.84171 13.0488 6.15829 13.0488 6.35355 12.8536L10.8536 8.35355C11.0488 8.15829 11.0488 7.84171 10.8536 7.64645L6.35355 3.14645C6.15829 2.95118 5.84171 2.95118 5.64645 3.14645Z',
|
||||||
|
fill: 'currentColor'
|
||||||
|
})
|
||||||
|
])
|
||||||
|
]) : h('div', { style: { width: '16px' } }),
|
||||||
|
h('span', {
|
||||||
|
style: {
|
||||||
|
color: row.isParent ? '#062333' : '#666666',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: row.isParent ? '500' : 'normal',
|
||||||
|
marginLeft: row.isParent ? '0' : '24px'
|
||||||
|
}
|
||||||
|
}, row.name)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '类型',
|
||||||
|
key: 'type',
|
||||||
|
width: 156,
|
||||||
|
render: (row: Chapter) => {
|
||||||
|
if (row.type === '-') {
|
||||||
|
return h('span', { style: { color: '#BABABA' } }, '-')
|
||||||
|
}
|
||||||
|
return h('div', {
|
||||||
|
style: {
|
||||||
|
display: 'inline-block',
|
||||||
|
padding: '4px 8px',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
border: '1px solid #BABABA',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#BABABA'
|
||||||
|
}
|
||||||
|
}, row.type)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '排序',
|
||||||
|
key: 'sort',
|
||||||
|
width: 120,
|
||||||
|
render: (row: Chapter) => {
|
||||||
|
return h('span', { style: { color: '#062333', fontSize: '12px' } }, row.sort)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
key: 'createTime',
|
||||||
|
width: 235,
|
||||||
|
render: (row: Chapter) => {
|
||||||
|
return h('span', { style: { color: '#062333', fontSize: '12px' } }, row.createTime)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 200,
|
||||||
|
render: (row: Chapter) => {
|
||||||
|
return h('div', { style: { display: 'flex', gap: '16px', alignItems: 'center', justifyContent: 'center' } }, [
|
||||||
|
h(NButton, {
|
||||||
|
size: 'small',
|
||||||
|
type: 'info',
|
||||||
|
secondary: true,
|
||||||
|
onClick: () => editChapter(row)
|
||||||
|
}, { default: () => '编辑' }),
|
||||||
|
h(NButton, {
|
||||||
|
size: 'small',
|
||||||
|
type: 'error',
|
||||||
|
secondary: true,
|
||||||
|
onClick: () => deleteChapter(row)
|
||||||
|
}, { default: () => '删除' })
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.chapter-management {
|
.chapter-management {
|
||||||
padding: 20px;
|
width: 1293px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-placeholder {
|
/* 顶部工具栏 */
|
||||||
text-align: center;
|
.toolbar {
|
||||||
padding: 60px 20px;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 25px;
|
||||||
|
background: #fff;
|
||||||
|
padding: 30px 0 20px 30px;
|
||||||
|
border-bottom: 2px solid #F6F6F6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-placeholder h2 {
|
.toolbar h2 {
|
||||||
font-size: 24px;
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
color: #333;
|
color: #333;
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-placeholder p {
|
.toolbar-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 7px 16px;
|
||||||
|
border: none;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #0288D1;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-new {
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #0288D1;
|
||||||
|
color: #0288D1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-default {
|
||||||
|
background: white;
|
||||||
|
color: #999999;
|
||||||
|
border: 1px solid #999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-default:hover {
|
||||||
|
border-color: #1890ff;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: white;
|
||||||
|
color: #FF4D4F;
|
||||||
|
border: 1px solid #FF4D4F;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover {
|
||||||
|
background: #ff7875;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid #F1F3F4;
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box input {
|
||||||
|
border: none;
|
||||||
|
padding: 6px 12px;
|
||||||
|
outline: none;
|
||||||
|
width: 200px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-search {
|
||||||
|
background: #0288D1;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 6px 16px;
|
||||||
|
cursor: pointer;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #666;
|
}
|
||||||
|
|
||||||
|
.btn-search:hover {
|
||||||
|
background: #0277bd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-box {
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Naive UI 表格样式定制 */
|
||||||
|
:deep(.chapter-data-table) {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 40px;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 500px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格头部样式 */
|
||||||
|
:deep(.chapter-data-table .n-data-table-thead) {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.chapter-data-table .n-data-table-th) {
|
||||||
|
background: #fafafa;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #062333;
|
||||||
|
font-size: 14px;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
padding: 12px 8px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格行样式 */
|
||||||
|
:deep(.chapter-data-table .n-data-table-td) {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #062333;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
padding: 12px 8px;
|
||||||
|
vertical-align: middle;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 所有列居中对齐 */
|
||||||
|
:deep(.chapter-data-table .n-data-table-td) {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.chapter-data-table .n-data-table-th) {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.chapter-data-table .n-data-table-tr:hover) {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 复选框样式 */
|
||||||
|
:deep(.chapter-data-table .n-checkbox) {
|
||||||
|
--n-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 隐藏Naive UI默认的展开触发器 */
|
||||||
|
:deep(.chapter-data-table .n-data-table-expand-trigger) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 隐藏Naive UI默认的展开占位符 */
|
||||||
|
:deep(.chapter-data-table .n-data-table-expand-placeholder) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮组样式调整 */
|
||||||
|
:deep(.chapter-data-table .n-button) {
|
||||||
|
font-size: 12px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 0 12px;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 操作按钮样式 - 只有边框和文字颜色,无背景色 */
|
||||||
|
:deep(.chapter-data-table .n-button--info-type.n-button--secondary) {
|
||||||
|
background-color: transparent !important;
|
||||||
|
border: 1px solid #0288D1;
|
||||||
|
color: #0288D1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.chapter-data-table .n-button--info-type.n-button--secondary:hover) {
|
||||||
|
background-color: rgba(32, 128, 240, 0.05) !important;
|
||||||
|
border: 1px solid #0288D1;
|
||||||
|
color: #248DD3;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.chapter-data-table .n-button--error-type.n-button--secondary) {
|
||||||
|
background-color: transparent !important;
|
||||||
|
border: 1px solid #FF4D4F;
|
||||||
|
color: #FD8485;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.chapter-data-table .n-button--error-type.n-button--secondary:hover) {
|
||||||
|
background-color: rgba(208, 48, 80, 0.05) !important;
|
||||||
|
border: 1px solid #FF4D4F;
|
||||||
|
color: #FD8485;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格行样式 */
|
||||||
|
:deep(.chapter-table-row) {
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.chapter-table-row:hover) {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 自定义分页器样式 */
|
||||||
|
.custom-pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
background: #fff;
|
||||||
|
padding: 20px 0;
|
||||||
|
margin-top: auto;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-numbers {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-number {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
line-height: 38px;
|
||||||
|
text-align: center;
|
||||||
|
color: #333;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 0 5px;
|
||||||
|
margin: 0 4px;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-number-bordered {
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-number.active {
|
||||||
|
background-color: #0088D1;
|
||||||
|
color: white;
|
||||||
|
border-color: #0088D1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-number:hover:not(.disabled) {
|
||||||
|
color: #0088D1;
|
||||||
|
border-color: #0088D1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-number.disabled {
|
||||||
|
color: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-number.disabled:hover {
|
||||||
|
color: #ccc;
|
||||||
|
border-color: #d9d9d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-button {
|
||||||
|
padding: 0 8px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-button:hover:not(.disabled) {
|
||||||
|
color: #0088D1;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="course-editor">
|
<div class="course-editor">
|
||||||
<!-- 左侧导航菜单 -->
|
<!-- 左侧导航菜单 -->
|
||||||
<div class="sidebar">
|
<div class="sidebar" v-if="showSidebar">
|
||||||
<router-link
|
<router-link
|
||||||
:to="`/teacher/course-editor/${courseId}/courseware`"
|
:to="`/teacher/course-editor/${courseId}/courseware`"
|
||||||
class="menu-item"
|
class="menu-item"
|
||||||
@ -13,15 +13,10 @@
|
|||||||
/>
|
/>
|
||||||
<span>课件</span>
|
<span>课件</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link :to="`/teacher/course-editor/${courseId}/chapters`" class="menu-item"
|
||||||
:to="`/teacher/course-editor/${courseId}/chapters`"
|
:class="{ active: $route.path.includes('chapters') }">
|
||||||
class="menu-item"
|
<img :src="$route.path.includes('chapters') ? '/images/teacher/章节-选中.png' : '/images/teacher/章节.png'"
|
||||||
:class="{ active: $route.path.includes('chapters') }"
|
alt="章节" />
|
||||||
>
|
|
||||||
<img
|
|
||||||
:src="$route.path.includes('chapters') ? '/images/teacher/章节-选中.png' : '/images/teacher/章节.png'"
|
|
||||||
alt="章节"
|
|
||||||
/>
|
|
||||||
<span>章节</span>
|
<span>章节</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link
|
||||||
@ -35,17 +30,43 @@
|
|||||||
/>
|
/>
|
||||||
<span>作业</span>
|
<span>作业</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
|
||||||
:to="`/teacher/course-editor/${courseId}/practice`"
|
<!-- 练考通父菜单 -->
|
||||||
|
<div
|
||||||
class="menu-item"
|
class="menu-item"
|
||||||
:class="{ active: $route.path.includes('practice') }"
|
:class="{ active: $route.path.includes('practice') }"
|
||||||
|
@click="togglePracticeMenu"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="$route.path.includes('practice') ? '/images/teacher/练考通-选中.png' : '/images/teacher/练考通.png'"
|
:src="($route.path.includes('practice')) ? '/images/teacher/练考通-选中.png' : '/images/teacher/练考通.png'"
|
||||||
alt="练考通"
|
alt="练考通"
|
||||||
/>
|
/>
|
||||||
<span>练考通</span>
|
<span>练考通</span>
|
||||||
|
<div class="expand-icon" :class="{ expanded: practiceMenuExpanded }">
|
||||||
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
|
||||||
|
<path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 练考通子菜单 -->
|
||||||
|
<div class="submenu" :class="{ expanded: practiceMenuExpanded }">
|
||||||
|
<router-link
|
||||||
|
:to="`/teacher/course-editor/${courseId}/practice/exam-library`"
|
||||||
|
class="submenu-item"
|
||||||
|
:class="{ active: $route.path.includes('exam-library') }"
|
||||||
|
>
|
||||||
|
<span>试卷库</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<router-link
|
||||||
|
:to="`/teacher/course-editor/${courseId}/practice/marking-center`"
|
||||||
|
class="submenu-item"
|
||||||
|
:class="{ active: $route.path.includes('marking-center') }"
|
||||||
|
>
|
||||||
|
<span>阅卷中心</span>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
<router-link
|
<router-link
|
||||||
:to="`/teacher/course-editor/${courseId}/question-bank`"
|
:to="`/teacher/course-editor/${courseId}/question-bank`"
|
||||||
class="menu-item"
|
class="menu-item"
|
||||||
@ -57,59 +78,34 @@
|
|||||||
/>
|
/>
|
||||||
<span>题库</span>
|
<span>题库</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link :to="`/teacher/course-editor/${courseId}/certificate`" class="menu-item"
|
||||||
:to="`/teacher/course-editor/${courseId}/certificate`"
|
:class="{ active: $route.path.includes('certificate') }">
|
||||||
class="menu-item"
|
<img :src="$route.path.includes('certificate') ? '/images/teacher/证书-选中.png' : '/images/teacher/证书.png'"
|
||||||
:class="{ active: $route.path.includes('certificate') }"
|
alt="证书" />
|
||||||
>
|
|
||||||
<img
|
|
||||||
:src="$route.path.includes('certificate') ? '/images/teacher/证书-选中.png' : '/images/teacher/证书.png'"
|
|
||||||
alt="证书"
|
|
||||||
/>
|
|
||||||
<span>证书</span>
|
<span>证书</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link :to="`/teacher/course-editor/${courseId}/discussion`" class="menu-item"
|
||||||
:to="`/teacher/course-editor/${courseId}/discussion`"
|
:class="{ active: $route.path.includes('discussion') }">
|
||||||
class="menu-item"
|
<img :src="$route.path.includes('discussion') ? '/images/teacher/讨论-选中.png' : '/images/teacher/讨论.png'"
|
||||||
:class="{ active: $route.path.includes('discussion') }"
|
alt="讨论" />
|
||||||
>
|
|
||||||
<img
|
|
||||||
:src="$route.path.includes('discussion') ? '/images/teacher/讨论-选中.png' : '/images/teacher/讨论.png'"
|
|
||||||
alt="讨论"
|
|
||||||
/>
|
|
||||||
<span>讨论</span>
|
<span>讨论</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link :to="`/teacher/course-editor/${courseId}/statistics`" class="menu-item"
|
||||||
:to="`/teacher/course-editor/${courseId}/statistics`"
|
:class="{ active: $route.path.includes('statistics') }">
|
||||||
class="menu-item"
|
<img :src="$route.path.includes('statistics') ? '/images/teacher/统计-选中.png' : '/images/teacher/统计.png'"
|
||||||
:class="{ active: $route.path.includes('statistics') }"
|
alt="统计" />
|
||||||
>
|
|
||||||
<img
|
|
||||||
:src="$route.path.includes('statistics') ? '/images/teacher/统计-选中.png' : '/images/teacher/统计.png'"
|
|
||||||
alt="统计"
|
|
||||||
/>
|
|
||||||
<span>统计</span>
|
<span>统计</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link :to="`/teacher/course-editor/${courseId}/notification`" class="menu-item"
|
||||||
:to="`/teacher/course-editor/${courseId}/notification`"
|
:class="{ active: $route.path.includes('notification') }">
|
||||||
class="menu-item"
|
<img :src="$route.path.includes('notification') ? '/images/teacher/通知-选中.png' : '/images/teacher/通知.png'"
|
||||||
:class="{ active: $route.path.includes('notification') }"
|
alt="通知" />
|
||||||
>
|
|
||||||
<img
|
|
||||||
:src="$route.path.includes('notification') ? '/images/teacher/通知-选中.png' : '/images/teacher/通知.png'"
|
|
||||||
alt="通知"
|
|
||||||
/>
|
|
||||||
<span>通知</span>
|
<span>通知</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link :to="`/teacher/course-editor/${courseId}/management`" class="menu-item"
|
||||||
:to="`/teacher/course-editor/${courseId}/management`"
|
:class="{ active: $route.path.includes('management') }">
|
||||||
class="menu-item"
|
<img :src="$route.path.includes('management') ? '/images/teacher/管理-选中.png' : '/images/teacher/管理.png'"
|
||||||
:class="{ active: $route.path.includes('management') }"
|
alt="管理" />
|
||||||
>
|
|
||||||
<img
|
|
||||||
:src="$route.path.includes('management') ? '/images/teacher/管理-选中.png' : '/images/teacher/管理.png'"
|
|
||||||
alt="管理"
|
|
||||||
/>
|
|
||||||
<span>管理</span>
|
<span>管理</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
@ -122,12 +118,34 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
// 获取课程ID
|
// 获取课程ID
|
||||||
const courseId = route.params.id
|
const courseId = route.params.id
|
||||||
|
|
||||||
|
// 控制练考通菜单的展开状态
|
||||||
|
const practiceMenuExpanded = ref(false)
|
||||||
|
|
||||||
|
// 切换练考通菜单展开状态
|
||||||
|
const togglePracticeMenu = () => {
|
||||||
|
practiceMenuExpanded.value = !practiceMenuExpanded.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听路由变化,如果当前路由是子菜单,自动展开父菜单
|
||||||
|
watch(() => route.path, (newPath) => {
|
||||||
|
if (newPath.includes('exam-library') || newPath.includes('marking-center')) {
|
||||||
|
practiceMenuExpanded.value = true
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
|
||||||
|
// 判断是否显示侧边菜单栏
|
||||||
|
const showSidebar = computed(() => {
|
||||||
|
return route.meta.hideSidebar !== true
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -178,14 +196,159 @@ const courseId = route.params.id
|
|||||||
.menu-item img {
|
.menu-item img {
|
||||||
margin-left: 40px;
|
margin-left: 40px;
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
width: 16px;
|
width: 18px;
|
||||||
height: 16px;
|
height: 18px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item span {
|
.menu-item span {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 作业二级导航样式 */
|
||||||
|
.menu-group {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #666;
|
||||||
|
border-radius: 5px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-header:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-header img {
|
||||||
|
margin-left: 40px;
|
||||||
|
margin-top: 1px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-header span {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #666;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-header .n-base-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-header .n-base-icon.expanded {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 20px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item::before {
|
||||||
|
content: '';
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
margin-left: 40px;
|
||||||
|
margin-right: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item.active {
|
||||||
|
background: #F5F8FB;
|
||||||
|
color: #0288D1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item.active span {
|
||||||
|
color: #0288D1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item span {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #666;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-icon {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-icon.expanded {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 子菜单样式 */
|
||||||
|
.submenu {
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.3s ease;
|
||||||
|
margin-left: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu.expanded {
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 20px 12px 60px;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item:hover {
|
||||||
|
background: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item.active {
|
||||||
|
background: #E3F2FD;
|
||||||
|
color: #0288D1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item span {
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 右侧内容区域 */
|
/* 右侧内容区域 */
|
||||||
|
1544
src/views/teacher/course/ExamPages/AddExam.vue
Normal file
1544
src/views/teacher/course/ExamPages/AddExam.vue
Normal file
File diff suppressed because it is too large
Load Diff
236
src/views/teacher/course/ExamPages/ExamLibrary.vue
Normal file
236
src/views/teacher/course/ExamPages/ExamLibrary.vue
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
<template>
|
||||||
|
<n-config-provider :theme-overrides="themeOverrides">
|
||||||
|
<div class="exam-library-container">
|
||||||
|
<div class="header-section">
|
||||||
|
<h1 class="title">试卷库</h1>
|
||||||
|
<n-space class="actions-group">
|
||||||
|
<n-button type="primary" @click="handleAddExam">添加试卷</n-button>
|
||||||
|
<n-button ghost>导入</n-button>
|
||||||
|
<n-button ghost>导出</n-button>
|
||||||
|
<n-button type="error" ghost>删除</n-button>
|
||||||
|
<n-input placeholder="请输入想要搜索的内容" />
|
||||||
|
<n-button type="primary">搜索</n-button>
|
||||||
|
</n-space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<n-data-table :columns="columns" :data="examData" :row-key="(row: Exam) => row.id"
|
||||||
|
@update:checked-row-keys="handleCheck" class="exam-table" :single-line="false" />
|
||||||
|
|
||||||
|
<div class="pagination-container">
|
||||||
|
<n-pagination v-model:page="currentPage" :page-count="totalPages" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-config-provider>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { h, ref, VNode } from 'vue';
|
||||||
|
import { NButton, NSpace, useMessage, NDataTable, NPagination, NInput, NConfigProvider } from 'naive-ui';
|
||||||
|
import type { DataTableColumns, GlobalThemeOverrides } from 'naive-ui';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 定义考试条目的数据类型
|
||||||
|
type Exam = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
category: '练习' | '考试';
|
||||||
|
questionCount: number;
|
||||||
|
chapter: string;
|
||||||
|
totalScore: number;
|
||||||
|
difficulty: '易' | '中' | '难';
|
||||||
|
status: '发布中' | '未发布' | '已结束';
|
||||||
|
startTime: string;
|
||||||
|
endTime: string;
|
||||||
|
creator: string;
|
||||||
|
creationTime: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 自定义主题颜色
|
||||||
|
const themeOverrides: GlobalThemeOverrides = {
|
||||||
|
common: {
|
||||||
|
primaryColor: '#0288D1',
|
||||||
|
primaryColorHover: '#0277BD', // A slightly darker shade for hover
|
||||||
|
primaryColorPressed: '#01579B', // A darker shade for pressed
|
||||||
|
errorColor: '#FF4D4F',
|
||||||
|
errorColorHover: '#E54547',
|
||||||
|
errorColorPressed: '#C0383A',
|
||||||
|
},
|
||||||
|
Button: {
|
||||||
|
// For ghost primary buttons
|
||||||
|
textColorGhostPrimary: '#0288D1',
|
||||||
|
borderPrimary: '1px solid #0288D1',
|
||||||
|
// For ghost error buttons
|
||||||
|
textColorGhostError: '#FF4D4F',
|
||||||
|
borderError: '1px solid #FF4D4F',
|
||||||
|
},
|
||||||
|
Pagination: {
|
||||||
|
itemColorActive: '#0288D1',
|
||||||
|
itemTextColorActive: '#fff',
|
||||||
|
itemBorderActive: '1px solid #0288D1',
|
||||||
|
itemColorActiveHover: '#0277BD', // 使用比主色稍深一点的颜色
|
||||||
|
itemTextColorActiveHover: '#fff', // 保持文字为白色
|
||||||
|
itemBorderActiveHover: '1px solid #0277BD' // 边框颜色也相应加深
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const message = useMessage();
|
||||||
|
|
||||||
|
// 创建表格列的函数
|
||||||
|
const createColumns = ({
|
||||||
|
handleAction,
|
||||||
|
}: {
|
||||||
|
handleAction: (action: string, rowData: Exam) => void;
|
||||||
|
}): DataTableColumns<Exam> => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'selection',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '序号',
|
||||||
|
key: 'id',
|
||||||
|
width: 60,
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '试卷名称',
|
||||||
|
key: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '分类',
|
||||||
|
key: 'category',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '题量',
|
||||||
|
key: 'questionCount',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '所属章节',
|
||||||
|
key: 'chapter',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '总分',
|
||||||
|
key: 'totalScore',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '难度',
|
||||||
|
key: 'difficulty',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
key: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '起止时间',
|
||||||
|
key: 'startTime',
|
||||||
|
render(row) {
|
||||||
|
return `${row.startTime} - ${row.endTime}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建人',
|
||||||
|
key: 'creator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
key: 'creationTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
render(row) {
|
||||||
|
const buttons: VNode[] = [];
|
||||||
|
if (row.status === '发布中') {
|
||||||
|
buttons.push(
|
||||||
|
h(NButton, { size: 'small', type: 'primary', ghost: true, style: 'margin: 0 3px;', onClick: () => handleAction('批阅', row) }, { default: () => '批阅' })
|
||||||
|
);
|
||||||
|
} else if (row.status === '未发布') {
|
||||||
|
buttons.push(
|
||||||
|
h(NButton, { size: 'small', type: 'primary', style: 'margin: 0 3px;', onClick: () => handleAction('发布', row) }, { default: () => '发布' })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.push(
|
||||||
|
h(NButton, { size: 'small', type: 'primary', ghost: true, style: 'margin: 0 3px;', onClick: () => handleAction('编辑', row) }, { default: () => '编辑' })
|
||||||
|
);
|
||||||
|
buttons.push(
|
||||||
|
h(NButton, { size: 'small', type: 'error', ghost: true, style: 'margin: 0 3px;', onClick: () => handleAction('删除', row) }, { default: () => '删除' })
|
||||||
|
);
|
||||||
|
return h(NSpace, {}, { default: () => buttons });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 模拟数据
|
||||||
|
const examData = ref<Exam[]>([
|
||||||
|
{ id: 1, name: '试卷名称试卷名称', category: '练习', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '发布中', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' },
|
||||||
|
{ id: 2, name: '试卷名称试卷名称', category: '考试', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '未发布', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' },
|
||||||
|
{ id: 3, name: '试卷名称试卷名称', category: '练习', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '发布中', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' },
|
||||||
|
{ id: 4, name: '试卷名称试卷名称', category: '考试', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '发布中', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' },
|
||||||
|
{ id: 5, name: '试卷名称试卷名称', category: '练习', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '发布中', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' },
|
||||||
|
{ id: 6, name: '试卷名称试卷名称', category: '考试', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '未发布', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' },
|
||||||
|
{ id: 7, name: '试卷名称试卷名称', category: '练习', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '发布中', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' },
|
||||||
|
{ id: 8, name: '试卷名称试卷名称', category: '考试', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '未发布', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const columns = createColumns({
|
||||||
|
handleAction: (action, row) => {
|
||||||
|
message.info(`执行操作: ${action} on row ${row.id}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkedRowKeys = ref<Array<string | number>>([]);
|
||||||
|
const handleCheck = (rowKeys: Array<string | number>) => {
|
||||||
|
checkedRowKeys.value = rowKeys;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 分页状态
|
||||||
|
const currentPage = ref(1);
|
||||||
|
const totalPages = ref(29); // 来自图片
|
||||||
|
|
||||||
|
const handleAddExam = () => {
|
||||||
|
// 这里可以添加导航到添加试卷页面的逻辑
|
||||||
|
router.push({ name: 'AddExam' });
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.exam-library-container {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
border-bottom: 1px solid #E6E6E6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-table {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
11
src/views/teacher/course/ExamPages/MarkingCenter.vue
Normal file
11
src/views/teacher/course/ExamPages/MarkingCenter.vue
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>阅卷中心</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
720
src/views/teacher/course/HomeworkLibrary.vue
Normal file
720
src/views/teacher/course/HomeworkLibrary.vue
Normal file
@ -0,0 +1,720 @@
|
|||||||
|
<template>
|
||||||
|
<div class="homework-library">
|
||||||
|
<div class="toolbar">
|
||||||
|
<h2>作业库</h2>
|
||||||
|
<div class="toolbar-actions">
|
||||||
|
<button class="btn btn-primary">添加作业</button>
|
||||||
|
<button class="btn btn-new">导入</button>
|
||||||
|
<button class="btn btn-new">导出</button>
|
||||||
|
<button class="btn btn-danger">删除</button>
|
||||||
|
<div class="search-box">
|
||||||
|
<input type="text" placeholder="请输入想要搜索的内容" />
|
||||||
|
<button class="btn btn-search">搜索</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-area">
|
||||||
|
<div class="table-container">
|
||||||
|
<n-config-provider :locale="zhCN" :date-locale="dateZhCN">
|
||||||
|
<n-data-table :columns="columns" :data="homeworkList" :row-key="rowKey" :checked-row-keys="selectedHomework"
|
||||||
|
@update:checked-row-keys="handleCheck" :bordered="false" :single-line="false" size="medium"
|
||||||
|
class="homework-data-table" />
|
||||||
|
</n-config-provider>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pagination-container">
|
||||||
|
<!-- 自定义分页器 -->
|
||||||
|
<div class="custom-pagination">
|
||||||
|
<div class="pagination-content">
|
||||||
|
<div class="page-numbers">
|
||||||
|
<span class="page-number nav-button" :class="{ disabled: currentPage === 1 }" @click="goToPage('first')">
|
||||||
|
首页
|
||||||
|
</span>
|
||||||
|
<span class="page-number nav-button" :class="{ disabled: currentPage === 1 }" @click="goToPage('prev')">
|
||||||
|
上一页
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-for="page in visiblePages" :key="page" class="page-number page-number-bordered"
|
||||||
|
:class="{ active: page === currentPage }" @click="goToPage(page)">
|
||||||
|
{{ page }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-if="showRightEllipsis" class="page-number">...</span>
|
||||||
|
<span v-if="totalPages > 1" class="page-number page-number-bordered" @click="goToPage(totalPages)">
|
||||||
|
{{ totalPages }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="page-number nav-button" :class="{ disabled: currentPage === totalPages }"
|
||||||
|
@click="goToPage('next')">
|
||||||
|
下一页
|
||||||
|
</span>
|
||||||
|
<span class="page-number nav-button" :class="{ disabled: currentPage === totalPages }"
|
||||||
|
@click="goToPage('last')">
|
||||||
|
尾页
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, h, computed } from 'vue'
|
||||||
|
import { NButton, NDropdown, NDataTable, NConfigProvider, zhCN, dateZhCN } from 'naive-ui'
|
||||||
|
import type { DataTableColumns } from 'naive-ui'
|
||||||
|
|
||||||
|
// 作业类型定义
|
||||||
|
interface HomeworkItem {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
chapter: string
|
||||||
|
class: string
|
||||||
|
creator: string
|
||||||
|
createTime: string
|
||||||
|
isTop: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中的作业行
|
||||||
|
const selectedHomework = ref<number[]>([])
|
||||||
|
|
||||||
|
// 分页相关
|
||||||
|
const currentPage = ref(1)
|
||||||
|
const pageSize = ref(10)
|
||||||
|
const totalCount = ref(6)
|
||||||
|
|
||||||
|
// 计算总页数
|
||||||
|
const totalPages = computed(() => Math.ceil(totalCount.value / pageSize.value))
|
||||||
|
|
||||||
|
// 计算可见页码
|
||||||
|
const visiblePages = computed(() => {
|
||||||
|
const pages = []
|
||||||
|
const maxVisible = 5
|
||||||
|
let start = Math.max(1, currentPage.value - Math.floor(maxVisible / 2))
|
||||||
|
let end = Math.min(totalPages.value, start + maxVisible - 1)
|
||||||
|
|
||||||
|
if (end - start + 1 < maxVisible) {
|
||||||
|
start = Math.max(1, end - maxVisible + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = start; i <= end; i++) {
|
||||||
|
pages.push(i)
|
||||||
|
}
|
||||||
|
return pages
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算是否显示右侧省略号
|
||||||
|
const showRightEllipsis = computed(() => {
|
||||||
|
return totalPages.value > 5 && currentPage.value < totalPages.value - 2
|
||||||
|
})
|
||||||
|
|
||||||
|
// 分页导航函数
|
||||||
|
const goToPage = (target: number | string) => {
|
||||||
|
if (typeof target === 'string') {
|
||||||
|
switch (target) {
|
||||||
|
case 'first':
|
||||||
|
currentPage.value = 1
|
||||||
|
break
|
||||||
|
case 'prev':
|
||||||
|
if (currentPage.value > 1) currentPage.value--
|
||||||
|
break
|
||||||
|
case 'next':
|
||||||
|
if (currentPage.value < totalPages.value) currentPage.value++
|
||||||
|
break
|
||||||
|
case 'last':
|
||||||
|
currentPage.value = totalPages.value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentPage.value = target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 作业列表数据
|
||||||
|
const homeworkList = ref<HomeworkItem[]>([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '作业名称作业名称作业名称',
|
||||||
|
chapter: '第一节 开课彩蛋新开始',
|
||||||
|
class: '班级一、班级二',
|
||||||
|
creator: '王建国',
|
||||||
|
createTime: '2025.07.25 09:20',
|
||||||
|
isTop: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '作业名称作业名称作业名称',
|
||||||
|
chapter: '第一节 开课彩蛋新开始',
|
||||||
|
class: '班级一、班级二',
|
||||||
|
creator: '王建国',
|
||||||
|
createTime: '2025.07.25 09:20',
|
||||||
|
isTop: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '作业名称作业名称作业名称',
|
||||||
|
chapter: '第一节 开课彩蛋新开始',
|
||||||
|
class: '班级一、班级二',
|
||||||
|
creator: '王建国',
|
||||||
|
createTime: '2025.07.25 09:20',
|
||||||
|
isTop: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: '作业名称作业名称作业名称',
|
||||||
|
chapter: '-',
|
||||||
|
class: '班级一、班级二',
|
||||||
|
creator: '王建国',
|
||||||
|
createTime: '2025.07.25 09:20',
|
||||||
|
isTop: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: '作业名称作业名称作业名称',
|
||||||
|
chapter: '第一节 开课彩蛋新开始',
|
||||||
|
class: '班级一、班级二',
|
||||||
|
creator: '王建国',
|
||||||
|
createTime: '2025.07.25 09:20',
|
||||||
|
isTop: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: '作业名称作业名称作业名称',
|
||||||
|
chapter: '第一节 开课彩蛋新开始',
|
||||||
|
class: '班级一、班级二',
|
||||||
|
creator: '王建国',
|
||||||
|
createTime: '2025.07.25 09:20',
|
||||||
|
isTop: false
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// 行键
|
||||||
|
const rowKey = (row: HomeworkItem) => row.id
|
||||||
|
|
||||||
|
// 处理复选框选择
|
||||||
|
const handleCheck = (keys: number[]) => {
|
||||||
|
selectedHomework.value = keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
const columns: DataTableColumns<HomeworkItem> = [
|
||||||
|
{
|
||||||
|
type: 'selection',
|
||||||
|
width: 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '序号',
|
||||||
|
key: 'index',
|
||||||
|
width: 80,
|
||||||
|
render: (_, index) => index + 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '作业名称',
|
||||||
|
key: 'name',
|
||||||
|
render: (row) => {
|
||||||
|
return h('div', { style: 'display: flex; align-items: center; gap: 8px;' }, [
|
||||||
|
h('span', { class: 'homework-name' }, row.name),
|
||||||
|
row.isTop ? h('span', { class: 'tag tag-pinned' }, '置顶') : null
|
||||||
|
])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '所属章节',
|
||||||
|
key: 'chapter'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '绑定班级',
|
||||||
|
key: 'class'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建人',
|
||||||
|
key: 'creator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
key: 'createTime'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 200,
|
||||||
|
render: (row) => {
|
||||||
|
return h('div', { style: 'display: flex; gap: 8px; align-items: center;' }, [
|
||||||
|
h(NButton, {
|
||||||
|
size: 'small',
|
||||||
|
type: 'info',
|
||||||
|
secondary: true,
|
||||||
|
onClick: () => editHomework(row.id)
|
||||||
|
}, { default: () => '编辑' }),
|
||||||
|
h(NButton, {
|
||||||
|
size: 'small',
|
||||||
|
type: 'error',
|
||||||
|
secondary: true,
|
||||||
|
onClick: () => deleteHomework(row.id)
|
||||||
|
}, { default: () => '删除' }),
|
||||||
|
h(NDropdown, {
|
||||||
|
trigger: 'click',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: '重命名',
|
||||||
|
key: 'rename',
|
||||||
|
icon: () => h('img', { src: '/public/images/teacher/重命名.png', style: 'width: 10px; height: 10px;' })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: row.isTop ? '取消置顶' : '置顶',
|
||||||
|
key: 'toggleTop',
|
||||||
|
icon: () => h('img', { src: '/public/images/teacher/置顶.png', style: 'width: 10px; height: 10px;' })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '权限设置',
|
||||||
|
key: 'permissions',
|
||||||
|
icon: () => h('img', { src: '/public/images/teacher/权限设置.png', style: 'width: 10px; height: 10px;' })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '下载',
|
||||||
|
key: 'download',
|
||||||
|
icon: () => h('img', { src: '/public/images/teacher/下载.png', style: 'width: 10px; height: 10px;' })
|
||||||
|
}
|
||||||
|
],
|
||||||
|
onSelect: (key) => handleAction(key, row)
|
||||||
|
}, {
|
||||||
|
default: () => h(NButton, {
|
||||||
|
size: 'small',
|
||||||
|
type: 'info',
|
||||||
|
secondary: true
|
||||||
|
}, { default: () => '更多' })
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 编辑作业
|
||||||
|
const editHomework = (id: number) => {
|
||||||
|
console.log('编辑作业:', id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除作业
|
||||||
|
const deleteHomework = (id: number) => {
|
||||||
|
console.log('删除作业:', id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理下拉菜单操作
|
||||||
|
const handleAction = (key: string, row: HomeworkItem) => {
|
||||||
|
console.log('操作:', key, row)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.homework-library {
|
||||||
|
width: 1293px;
|
||||||
|
padding: 10px 20px 20px 0;
|
||||||
|
height: 100%;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
border-bottom: 2px solid #F6F6F6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-area {
|
||||||
|
height: calc(100% - 80px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-bottom: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 7px 16px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
background: #fff;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
border-color: #40a9ff;
|
||||||
|
color: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #0288D1;
|
||||||
|
border-color: #0288D1;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #40a9ff;
|
||||||
|
border-color: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-new {
|
||||||
|
background: #fff;
|
||||||
|
border-color: #0288D1;
|
||||||
|
color: #0288D1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-new:hover {
|
||||||
|
background: #0288D1;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-default {
|
||||||
|
background: #fff;
|
||||||
|
border-color: #0288D1;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-default:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
border-color: #FF4D4F;
|
||||||
|
color: #FF4D4F;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover {
|
||||||
|
background: #FF4D4F;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-small {
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-info {
|
||||||
|
border-color: #0288D1;
|
||||||
|
color: #0288D1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-more {
|
||||||
|
border-color: #0288D1;
|
||||||
|
color: #0288D1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid #F1F3F4;
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box input {
|
||||||
|
border: none;
|
||||||
|
padding: 6px 12px;
|
||||||
|
outline: none;
|
||||||
|
width: 200px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box input::placeholder {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-search {
|
||||||
|
background: #0288D1;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 6px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-search:hover {
|
||||||
|
background: #0288D1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Naive UI 表格样式定制 */
|
||||||
|
:deep(.homework-data-table) {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-data-table-table) {
|
||||||
|
border: 1px solid #F1F3F4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格头部样式 */
|
||||||
|
:deep(.homework-data-table .n-data-table-thead) {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.homework-data-table .n-data-table-th) {
|
||||||
|
background: #fafafa;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #062333;
|
||||||
|
font-size: 14px;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
padding: 12px 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格行样式 */
|
||||||
|
:deep(.homework-data-table .n-data-table-td) {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #062333;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
padding: 12px 8px;
|
||||||
|
vertical-align: middle;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 名称列也居中 */
|
||||||
|
:deep(.homework-data-table .n-data-table-td[data-col-key="name"]) {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.homework-data-table .n-data-table-th[data-col-key="name"]) {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.homework-data-table .n-data-table-tr:hover) {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 复选框样式 */
|
||||||
|
:deep(.homework-data-table .n-checkbox) {
|
||||||
|
--n-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮组样式调整 */
|
||||||
|
:deep(.homework-data-table .n-button) {
|
||||||
|
font-size: 12px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 0 12px;
|
||||||
|
margin: 2px;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 编辑按钮样式 */
|
||||||
|
:deep(.homework-data-table .n-button--info-type) {
|
||||||
|
border: 1px solid #0288D1 !important;
|
||||||
|
color: #0288D1 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.homework-data-table .n-button--info-type:hover) {
|
||||||
|
border-color: #0288D1 !important;
|
||||||
|
color: #0288D1 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 删除按钮样式 */
|
||||||
|
:deep(.homework-data-table .n-button--error-type) {
|
||||||
|
border: 1px solid #FF4D4F !important;
|
||||||
|
color: #FF4D4F !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.homework-data-table .n-button--error-type:hover) {
|
||||||
|
border-color: #FF4D4F !important;
|
||||||
|
color: #FF4D4F !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 更多按钮样式 */
|
||||||
|
:deep(.homework-data-table .n-button--info-type:last-child) {
|
||||||
|
border: 1px solid #4165D7 !important;
|
||||||
|
color: #4165D7 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.homework-data-table .n-button--info-type:last-child:hover) {
|
||||||
|
border-color: #4165D7 !important;
|
||||||
|
color: #4165D7 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 下拉菜单样式 */
|
||||||
|
:deep(.n-dropdown-option) {
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #062333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分页器容器 */
|
||||||
|
.pagination-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px 0;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: #fff;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 自定义分页器样式 */
|
||||||
|
.custom-pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
background: #fff;
|
||||||
|
padding: 20px 0;
|
||||||
|
margin-top: auto;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-numbers {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-number {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
line-height: 38px;
|
||||||
|
text-align: center;
|
||||||
|
color: #333;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 0 5px;
|
||||||
|
margin: 0 4px;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-number-bordered {
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-number.active {
|
||||||
|
background-color: #0088D1;
|
||||||
|
color: white;
|
||||||
|
border-color: #0088D1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-number:hover:not(.disabled) {
|
||||||
|
color: #0088D1;
|
||||||
|
border-color: #0088D1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-number.disabled {
|
||||||
|
color: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-number.disabled:hover {
|
||||||
|
color: #ccc;
|
||||||
|
border-color: #d9d9d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-button {
|
||||||
|
padding: 0 8px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-button:hover:not(.disabled) {
|
||||||
|
color: #0088D1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格内所有文字颜色统一 */
|
||||||
|
:deep(.homework-data-table .n-data-table-th__title) {
|
||||||
|
color: #062333;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.homework-data-table .n-data-table-td) {
|
||||||
|
color: #062333;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 作业名称和标签样式 */
|
||||||
|
.homework-name {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 8px;
|
||||||
|
color: #062333;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #9ac1d6;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #9ac1d6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-pinned {
|
||||||
|
color: #9ac1d6;
|
||||||
|
border-color: #9ac1d6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保在Naive UI表格中标签样式正确显示 */
|
||||||
|
:deep(.homework-data-table .tag) {
|
||||||
|
display: inline-block !important;
|
||||||
|
padding: 2px 6px !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
font-size: 10px !important;
|
||||||
|
color: #9ac1d6 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
border: 1px solid #9ac1d6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.homework-data-table .tag-pinned) {
|
||||||
|
color: #9ac1d6 !important;
|
||||||
|
border-color: #9ac1d6 !important;
|
||||||
|
font-size: 10px !important;
|
||||||
|
}
|
||||||
|
</style>
|
409
src/views/teacher/course/HomeworkReview.vue
Normal file
409
src/views/teacher/course/HomeworkReview.vue
Normal file
@ -0,0 +1,409 @@
|
|||||||
|
<template>
|
||||||
|
<div class="homework-review">
|
||||||
|
<!-- 顶部筛选区域 -->
|
||||||
|
<div class="top-section">
|
||||||
|
<div class="filter-tabs">
|
||||||
|
<span class="tab-item" :class="{ active: activeTab === 'all' }" @click="setActiveTab('all')">全部</span>
|
||||||
|
<span class="tab-item" :class="{ active: activeTab === 'publishing' }"
|
||||||
|
@click="setActiveTab('publishing')">发布中</span>
|
||||||
|
<span class="tab-item" :class="{ active: activeTab === 'ended' }" @click="setActiveTab('ended')">已结束</span>
|
||||||
|
</div>
|
||||||
|
<div class="class-dropdown">
|
||||||
|
<span class="dropdown-text">班级名称</span>
|
||||||
|
<svg class="dropdown-arrow" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M3.14645 5.64645C3.34171 5.45118 3.65829 5.45118 3.85355 5.64645L8 9.79289L12.1464 5.64645C12.3417 5.45118 12.6583 5.45118 12.8536 5.64645C13.0488 5.84171 13.0488 6.15829 12.8536 6.35355L8.35355 10.8536C8.15829 11.0488 7.84171 11.0488 7.64645 10.8536L3.14645 6.35355C2.95118 6.15829 2.95118 5.84171 3.14645 5.64645Z"
|
||||||
|
fill="#C2C2C2"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 作业列表 -->
|
||||||
|
<div class="homework-list">
|
||||||
|
<!-- 已结束作业 -->
|
||||||
|
<div v-for="homework in filteredHomeworks" :key="homework.id">
|
||||||
|
|
||||||
|
|
||||||
|
<div v-if="homework.status === 'ended'" class="homework-card ended">
|
||||||
|
<a class="delete-link" @click="deleteHomework(homework.id)">删除</a>
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="homework-title">{{ homework.title }}</h3>
|
||||||
|
<span class="status-badge ended">已结束</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="content-left">
|
||||||
|
<p class="homework-desc">{{ homework.content }}</p>
|
||||||
|
<div class="homework-info">
|
||||||
|
<div class="info-item">
|
||||||
|
<img class="icon" src="/images/teacher/发布人.png" alt="发布人" />
|
||||||
|
<span class="text">发布人:{{ homework.publisher }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<img class="icon" src="/images/teacher/起点时间.png" alt="时间" />
|
||||||
|
<span class="text">起止时间:{{ homework.timeRange }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content-right">
|
||||||
|
<div class="stats-area">
|
||||||
|
<span class="big-number">{{ homework.pendingCount }}</span>
|
||||||
|
<span class="label primary">待批</span>
|
||||||
|
<span class="label secondary">{{ homework.submittedCount }}已交</span>
|
||||||
|
<span class="label secondary">{{ homework.unsubmittedCount }}未交</span>
|
||||||
|
</div>
|
||||||
|
<button class="action-button review" @click="reviewHomework(homework.id)">批阅</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 发布中作业 -->
|
||||||
|
<div v-if="homework.status === 'publishing'" class="homework-card publishing">
|
||||||
|
<a class="delete-link" @click="deleteHomework(homework.id)">删除</a>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="homework-title">{{ homework.title }}</h3>
|
||||||
|
<span class="status-badge publishing">发布中</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="content-left">
|
||||||
|
<p class="homework-desc">{{ homework.content }}</p>
|
||||||
|
<div class="homework-info">
|
||||||
|
<div class="info-item">
|
||||||
|
<img class="icon" src="/images/teacher/发布人.png" alt="发布人" />
|
||||||
|
<span class="text">发布人:{{ homework.publisher }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<img class="icon" src="/images/teacher/起点时间.png" alt="时间" />
|
||||||
|
<span class="text">起止时间:{{ homework.timeRange }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content-right">
|
||||||
|
<div class="stats-area">
|
||||||
|
<span class="big-number">{{ homework.pendingCount }}</span>
|
||||||
|
<span class="label primary">待批</span>
|
||||||
|
<span class="label secondary">{{ homework.submittedCount }}已交</span>
|
||||||
|
<span class="label secondary">{{ homework.unsubmittedCount }}未交</span>
|
||||||
|
</div>
|
||||||
|
<button class="action-button view" @click="viewHomework(homework.id)">查看</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
interface HomeworkItem {
|
||||||
|
id: number
|
||||||
|
title: string
|
||||||
|
content: string
|
||||||
|
publisher: string
|
||||||
|
timeRange: string
|
||||||
|
status: 'publishing' | 'ended'
|
||||||
|
pendingCount: number
|
||||||
|
submittedCount: number
|
||||||
|
unsubmittedCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeTab = ref<'all' | 'publishing' | 'ended'>('all')
|
||||||
|
|
||||||
|
const homeworks = ref<HomeworkItem[]>([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '作业名称作业名称作业名称作业名称',
|
||||||
|
content: '作业内容作业内容作业内容作业内容作业内容作业内容作业内容作业内容作业内容',
|
||||||
|
publisher: '王建国',
|
||||||
|
timeRange: '2025.8.18-2025.9.18',
|
||||||
|
status: 'ended',
|
||||||
|
pendingCount: 10,
|
||||||
|
submittedCount: 0,
|
||||||
|
unsubmittedCount: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: '作业名称作业名称作业名称作业名称',
|
||||||
|
content: '作业内容作业内容作业内容作业内容作业内容作业内容作业内容作业内容作业内容',
|
||||||
|
publisher: '王建国',
|
||||||
|
timeRange: '2025.8.18-2025.9.18',
|
||||||
|
status: 'publishing',
|
||||||
|
pendingCount: 0,
|
||||||
|
submittedCount: 0,
|
||||||
|
unsubmittedCount: 0
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const setActiveTab = (tab: 'all' | 'publishing' | 'ended') => {
|
||||||
|
activeTab.value = tab
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredHomeworks = computed(() => {
|
||||||
|
if (activeTab.value === 'all') {
|
||||||
|
return homeworks.value
|
||||||
|
}
|
||||||
|
return homeworks.value.filter(homework => homework.status === activeTab.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const reviewHomework = (id: number) => {
|
||||||
|
console.log('批阅作业:', id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewHomework = (id: number) => {
|
||||||
|
console.log('查看作业:', id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteHomework = (id: number) => {
|
||||||
|
console.log('删除作业:', id)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.homework-review {
|
||||||
|
width: 1293px;
|
||||||
|
background: #fff;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 顶部筛选区域 */
|
||||||
|
.top-section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 30px 0 0 25px;
|
||||||
|
margin-right: 30px;
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #333333;
|
||||||
|
cursor: pointer;
|
||||||
|
padding-top: 6px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
border-bottom: 3px solid transparent;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item.active {
|
||||||
|
color: #5BADD9;
|
||||||
|
border-bottom: 4px solid #0288D1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item:hover {
|
||||||
|
color: #2196F3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-dropdown {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 276px;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #062333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-arrow {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 作业列表 */
|
||||||
|
.homework-list {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.homework-card {
|
||||||
|
background: #fff;
|
||||||
|
margin: 0;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
padding: 20px 25px;
|
||||||
|
position: relative;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.homework-card:hover {
|
||||||
|
background-color: #F5F5F5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.homework-card:hover .delete-link {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.homework-card:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片头部 */
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.homework-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #062333;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge.ended {
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge.publishing {
|
||||||
|
color: #FF9800;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片内容 */
|
||||||
|
.card-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-left {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.homework-desc {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.homework-info {
|
||||||
|
display: flex;
|
||||||
|
gap: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item .icon {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item .text {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右侧内容 */
|
||||||
|
.content-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-link {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 35%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: #2196F3;
|
||||||
|
font-size: 14px;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
align-self: flex-start;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-area {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.big-number {
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #062333;
|
||||||
|
line-height: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.primary {
|
||||||
|
color: #062333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.secondary {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 操作按钮 */
|
||||||
|
.action-button {
|
||||||
|
background: #0288D1;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 6px 24px;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.3s ease;
|
||||||
|
min-width: 92px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button:hover {
|
||||||
|
background: #0288D1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.review {}
|
||||||
|
|
||||||
|
.action-button.view {
|
||||||
|
background: #0288D1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 发布中状态特殊布局调整 */
|
||||||
|
.homework-card.publishing .content-right {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.homework-card.publishing .stats-area {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="practice-management">
|
<div class="practice-management">
|
||||||
<div class="content-placeholder">
|
<router-view></router-view>
|
||||||
<h2>练考通管理</h2>
|
|
||||||
<p>练考通管理功能正在开发中...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -17,20 +14,4 @@
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-placeholder {
|
|
||||||
text-align: center;
|
|
||||||
padding: 60px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-placeholder h2 {
|
|
||||||
font-size: 24px;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-placeholder p {
|
|
||||||
font-size: 16px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user