feat: 添加教师端证书首页;添加证书详情页;添加证书颁奖页面及一系列功能;添加新增证书页面;
Before Width: | Height: | Size: 3.8 MiB After Width: | Height: | Size: 3.8 MiB |
Before Width: | Height: | Size: 497 B After Width: | Height: | Size: 497 B |
Before Width: | Height: | Size: 396 B After Width: | Height: | Size: 396 B |
Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 392 B |
Before Width: | Height: | Size: 278 B After Width: | Height: | Size: 278 B |
BIN
public/images/teacher/more.png
Normal file
After Width: | Height: | Size: 847 B |
Before Width: | Height: | Size: 276 B After Width: | Height: | Size: 276 B |
Before Width: | Height: | Size: 779 B After Width: | Height: | Size: 779 B |
Before Width: | Height: | Size: 962 B After Width: | Height: | Size: 962 B |
979
src/components/CertificateIssuanceModal.vue
Normal file
@ -0,0 +1,979 @@
|
||||
<template>
|
||||
<n-modal :show="show" @update:show="handleUpdateShow" preset="card"
|
||||
style="width: 90%; max-width: 1200px; max-height: 85vh; min-height: 800px;" :mask-closable="false" :closable="false">
|
||||
<template #header>
|
||||
<div class="modal-header">
|
||||
<!-- 步骤指示器 -->
|
||||
<div class="step-indicator">
|
||||
<div class="step-item" :class="{ active: currentStep === 1 }">
|
||||
<div class="step-number">1</div>
|
||||
<span class="step-text">选择考试/学习项目</span>
|
||||
</div>
|
||||
<div class="step-item" :class="{ active: currentStep === 2 }">
|
||||
<div class="step-number">2</div>
|
||||
<span class="step-text">设置颁发规则</span>
|
||||
</div>
|
||||
<div class="step-item" :class="{ active: currentStep === 3 }">
|
||||
<div class="step-number">3</div>
|
||||
<span class="step-text">颁发证书</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 关闭按钮 -->
|
||||
<n-button text @click="handleClose" class="close-button">×</n-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="modal-content">
|
||||
<!-- 第一步:选择考试/学习项目 -->
|
||||
<div v-if="currentStep === 1" class="step-content">
|
||||
<!-- 筛选区域 -->
|
||||
<div class="filter-section">
|
||||
<span class="label">使用类别:</span>
|
||||
<n-select v-model:value="selectedCategory" :options="categoryOptions" placeholder="考试"
|
||||
class="category-select" />
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-section">
|
||||
<n-data-table :columns="columns" :data="filteredExams" :row-key="(row) => row.id"
|
||||
:checked-row-keys="selectedExams" @update:checked-row-keys="handleSelectionChange" :bordered="false"
|
||||
:single-line="false" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第二步:设置颁发规则 -->
|
||||
<div v-if="currentStep === 2" class="step-content">
|
||||
<div class="rules-section">
|
||||
<div class="table-section">
|
||||
<n-data-table :columns="rulesColumns" :data="selectedExamData" :row-key="(row) => row.id"
|
||||
:bordered="false" :single-line="false" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第三步:颁发证书 -->
|
||||
<div v-if="currentStep === 3" class="step-content">
|
||||
<div class="issuance-section">
|
||||
<div class="table-section">
|
||||
<n-data-table :columns="studentColumns" :data="studentList" :row-key="(row) => row.id"
|
||||
:checked-row-keys="selectedStudents" @update:checked-row-keys="handleStudentSelectionChange" :bordered="false"
|
||||
:single-line="false" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="modal-footer">
|
||||
<div class="footer-content" :class="{ 'no-pagination': currentStep !== 1 && currentStep !== 3 }">
|
||||
<!-- 分页器区域 -->
|
||||
<div v-if="currentStep === 1" class="pagination-section">
|
||||
<n-pagination
|
||||
v-model:page="pagination.page"
|
||||
:page-count="pagination.pageCount"
|
||||
:page-size="pagination.pageSize"
|
||||
:show-size-picker="pagination.showSizePicker"
|
||||
@update:page="pagination.onChange"
|
||||
/>
|
||||
</div>
|
||||
<!-- 第三步的分页器和发证人数信息 -->
|
||||
<div v-if="currentStep === 3" class="pagination-section">
|
||||
<n-pagination
|
||||
v-model:page="pagination.page"
|
||||
:page-count="pagination.pageCount"
|
||||
:page-size="pagination.pageSize"
|
||||
:show-size-picker="pagination.showSizePicker"
|
||||
@update:page="pagination.onChange"
|
||||
/>
|
||||
</div>
|
||||
<!-- 发证人数信息单独显示在中间 -->
|
||||
<div v-if="currentStep === 3" class="certificate-count-section">
|
||||
<span class="certificate-count">本次发证人数<span class="certificate-number">{{ selectedStudents.length }}</span>人</span>
|
||||
</div>
|
||||
<div class="button-section">
|
||||
<n-button v-if="currentStep > 1" class="previous-button" @click="handlePrevious">
|
||||
上一步
|
||||
</n-button>
|
||||
<n-button type="primary" class="primary-button" @click="handleNext"
|
||||
:disabled="currentStep === 1 && selectedExams.length === 0">
|
||||
{{ currentStep === 3 ? '颁发证书' : '下一步' }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, h } from 'vue'
|
||||
import { NModal, NButton, NSelect, NDataTable, NPagination } from 'naive-ui'
|
||||
|
||||
interface Exam {
|
||||
id: number
|
||||
serialNo: number
|
||||
examName: string
|
||||
totalScore: number
|
||||
startTime: string
|
||||
endTime: string
|
||||
creator: string
|
||||
createTime: string
|
||||
isExpired: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
show: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:show': [value: boolean]
|
||||
'confirm': [selectedExams: Exam[]]
|
||||
}>()
|
||||
|
||||
// 响应式数据
|
||||
const currentStep = ref(1)
|
||||
const selectedCategory = ref('考试')
|
||||
const selectedExams = ref<number[]>([101, 102, 103, 104]) // 默认选中前四行
|
||||
const selectedStudents = ref<number[]>([201, 202, 203, 204, 205, 207]) // 默认选中学员
|
||||
|
||||
// 筛选选项
|
||||
const categoryOptions = [
|
||||
{ label: '考试', value: '考试' },
|
||||
{ label: '学习项目', value: '学习项目' },
|
||||
{ label: '练习', value: '练习' }
|
||||
]
|
||||
|
||||
// 模拟考试数据
|
||||
const examList = ref<Exam[]>([
|
||||
{
|
||||
id: 1,
|
||||
serialNo: 1,
|
||||
examName: '数据库系统概论期末考试',
|
||||
totalScore: 100,
|
||||
startTime: '2025.01.15 14:00',
|
||||
endTime: '2025.01.15 16:00',
|
||||
creator: '张教授',
|
||||
createTime: '2025.01.10 10:30',
|
||||
isExpired: '否'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
serialNo: 2,
|
||||
examName: '数据结构与算法期中考试',
|
||||
totalScore: 80,
|
||||
startTime: '2025.01.20 09:00',
|
||||
endTime: '2025.01.20 11:00',
|
||||
creator: '李老师',
|
||||
createTime: '2025.01.12 15:45',
|
||||
isExpired: '否'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
serialNo: 3,
|
||||
examName: '计算机网络基础测试',
|
||||
totalScore: 60,
|
||||
startTime: '2025.01.25 16:00',
|
||||
endTime: '2025.01.25 17:00',
|
||||
creator: '王教授',
|
||||
createTime: '2025.01.15 08:20',
|
||||
isExpired: '否'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
serialNo: 4,
|
||||
examName: '软件工程实践项目',
|
||||
totalScore: 150,
|
||||
startTime: '2025.02.01 00:00',
|
||||
endTime: '2025.02.28 23:59',
|
||||
creator: '陈老师',
|
||||
createTime: '2025.01.20 14:15',
|
||||
isExpired: '否'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
serialNo: 5,
|
||||
examName: '操作系统原理期末考试',
|
||||
totalScore: 100,
|
||||
startTime: '2025.02.05 14:30',
|
||||
endTime: '2025.02.05 16:30',
|
||||
creator: '刘教授',
|
||||
createTime: '2025.01.25 11:00',
|
||||
isExpired: '否'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
serialNo: 6,
|
||||
examName: 'Java程序设计基础练习',
|
||||
totalScore: 50,
|
||||
startTime: '2025.02.10 10:00',
|
||||
endTime: '2025.02.10 11:00',
|
||||
creator: '赵老师',
|
||||
createTime: '2025.01.30 16:30',
|
||||
isExpired: '否'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
serialNo: 7,
|
||||
examName: 'Web前端开发技术考试',
|
||||
totalScore: 90,
|
||||
startTime: '2025.02.15 15:00',
|
||||
endTime: '2025.02.15 17:00',
|
||||
creator: '孙老师',
|
||||
createTime: '2025.02.05 09:45',
|
||||
isExpired: '否'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
serialNo: 8,
|
||||
examName: '人工智能导论期中考试',
|
||||
totalScore: 100,
|
||||
startTime: '2025.02.20 09:30',
|
||||
endTime: '2025.02.20 11:30',
|
||||
creator: '周教授',
|
||||
createTime: '2025.02.10 13:20',
|
||||
isExpired: '否'
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
serialNo: 9,
|
||||
examName: '机器学习算法实践',
|
||||
totalScore: 120,
|
||||
startTime: '2025.02.25 14:00',
|
||||
endTime: '2025.02.25 16:00',
|
||||
creator: '吴老师',
|
||||
createTime: '2025.02.15 10:10',
|
||||
isExpired: '否'
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
serialNo: 10,
|
||||
examName: '数据库设计项目考核',
|
||||
totalScore: 200,
|
||||
startTime: '2025.03.01 00:00',
|
||||
endTime: '2025.03.15 23:59',
|
||||
creator: '郑老师',
|
||||
createTime: '2025.02.20 15:30',
|
||||
isExpired: '否'
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
serialNo: 11,
|
||||
examName: '计算机组成原理考试',
|
||||
totalScore: 100,
|
||||
startTime: '2025.03.05 14:00',
|
||||
endTime: '2025.03.05 16:00',
|
||||
creator: '黄教授',
|
||||
createTime: '2025.02.25 11:45',
|
||||
isExpired: '否'
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
serialNo: 12,
|
||||
examName: '软件测试技术练习',
|
||||
totalScore: 60,
|
||||
startTime: '2025.03.10 10:30',
|
||||
endTime: '2025.03.10 11:30',
|
||||
creator: '林老师',
|
||||
createTime: '2025.03.01 14:20',
|
||||
isExpired: '否'
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
serialNo: 13,
|
||||
examName: '移动应用开发项目',
|
||||
totalScore: 180,
|
||||
startTime: '2025.03.15 00:00',
|
||||
endTime: '2025.03.30 23:59',
|
||||
creator: '马老师',
|
||||
createTime: '2025.03.05 09:15',
|
||||
isExpired: '否'
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
serialNo: 14,
|
||||
examName: '云计算技术基础考试',
|
||||
totalScore: 80,
|
||||
startTime: '2025.03.20 15:30',
|
||||
endTime: '2025.03.20 17:30',
|
||||
creator: '徐教授',
|
||||
createTime: '2025.03.10 16:40',
|
||||
isExpired: '否'
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
serialNo: 15,
|
||||
examName: '信息安全技术实践',
|
||||
totalScore: 100,
|
||||
startTime: '2025.03.25 09:00',
|
||||
endTime: '2025.03.25 11:00',
|
||||
creator: '朱老师',
|
||||
createTime: '2025.03.15 13:50',
|
||||
isExpired: '否'
|
||||
}
|
||||
])
|
||||
|
||||
// 模拟学习项目数据
|
||||
const projectList = ref([
|
||||
{
|
||||
id: 101,
|
||||
serialNo: 1,
|
||||
examName: '第一章课前准备',
|
||||
category: '1',
|
||||
startTime: '2025.08.17 09:00',
|
||||
endTime: '2025.08.17 09:00',
|
||||
creator: '王建国',
|
||||
createTime: '2025.08.20 09:20'
|
||||
},
|
||||
{
|
||||
id: 102,
|
||||
serialNo: 1,
|
||||
examName: '第二章课前准备',
|
||||
category: '2',
|
||||
startTime: '2025.08.18 10:00',
|
||||
endTime: '2025.08.18 12:00',
|
||||
creator: '李老师',
|
||||
createTime: '2025.08.20 09:20'
|
||||
},
|
||||
{
|
||||
id: 103,
|
||||
serialNo: 1,
|
||||
examName: '第三章课前准备',
|
||||
category: '3',
|
||||
startTime: '2025.08.19 14:00',
|
||||
endTime: '2025.08.19 16:00',
|
||||
creator: '张教授',
|
||||
createTime: '2025.08.20 09:20'
|
||||
},
|
||||
{
|
||||
id: 104,
|
||||
serialNo: 1,
|
||||
examName: '第四章课前准备',
|
||||
category: '4',
|
||||
startTime: '2025.08.20 09:30',
|
||||
endTime: '2025.08.20 11:30',
|
||||
creator: '陈老师',
|
||||
createTime: '2025.08.20 09:20'
|
||||
}
|
||||
])
|
||||
|
||||
// 模拟学员数据
|
||||
const studentList = ref([
|
||||
{
|
||||
id: 201,
|
||||
serialNo: 1,
|
||||
studentName: '王街道',
|
||||
studentAccount: '565556622552',
|
||||
referenceExam: '在数据库的三级模式结构中,内模式有__',
|
||||
referenceTime: '2025.08.09 09:22',
|
||||
examScore: 120
|
||||
},
|
||||
{
|
||||
id: 202,
|
||||
serialNo: 1,
|
||||
studentName: '王街道',
|
||||
studentAccount: '565556622552',
|
||||
referenceExam: '在数据库的三级模式结构中,内模式有__',
|
||||
referenceTime: '2025.08.09 09:22',
|
||||
examScore: 120
|
||||
},
|
||||
{
|
||||
id: 203,
|
||||
serialNo: 1,
|
||||
studentName: '王街道',
|
||||
studentAccount: '565556622552',
|
||||
referenceExam: '在数据库的三级模式结构中,内模式有__',
|
||||
referenceTime: '2025.08.09 09:22',
|
||||
examScore: 120
|
||||
},
|
||||
{
|
||||
id: 204,
|
||||
serialNo: 1,
|
||||
studentName: '王街道',
|
||||
studentAccount: '565556622552',
|
||||
referenceExam: '在数据库的三级模式结构中,内模式有__',
|
||||
referenceTime: '2025.08.09 09:22',
|
||||
examScore: 120
|
||||
},
|
||||
{
|
||||
id: 205,
|
||||
serialNo: 1,
|
||||
studentName: '王街道',
|
||||
studentAccount: '565556622552',
|
||||
referenceExam: '在数据库的三级模式结构中,内模式有__',
|
||||
referenceTime: '2025.08.09 09:22',
|
||||
examScore: 120
|
||||
},
|
||||
{
|
||||
id: 206,
|
||||
serialNo: 1,
|
||||
studentName: '王街道',
|
||||
studentAccount: '565556622552',
|
||||
referenceExam: '在数据库的三级模式结构中,内模式有__',
|
||||
referenceTime: '2025.08.09 09:22',
|
||||
examScore: 120
|
||||
},
|
||||
{
|
||||
id: 207,
|
||||
serialNo: 1,
|
||||
studentName: '王街道',
|
||||
studentAccount: '565556622552',
|
||||
referenceExam: '在数据库的三级模式结构中,内模式有__',
|
||||
referenceTime: '2025.08.09 09:22',
|
||||
examScore: 120
|
||||
},
|
||||
{
|
||||
id: 208,
|
||||
serialNo: 1,
|
||||
studentName: '王街道',
|
||||
studentAccount: '565556622552',
|
||||
referenceExam: '在数据库的三级模式结构中,内模式有__',
|
||||
referenceTime: '2025.08.09 09:22',
|
||||
examScore: 120
|
||||
}
|
||||
])
|
||||
|
||||
// 学员表格列定义
|
||||
const studentColumns = [
|
||||
{
|
||||
type: 'selection' as const,
|
||||
width: 50
|
||||
},
|
||||
{
|
||||
title: '序号',
|
||||
key: 'serialNo',
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
title: '学员姓名',
|
||||
key: 'studentName',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '学员帐号',
|
||||
key: 'studentAccount',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '参考考试',
|
||||
key: 'referenceExam',
|
||||
width: 400
|
||||
},
|
||||
{
|
||||
title: '参考时间',
|
||||
key: 'referenceTime',
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
title: '考试成绩',
|
||||
key: 'examScore',
|
||||
width: 100
|
||||
}
|
||||
]
|
||||
|
||||
// 表格列定义
|
||||
const columns = computed(() => {
|
||||
if (selectedCategory.value === '学习项目') {
|
||||
return [
|
||||
{
|
||||
type: 'selection' as const,
|
||||
width: 50
|
||||
},
|
||||
{
|
||||
title: '序号',
|
||||
key: 'serialNo',
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
title: '学习项目名称',
|
||||
key: 'examName',
|
||||
width: 300
|
||||
},
|
||||
{
|
||||
title: '分类',
|
||||
key: 'category',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '开始时间',
|
||||
key: 'startTime',
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
title: '结束时间',
|
||||
key: 'endTime',
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
title: '创建人',
|
||||
key: 'creator',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createTime',
|
||||
width: 140
|
||||
}
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{
|
||||
type: 'selection' as const,
|
||||
width: 50
|
||||
},
|
||||
{
|
||||
title: '序号',
|
||||
key: 'serialNo',
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
title: '考试名称',
|
||||
key: 'examName',
|
||||
width: 300
|
||||
},
|
||||
{
|
||||
title: '总分',
|
||||
key: 'totalScore',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '开始时间',
|
||||
key: 'startTime',
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
title: '结束时间',
|
||||
key: 'endTime',
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
title: '创建人',
|
||||
key: 'creator',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createTime',
|
||||
width: 140
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
// 颁发规则表格列定义
|
||||
const rulesColumns = computed(() => {
|
||||
if (selectedCategory.value === '学习项目') {
|
||||
return [
|
||||
{
|
||||
type: 'selection' as const,
|
||||
width: 50
|
||||
},
|
||||
{
|
||||
title: '序号',
|
||||
key: 'serialNo',
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
title: '章节名称',
|
||||
key: 'examName',
|
||||
width: 300
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createTime',
|
||||
width: 140
|
||||
}
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{
|
||||
type: 'selection' as const,
|
||||
width: 50
|
||||
},
|
||||
{
|
||||
title: '序号',
|
||||
key: 'serialNo',
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
title: '考试名称',
|
||||
key: 'examName',
|
||||
width: 300
|
||||
},
|
||||
{
|
||||
title: '总分',
|
||||
key: 'totalScore',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '发放规则',
|
||||
key: 'issuanceRules',
|
||||
width: 200,
|
||||
render: () => {
|
||||
return h('div', { class: 'rules-input-container' }, [
|
||||
h('input', {
|
||||
class: 'rule-input',
|
||||
placeholder: '最低分',
|
||||
style: 'width: 80px; min-height: 32px; margin-right: 8px; border: 1px solid #E6E6E6; border-radius: 0; padding: 4px 6px; background-color: #FCFCFC;'
|
||||
}),
|
||||
h('span', '-'),
|
||||
h('input', {
|
||||
class: 'rule-input',
|
||||
placeholder: '最高分',
|
||||
style: 'width: 80px; min-height: 32px; margin-left: 8px; border: 1px solid #E6E6E6; border-radius: 0; padding: 4px 6px; background-color: #FCFCFC;'
|
||||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
// 分页配置
|
||||
const pagination = {
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
showSizePicker: false,
|
||||
pageCount: 43,
|
||||
onChange: (page: number) => {
|
||||
console.log('页码变化:', page)
|
||||
}
|
||||
}
|
||||
|
||||
// 计算属性
|
||||
const filteredExams = computed(() => {
|
||||
if (selectedCategory.value === '学习项目') {
|
||||
return projectList.value
|
||||
} else {
|
||||
return examList.value.filter((exam: Exam) => {
|
||||
if (selectedCategory.value === '考试') {
|
||||
return exam.examName.includes('考试')
|
||||
} else if (selectedCategory.value === '练习') {
|
||||
return exam.examName.includes('练习')
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 选中的考试数据
|
||||
const selectedExamData = computed(() => {
|
||||
if (selectedCategory.value === '学习项目') {
|
||||
return projectList.value.filter((project: any) =>
|
||||
selectedExams.value.includes(project.id)
|
||||
)
|
||||
} else {
|
||||
return examList.value.filter((exam: Exam) =>
|
||||
selectedExams.value.includes(exam.id)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// 方法
|
||||
const handleUpdateShow = (value: boolean) => {
|
||||
emit('update:show', value)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
emit('update:show', false)
|
||||
}
|
||||
|
||||
const handleSelectionChange = (keys: number[]) => {
|
||||
selectedExams.value = keys
|
||||
}
|
||||
|
||||
const handleStudentSelectionChange = (keys: number[]) => {
|
||||
selectedStudents.value = keys
|
||||
}
|
||||
|
||||
const handleNext = () => {
|
||||
if (currentStep.value < 3) {
|
||||
currentStep.value++
|
||||
} else {
|
||||
// 完成颁发
|
||||
const selectedExamItems = examList.value.filter((exam: Exam) =>
|
||||
selectedExams.value.includes(exam.id)
|
||||
)
|
||||
emit('confirm', selectedExamItems)
|
||||
emit('update:show', false)
|
||||
}
|
||||
}
|
||||
|
||||
const handlePrevious = () => {
|
||||
if (currentStep.value > 1) {
|
||||
currentStep.value--
|
||||
}
|
||||
}
|
||||
|
||||
// const handleCancel = () => {
|
||||
// selectedExams.value = []
|
||||
// currentStep.value = 1
|
||||
emit('update:show', false)
|
||||
// }
|
||||
|
||||
// 监听显示状态变化
|
||||
watch(() => props.show, (newVal: boolean) => {
|
||||
if (!newVal) {
|
||||
selectedExams.value = []
|
||||
currentStep.value = 1
|
||||
selectedCategory.value = '考试'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 模态框头部样式 */
|
||||
.modal-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1.5px solid #E6E6E6;
|
||||
}
|
||||
|
||||
.modal-header .close-button {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
font-size: 34px;
|
||||
font-weight: bold;
|
||||
color: #999999;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 步骤指示器样式 */
|
||||
.step-indicator {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #000;
|
||||
font-size: 20px;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.step-item .step-number {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 8px;
|
||||
color: #999;
|
||||
background-color: white;
|
||||
font-size: 14px;
|
||||
background-color: #EBEBEB;
|
||||
}
|
||||
|
||||
.step-item.active {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.step-item.active .step-number {
|
||||
background-color: #0288D1;
|
||||
color: white;
|
||||
border-color: #0288D1;
|
||||
}
|
||||
|
||||
.step-indicator .step-item:not(:last-child)::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -50%;
|
||||
transform: translateX(50%);
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #D8D8D8;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* 筛选区域 */
|
||||
.filter-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.filter-section .label {
|
||||
margin-right: 10px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.filter-section .n-select {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
/* 表格样式覆盖 */
|
||||
:deep(.n-data-table) {
|
||||
background: white;
|
||||
border: 1px solid #F1F3F4;
|
||||
}
|
||||
|
||||
:deep(.n-data-table .n-data-table-thead .n-data-table-tr) {
|
||||
background: #FCFCFC;
|
||||
}
|
||||
|
||||
:deep(.n-data-table .n-data-table-thead .n-data-table-th) {
|
||||
background: #FCFCFC;
|
||||
border-bottom: 1px solid #E6E6E6;
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
padding: 8px 6px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:deep(.n-data-table .n-data-table-tbody .n-data-table-td) {
|
||||
border-bottom: 1px solid #E6E6E6;
|
||||
padding: 8px 6px;
|
||||
font-size: 12px;
|
||||
color: #062333;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.n-data-table .n-data-table-tbody .n-data-table-tr:hover) {
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* 分页器区域样式 */
|
||||
.pagination-section {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.certificate-count {
|
||||
margin-left: 20px;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 发证人数信息区域样式 */
|
||||
.certificate-count-section {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.certificate-number {
|
||||
color: #ED2626;
|
||||
}
|
||||
|
||||
/* 分页样式 */
|
||||
:deep(.n-pagination) {
|
||||
justify-content: flex-start !important;
|
||||
display: flex !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
:deep(.n-pagination .n-pagination-item) {
|
||||
border: 1px solid #DBDBDB !important;
|
||||
background: white !important;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
:deep(.n-pagination .n-pagination-item--button) {
|
||||
border: 1px solid #DBDBDB !important;
|
||||
background: white !important;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
:deep(.n-pagination .n-pagination-item--active) {
|
||||
border: none !important;
|
||||
background: #0288D1 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
:deep(.n-pagination .n-pagination-item--disabled) {
|
||||
border: 1px solid #DBDBDB !important;
|
||||
background: white !important;
|
||||
color: #999 !important;
|
||||
}
|
||||
|
||||
/* 颁发规则输入框样式 */
|
||||
.rules-input-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.rule-input {
|
||||
border: 1px solid #E6E6E6 !important;
|
||||
border-radius: 0 !important;
|
||||
padding: 4px 6px;
|
||||
font-size: 12px;
|
||||
background: #FCFCFC !important;
|
||||
text-align: center;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.rule-input::placeholder {
|
||||
color: #999999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 底部按钮 */
|
||||
.modal-footer {
|
||||
margin-top: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 当没有分页器时,按钮区域应该占满整个宽度并右对齐 */
|
||||
.footer-content.no-pagination {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.button-section {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.button-section .n-button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.modal-footer .n-button.primary-button {
|
||||
background-color: #0288D1;
|
||||
color: white;
|
||||
border-radius: 3px;
|
||||
padding: 0 20px;
|
||||
height: 32px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.modal-footer .n-button.previous-button {
|
||||
border: 1px solid #0288D1;
|
||||
color: #0288D1;
|
||||
background-color: #E2F5FF;
|
||||
border-radius: 2px;
|
||||
padding: 0 20px;
|
||||
height: 32px;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
@ -270,7 +270,7 @@ watch(() => props.show, (newVal) => {
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 18px;
|
||||
font-size: 8px;
|
||||
padding-bottom: 10px;
|
||||
font-weight: 500;
|
||||
color: #000;
|
||||
|
@ -219,12 +219,25 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('../views/teacher/course/AddQuestion.vue'),
|
||||
meta: { title: '新增试题' }
|
||||
},
|
||||
{
|
||||
{
|
||||
path: 'certificate',
|
||||
name: 'CertificateManagement',
|
||||
component: CertificateManagement,
|
||||
meta: { title: '证书管理' }
|
||||
},
|
||||
{
|
||||
path: 'certificate/detail/:id',
|
||||
name: 'CertificateDetail',
|
||||
component: () => import('@/views/teacher/certificate/CertificateDetail.vue'),
|
||||
meta: { title: '证书详情' }
|
||||
},
|
||||
{
|
||||
path: 'certificate/add',
|
||||
name: 'CertificateCreate',
|
||||
component: () => import('@/views/teacher/certificate/CertificateCreate.vue'),
|
||||
meta: { title: '添加证书' }
|
||||
},
|
||||
|
||||
{
|
||||
path: 'discussion',
|
||||
name: 'DiscussionManagement',
|
||||
@ -263,6 +276,12 @@ const routes: RouteRecordRaw[] = [
|
||||
component: StudentManagement,
|
||||
meta: { title: '学员管理' }
|
||||
},
|
||||
{
|
||||
path: 'certificate/new',
|
||||
name: 'CertificateNew',
|
||||
component: () => import('@/views/teacher/certificate/CertificateNew.vue'),
|
||||
meta: { title: '新建证书' }
|
||||
},
|
||||
{
|
||||
path: 'chapter-editor-teacher/:courseId',
|
||||
name: 'ChapterEditor',
|
||||
@ -335,39 +354,7 @@ const routes: RouteRecordRaw[] = [
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'certificate',
|
||||
name: 'CertificateModule',
|
||||
component: () => import('@/views/teacher/certificate/index.vue'),
|
||||
meta: { title: '证书管理' },
|
||||
redirect: '/teacher/certificate/management',
|
||||
children: [
|
||||
{
|
||||
path: 'management',
|
||||
name: 'CertificateManagementPage',
|
||||
component: CertificateManagement,
|
||||
meta: { title: '证书管理' }
|
||||
},
|
||||
{
|
||||
path: 'template',
|
||||
name: 'CertificateTemplate',
|
||||
component: () => import('@/views/teacher/certificate/CertificateTemplate.vue'),
|
||||
meta: { title: '证书模板' }
|
||||
},
|
||||
{
|
||||
path: 'issuance',
|
||||
name: 'CertificateIssuance',
|
||||
component: () => import('@/views/teacher/certificate/CertificateIssuance.vue'),
|
||||
meta: { title: '证书颁发' }
|
||||
},
|
||||
{
|
||||
path: 'query',
|
||||
name: 'CertificateQuery',
|
||||
component: () => import('@/views/teacher/certificate/CertificateQuery.vue'),
|
||||
meta: { title: '证书查询' }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
]
|
||||
},
|
||||
|
||||
|
@ -75,7 +75,7 @@
|
||||
<!-- 右侧路由视图 -->
|
||||
<div class="router-view-container" :class="{ 'full-width': hideSidebar }">
|
||||
<!-- 面包屑 -->
|
||||
<div class="breadcrumb">
|
||||
<div class="breadcrumb" v-if="breadcrumbDisplay">
|
||||
<span class="breadcrumb-side"></span>
|
||||
<div class="custom-breadcrumb">
|
||||
<!-- 左侧课程管理 -->
|
||||
@ -117,6 +117,14 @@ const examMenuExpanded = ref(false); // 考试管理菜单展开状态
|
||||
const showTopImage = ref(true); // 控制顶部图片显示/隐藏
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const breadcrumbDisplay = computed(() => {
|
||||
const currentPath = route.path;
|
||||
// 在新建证书页面不显示面包屑
|
||||
if (currentPath.includes('certificate/new')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const setActiveNavItem = (index: number) => {
|
||||
activeNavItem.value = index;
|
||||
@ -161,20 +169,82 @@ const handleClose = () => {
|
||||
const hideSidebar = computed(() => {
|
||||
const currentPath = route.path
|
||||
// 当进入课程管理相关页面或章节编辑页面时隐藏侧边栏
|
||||
return currentPath.includes('course-editor') || currentPath.includes('chapter-editor-teacher')
|
||||
return currentPath.includes('course-editor') || currentPath.includes('chapter-editor-teacher') || currentPath.includes('certificate')
|
||||
})
|
||||
|
||||
// 动态生成面包屑路径项(右侧部分)
|
||||
const breadcrumbPathItems = computed(() => {
|
||||
const currentPath = route.path;
|
||||
|
||||
// 证书管理模块的面包屑逻辑(优先处理)
|
||||
if (currentPath.includes('certificate')) {
|
||||
console.log('证书页面路径:', currentPath);
|
||||
let breadcrumbs: Array<{ title: string, path: string }> = [];
|
||||
// 从路径中提取课程ID,如果路径包含course-editor
|
||||
const courseIdMatch = currentPath.match(/\/course-editor\/(\d+)/);
|
||||
const courseId = courseIdMatch ? courseIdMatch[1] : '1';
|
||||
|
||||
if (currentPath.includes('/certificate/detail/')) {
|
||||
console.log('匹配到证书详情页面');
|
||||
// 证书详细页面:课程管理 > 证书 > 证书详情
|
||||
breadcrumbs = [
|
||||
{
|
||||
title: '课程管理',
|
||||
path: '/teacher/course-management'
|
||||
},
|
||||
{
|
||||
title: '证书管理',
|
||||
path: `/teacher/course-editor/${courseId}/certificate`
|
||||
},
|
||||
{
|
||||
title: '证书详情',
|
||||
path: currentPath
|
||||
}
|
||||
];
|
||||
} else if (currentPath.includes('/certificate/add')) {
|
||||
console.log('匹配到添加证书页面');
|
||||
// 添加证书页面:课程管理 > 证书 > 添加证书
|
||||
breadcrumbs = [
|
||||
{
|
||||
title: '课程管理',
|
||||
path: '/teacher/course-management'
|
||||
},
|
||||
{
|
||||
title: '证书管理',
|
||||
path: `/teacher/course-editor/${courseId}/certificate`
|
||||
},
|
||||
{
|
||||
title: '添加证书',
|
||||
path: currentPath
|
||||
}
|
||||
];
|
||||
} else if (currentPath.endsWith('/certificate')) {
|
||||
console.log('匹配到证书管理页面');
|
||||
// 证书管理页面:课程管理 > 证书
|
||||
breadcrumbs = [
|
||||
{
|
||||
title: '课程管理',
|
||||
path: '/teacher/course-management'
|
||||
},
|
||||
{
|
||||
title: '证书',
|
||||
path: `/teacher/course-editor/${courseId}/certificate`
|
||||
}
|
||||
];
|
||||
} else {
|
||||
console.log('证书页面但未匹配到具体条件');
|
||||
}
|
||||
console.log('证书页面面包屑:', breadcrumbs);
|
||||
return breadcrumbs;
|
||||
}
|
||||
|
||||
// 如果在课程编辑器相关页面
|
||||
if (currentPath.includes('course-editor')) {
|
||||
// 从路径中提取课程ID
|
||||
const courseIdMatch = currentPath.match(/\/course-editor\/(\d+)/);
|
||||
const courseId = courseIdMatch ? courseIdMatch[1] : '未知';
|
||||
|
||||
let breadcrumbs = [];
|
||||
let breadcrumbs: Array<{ title: string, path: string }> = [];
|
||||
|
||||
// 根据当前路径添加子页面标题
|
||||
if (currentPath.includes('courseware')) {
|
||||
@ -267,7 +337,7 @@ const breadcrumbPathItems = computed(() => {
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// 普通作业页面:作业管理
|
||||
// 普通作业页面:作业管理
|
||||
breadcrumbs.push(
|
||||
{
|
||||
title: '作业管理',
|
||||
@ -303,12 +373,16 @@ const breadcrumbPathItems = computed(() => {
|
||||
} else if (currentPath.includes('certificate')) {
|
||||
breadcrumbs.push(
|
||||
{
|
||||
title: '证书管理',
|
||||
path: currentPath
|
||||
title: '课程管理',
|
||||
path: '/teacher/course-management'
|
||||
},
|
||||
{
|
||||
title: `课程${courseId}`,
|
||||
path: `/teacher/course-editor/${courseId}`
|
||||
title: '证书管理',
|
||||
path: '/teacher/course-editor/1/certificate'
|
||||
},
|
||||
{
|
||||
title: '证书名称详情',
|
||||
path: currentPath
|
||||
}
|
||||
);
|
||||
} else if (currentPath.includes('discussion')) {
|
||||
@ -391,6 +465,8 @@ const breadcrumbPathItems = computed(() => {
|
||||
return breadcrumbs;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 其他页面的面包屑逻辑
|
||||
const matchedRoutes = route.matched;
|
||||
let breadcrumbs = matchedRoutes
|
||||
@ -429,7 +505,7 @@ const updateActiveNavItem = () => {
|
||||
} else if (path.includes('exam-management')) {
|
||||
activeNavItem.value = 4; // 考试管理
|
||||
examMenuExpanded.value = true;
|
||||
|
||||
|
||||
const arr = ['question-management', 'exam-library', 'marking-center'];
|
||||
const found = arr.find(item => path.includes(item));
|
||||
activeSubNavItem.value = found || '';
|
||||
|
520
src/views/teacher/certificate/CertificateCreate.vue
Normal file
@ -0,0 +1,520 @@
|
||||
<template>
|
||||
<div class="certificate-create">
|
||||
<!-- 主要内容 -->
|
||||
<div class="content">
|
||||
<!-- 创建证书区域 -->
|
||||
<div class="create-certificate-section">
|
||||
<h3 class="section-title">创建证书</h3>
|
||||
<div class="upload-area" @click="showModal = true">
|
||||
<div class="upload-placeholder">
|
||||
<img src="/images/teacher/添加-灰.png" alt="添加" class="upload-icon" />
|
||||
<span class="upload-text">点击上传证书</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<div class="divider">
|
||||
<span class="divider-text">证书模板</span>
|
||||
</div>
|
||||
|
||||
<!-- 通用证书区域 -->
|
||||
<div class="general-certificate-section">
|
||||
<h3 class="section-title">通用证书</h3>
|
||||
<div class="certificate-templates">
|
||||
<div class="certificate-template">
|
||||
<img src="/images/teacher/certificate.png" alt="证书模板" class="template-image" />
|
||||
<span class="template-label">证书模板</span>
|
||||
</div>
|
||||
<div class="certificate-template">
|
||||
<img src="/images/teacher/certificate.png" alt="证书模板" class="template-image" />
|
||||
<span class="template-label">证书模板</span>
|
||||
</div>
|
||||
<div class="certificate-template">
|
||||
<img src="/images/teacher/certificate.png" alt="证书模板" class="template-image" />
|
||||
<span class="template-label">证书模板</span>
|
||||
</div>
|
||||
<div class="certificate-template">
|
||||
<img src="/images/teacher/certificate.png" alt="证书模板" class="template-image" />
|
||||
<span class="template-label">证书模板</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 选择证书尺寸模态框 -->
|
||||
<div v-if="showModal" class="modal-overlay" @click="showModal = false">
|
||||
<div class="modal-content" @click.stop>
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">选择证书尺寸</h3>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<!-- 方向选择 -->
|
||||
<div class="section">
|
||||
<h4 class="section-title">方向</h4>
|
||||
<div class="orientation-options">
|
||||
<div class="orientation-option" :class="{ active: selectedOrientation === 'landscape' }"
|
||||
@click="selectedOrientation = 'landscape'">
|
||||
<img src="/images/teacher/horizontal-version.png" alt="横版" class="orientation-icon" />
|
||||
<div class="orientation-text">
|
||||
<div>A4 (横版)</div>
|
||||
<div class="dimensions">30cm*21cm</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="orientation-option" :class="{ active: selectedOrientation === 'portrait' }"
|
||||
@click="selectedOrientation = 'portrait'">
|
||||
<img src="/images/teacher/vertical-version.png" alt="竖版" class="orientation-icon" />
|
||||
<div class="orientation-text">
|
||||
<div>A4 (竖版)</div>
|
||||
<div class="dimensions">21cm*30cm</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设置证书有效期 -->
|
||||
<div class="section">
|
||||
<h4 class="section-title">设置证书有效期:</h4>
|
||||
<div class="validity-options">
|
||||
<label class="validity-option">
|
||||
<input type="radio" v-model="validityType" value="duration" class="radio-input" />
|
||||
<span class="radio-label">自颁发日起</span>
|
||||
<div class="duration-input">
|
||||
<input type="number" v-model="validityDuration" class="number-input" />
|
||||
<span class="duration-text">月内有效<span> (设置0则代表证书发出终生有效)</span></span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="validity-option">
|
||||
<input type="radio" v-model="validityType" value="date" class="radio-input" />
|
||||
<span class="radio-label">有效期至</span>
|
||||
<div class="date-input">
|
||||
<input type="datetime-local" v-model="validityDate" class="date-field" />
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-cancel" @click="showModal = false">取消</button>
|
||||
<button class="btn btn-next" @click="handleNext">下一步</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
// const message = useMessage()
|
||||
|
||||
// 模态框控制
|
||||
const showModal = ref(false)
|
||||
|
||||
// 表单数据
|
||||
// const formData = reactive({
|
||||
// template: ''
|
||||
// })
|
||||
|
||||
// 方向选择
|
||||
const selectedOrientation = ref('landscape')
|
||||
|
||||
// 有效期设置
|
||||
const validityType = ref('duration')
|
||||
const validityDuration = ref(60)
|
||||
const validityDate = ref('')
|
||||
|
||||
// 处理下一步
|
||||
const handleNext = () => {
|
||||
console.log('选择的配置:', {
|
||||
orientation: selectedOrientation.value,
|
||||
validityType: validityType.value,
|
||||
validityDuration: validityDuration.value,
|
||||
validityDate: validityDate.value
|
||||
})
|
||||
|
||||
// 构建查询参数
|
||||
const queryParams = {
|
||||
orientation: selectedOrientation.value,
|
||||
validityType: validityType.value,
|
||||
validityDuration: validityDuration.value.toString(),
|
||||
validityDate: validityDate.value
|
||||
}
|
||||
|
||||
// 关闭模态框
|
||||
showModal.value = false
|
||||
|
||||
// 跳转到新建证书页面
|
||||
const currentPath = window.location.pathname
|
||||
const courseIdMatch = currentPath.match(/\/course-editor\/(\d+)/)
|
||||
const courseId = courseIdMatch ? courseIdMatch[1] : '1'
|
||||
|
||||
router.push({
|
||||
path: '/teacher/certificate/new',
|
||||
query: {
|
||||
...queryParams,
|
||||
courseId: courseId
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.certificate-create {
|
||||
min-height: 100vh;
|
||||
background: #fff;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
/* 主要内容 */
|
||||
.content {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
|
||||
/* 创建证书区域 */
|
||||
.create-certificate-section {
|
||||
background: #fff;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
background: #F7F8FA;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.upload-area:hover {
|
||||
border-color: #0288D1;
|
||||
background: #f0f8ff;
|
||||
}
|
||||
|
||||
.upload-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.upload-icon {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 分割线 */
|
||||
.divider {
|
||||
position: relative;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.divider::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: #E6E6E6;
|
||||
}
|
||||
|
||||
.divider-text {
|
||||
background: #fff;
|
||||
padding: 0 15px;
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 通用证书区域 */
|
||||
.general-certificate-section {
|
||||
background: #fff;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.certificate-templates {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.certificate-template {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.template-image {
|
||||
width: 213px;
|
||||
height: 160px;
|
||||
object-fit: contain;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.template-label {
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 模态框样式 */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border-radius: 2px;
|
||||
width: 90%;
|
||||
max-width: 700px;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
min-height: 600px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1.5px solid #E6E6E6;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding-top: 20px;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* 方向选择 */
|
||||
.section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.section h4 {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
|
||||
.orientation-options {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.orientation-option {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
background: #F7F8FA;
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.orientation-option:hover {
|
||||
border-color: #0288D1;
|
||||
}
|
||||
|
||||
.orientation-option.active {
|
||||
border-color: #0288D1;
|
||||
background: #f0f8ff;
|
||||
border: 1px solid #0288D1;
|
||||
}
|
||||
|
||||
.orientation-icon {
|
||||
width: 34px;
|
||||
height: 38px;
|
||||
margin: 0 auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.orientation-text {
|
||||
margin-top: 6px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.dimensions {
|
||||
font-size: 10px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 有效期设置 */
|
||||
.validity-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.validity-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.radio-input {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
accent-color: #0288D1;
|
||||
}
|
||||
|
||||
.radio-label {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.duration-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.number-input {
|
||||
width: 60px;
|
||||
padding: 8px 8px;
|
||||
border: 1px solid #E6E6E6;
|
||||
background-color: #FCFCFC;
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.duration-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.duration-text span {
|
||||
margin-left: 8px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.date-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.date-field {
|
||||
padding: 8px 8px;
|
||||
border: 1px solid #E6E6E6;
|
||||
background-color: #FCFCFC;
|
||||
font-size: 14px;
|
||||
width: 200px;
|
||||
background-image: url('/images/teacher/日历-选中.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: left 8px center;
|
||||
background-size: 16px 16px;
|
||||
padding-left: 32px;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
/* 隐藏WebKit浏览器的默认日历图标 */
|
||||
.date-field::-webkit-calendar-picker-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 隐藏WebKit浏览器的默认清除按钮 */
|
||||
.date-field::-webkit-clear-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 隐藏WebKit浏览器的默认内部阴影 */
|
||||
.date-field::-webkit-inner-spin-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn {
|
||||
padding: 6px 20px;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
min-width: 90px;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: #E2F5FF;
|
||||
border: 1px solid #0288D1;
|
||||
color: #0288D1;
|
||||
}
|
||||
|
||||
.btn-next {
|
||||
background: #0288D1;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.certificate-create {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.orientation-options {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 95%;
|
||||
margin: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
611
src/views/teacher/certificate/CertificateDetail.vue
Normal file
@ -0,0 +1,611 @@
|
||||
<template>
|
||||
<div class="certificate-detail">
|
||||
<!-- 左侧:证书样式和基本信息 -->
|
||||
<div class="left-section">
|
||||
<!-- 证书样式 -->
|
||||
<div class="certificate-style">
|
||||
<h2 class="section-title">证书样式</h2>
|
||||
<div class="certificate-preview">
|
||||
<img :src="certificate.thumbnail" :alt="certificate.name" class="certificate-image" />
|
||||
</div>
|
||||
|
||||
<!-- 证书信息 -->
|
||||
<div class="certificate-info">
|
||||
<h3 class="info-title">证书信息</h3>
|
||||
<div class="info-item">
|
||||
<span class="label">证书名称:</span>
|
||||
<span class="value">{{ certificate.name }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">证书分类:</span>
|
||||
<span class="value">{{ certificate.category }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">证书内容:</span>
|
||||
<div class="content-list">
|
||||
<div class="content-item">考试分数</div>
|
||||
<div class="content-item">考生姓名</div>
|
||||
<div class="content-item">认证时间</div>
|
||||
<div class="content-item">考试名称</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="action-buttons">
|
||||
<button class="action-btn edit-btn">
|
||||
<img src="/images/teacher/edit.png" alt="编辑" class="btn-icon">
|
||||
<span>编辑</span>
|
||||
</button>
|
||||
<button class="action-btn download-btn">
|
||||
<img src="/images/teacher/download.png" alt="下载" class="btn-icon">
|
||||
<span>下载</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:证书颁发记录 -->
|
||||
<div class="right-section">
|
||||
<div class="issuance-record">
|
||||
<div class="toolbar">
|
||||
<h2>证书颁发记录</h2>
|
||||
<div class="toolbar-actions">
|
||||
<button class="btn btn-primary" @click="showIssuanceModal = true">颁发证书</button>
|
||||
<button class="btn btn-danger">删除</button>
|
||||
<div class="filter-dropdown">
|
||||
<select class="filter-select">
|
||||
<option value="">班级名称</option>
|
||||
<option value="class1">班级名称1</option>
|
||||
<option value="class2">班级名称2</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="search-box">
|
||||
<input type="text" placeholder="请输入关键词" class="search-input" />
|
||||
<button class="btn btn-search">搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 证书颁发记录表格 -->
|
||||
<div class="record-table">
|
||||
<n-data-table
|
||||
:columns="columns"
|
||||
:data="issuanceRecords"
|
||||
:pagination="false"
|
||||
:bordered="false"
|
||||
:single-line="false"
|
||||
:row-key="(row) => row.id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 证书颁发模态框 -->
|
||||
<CertificateIssuanceModal
|
||||
v-model:show="showIssuanceModal"
|
||||
@confirm="handleIssuanceConfirm"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, h } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useMessage, NDataTable, NButton } from 'naive-ui'
|
||||
import CertificateIssuanceModal from '@/components/CertificateIssuanceModal.vue'
|
||||
|
||||
const route = useRoute()
|
||||
// const router = useRouter()
|
||||
const message = useMessage()
|
||||
|
||||
// 证书数据
|
||||
const certificate = ref({
|
||||
id: 0,
|
||||
name: '',
|
||||
category: '',
|
||||
thumbnail: ''
|
||||
})
|
||||
|
||||
// 定义数据类型
|
||||
interface IssuanceRecord {
|
||||
id: number
|
||||
selected: boolean
|
||||
certificateName: string
|
||||
certificateNumber: string
|
||||
issuanceTime: string
|
||||
issuer: string
|
||||
issuanceMethod: string
|
||||
usageCategory: string
|
||||
studentName: string
|
||||
studentAccount: string
|
||||
className: string
|
||||
isExpired: string
|
||||
validUntil: string
|
||||
}
|
||||
|
||||
// 颁发记录数据
|
||||
const issuanceRecords = ref<IssuanceRecord[]>([
|
||||
{
|
||||
id: 1,
|
||||
selected: true,
|
||||
certificateName: '证书名称',
|
||||
certificateNumber: '2665585665',
|
||||
issuanceTime: '2025.08.12 9:22',
|
||||
issuer: '王建国',
|
||||
issuanceMethod: '系统颁发',
|
||||
usageCategory: '考试证书',
|
||||
studentName: '某某某',
|
||||
studentAccount: '556655455',
|
||||
className: '班级名称1',
|
||||
isExpired: '否',
|
||||
validUntil: '永久有效'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
selected: true,
|
||||
certificateName: '证书名称',
|
||||
certificateNumber: '2665585665',
|
||||
issuanceTime: '2025.08.12 9:22',
|
||||
issuer: '王建国',
|
||||
issuanceMethod: '系统颁发',
|
||||
usageCategory: '学习项目',
|
||||
studentName: '某某某',
|
||||
studentAccount: '556655455',
|
||||
className: '班级名称1',
|
||||
isExpired: '否',
|
||||
validUntil: '永久有效'
|
||||
}
|
||||
])
|
||||
|
||||
// 全选状态
|
||||
// const selectAll = ref(true)
|
||||
|
||||
// 模态框状态
|
||||
const showIssuanceModal = ref(false)
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
type: 'selection' as const,
|
||||
width: 40
|
||||
},
|
||||
{
|
||||
title: '证书名称',
|
||||
key: 'certificateName',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '证书编号',
|
||||
key: 'certificateNumber',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '颁发时间',
|
||||
key: 'issuanceTime',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '颁发人',
|
||||
key: 'issuer',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '颁发方式',
|
||||
key: 'issuanceMethod',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '使用类别',
|
||||
key: 'usageCategory',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '学员姓名',
|
||||
key: 'studentName',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '学员帐号',
|
||||
key: 'studentAccount',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '班级名称',
|
||||
key: 'className',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '是否过期',
|
||||
key: 'isExpired',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '有效期至',
|
||||
key: 'validUntil',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 100,
|
||||
render: (row: IssuanceRecord) => {
|
||||
return h('div', { class: 'operations' }, [
|
||||
h(NButton, {
|
||||
size: 'small',
|
||||
type: 'primary',
|
||||
class: 'revoke-btn',
|
||||
onClick: () => handleRevoke(row)
|
||||
}, { default: () => '撤回' }),
|
||||
h(NButton, {
|
||||
size: 'small',
|
||||
type: 'error',
|
||||
class: 'delete-btn',
|
||||
onClick: () => handleDelete(row)
|
||||
}, { default: () => '删除' })
|
||||
])
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 获取证书ID
|
||||
const certificateId = route.params.id
|
||||
|
||||
// 模拟获取证书数据
|
||||
onMounted(() => {
|
||||
certificate.value = {
|
||||
id: Number(certificateId),
|
||||
name: '名称名称名称',
|
||||
category: '分类分类',
|
||||
thumbnail: '/images/teacher/certificate.png'
|
||||
}
|
||||
})
|
||||
|
||||
// 全选处理
|
||||
// const handleSelectAll = () => {
|
||||
// issuanceRecords.value.forEach(record => {
|
||||
// record.selected = selectAll.value
|
||||
// })
|
||||
// }
|
||||
|
||||
// 单项选择处理
|
||||
// const handleItemSelect = () => {
|
||||
// selectAll.value = issuanceRecords.value.every(record => record.selected)
|
||||
// }
|
||||
|
||||
// 撤回处理
|
||||
const handleRevoke = (record: IssuanceRecord) => {
|
||||
console.log('撤回证书:', record)
|
||||
message.success('撤回成功')
|
||||
}
|
||||
|
||||
// 删除处理
|
||||
const handleDelete = (record: IssuanceRecord) => {
|
||||
console.log('删除证书:', record)
|
||||
message.success('删除成功')
|
||||
}
|
||||
|
||||
// 处理颁发确认
|
||||
const handleIssuanceConfirm = (selectedExams: any[]) => {
|
||||
console.log('选中的考试/学习项目:', selectedExams)
|
||||
message.success('证书颁发成功')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.certificate-detail {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 左侧区域 */
|
||||
.left-section {
|
||||
flex: 1;
|
||||
max-width: 275px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.certificate-style {
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
|
||||
.certificate-preview {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.certificate-image {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.certificate-info {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.info-title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
margin-bottom: 12px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.content-list {
|
||||
margin-top: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.content-item {
|
||||
text-align: center;
|
||||
padding: 4px 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 45%;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 1.5px solid #E6E6E6;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* 右侧区域 */
|
||||
.right-section {
|
||||
background-color: #fff;
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.issuance-record {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 顶部工具栏 - 参考证书首页样式 */
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-right: 25px;
|
||||
background: #fff;
|
||||
padding: 30px 0 20px 30px;
|
||||
border-bottom: 2px solid #F6F6F6;
|
||||
}
|
||||
|
||||
.toolbar h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.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-danger {
|
||||
background: white;
|
||||
color: #F44336;
|
||||
border: 1px solid #F44336;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #F44336;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-search {
|
||||
background: #0288D1;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 6px 16px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn-search:hover {
|
||||
background: #0277bd;
|
||||
}
|
||||
|
||||
/* 筛选下拉框 */
|
||||
.filter-dropdown {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #F1F3F4;
|
||||
border-radius: 2px;
|
||||
font-size: 14px;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.filter-select:focus {
|
||||
border-color: #0288D1;
|
||||
}
|
||||
|
||||
/* 搜索框 */
|
||||
.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;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
.record-table {
|
||||
overflow-x: auto;
|
||||
padding: 30px 30px 30px 30px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Naive UI 表格样式覆盖 */
|
||||
:deep(.n-data-table) {
|
||||
background: white;
|
||||
border: 1px solid #F1F3F4;
|
||||
}
|
||||
|
||||
:deep(.n-data-table .n-data-table-table) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
:deep(.n-data-table .n-data-table-thead .n-data-table-tr) {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
:deep(.n-data-table .n-data-table-thead .n-data-table-th) {
|
||||
background: #f5f5f5;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-weight: 600;
|
||||
color: #062333;
|
||||
padding: 5px 6px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:deep(.n-data-table .n-data-table-tbody .n-data-table-td) {
|
||||
border-bottom: 1px solid #eee;
|
||||
padding: 16px 6px;
|
||||
font-size: 12px;
|
||||
color: #062333;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.n-data-table .n-data-table-tbody .n-data-table-tr:hover) {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
:deep(.operations) {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
:deep(.revoke-btn) {
|
||||
background: transparent !important;
|
||||
color: #0288D1 !important;
|
||||
border: 1px solid #0288D1 !important;
|
||||
border-radius: 2px !important;
|
||||
font-size: 12px !important;
|
||||
padding: 6px 12px !important;
|
||||
}
|
||||
|
||||
:deep(.delete-btn) {
|
||||
background: transparent !important;
|
||||
color: #FF4D4F !important;
|
||||
border: 1px solid #FF4D4F !important;
|
||||
border-radius: 2px !important;
|
||||
font-size: 12px !important;
|
||||
padding: 6px 12px !important;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1200px) {
|
||||
.certificate-detail {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.left-section {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.toolbar {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.toolbar-actions {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.record-table {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 8px 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<div class="certificate-management">
|
||||
<div class="certificate-issuance">
|
||||
<div class="content-placeholder">
|
||||
<h2>证书管理</h2>
|
||||
<p>证书管理功能正在开发中...</p>
|
||||
<h2>证书颁发</h2>
|
||||
<p>证书颁发功能正在开发中...</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 证书管理逻辑
|
||||
// 证书颁发逻辑
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.certificate-management {
|
||||
.certificate-issuance {
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
height: 100%;
|
600
src/views/teacher/certificate/CertificateManagement.vue
Normal file
@ -0,0 +1,600 @@
|
||||
<template>
|
||||
<div class="certificate-management">
|
||||
<!-- 顶部操作栏 -->
|
||||
<div class="toolbar">
|
||||
<h2>全部证书</h2>
|
||||
<div class="toolbar-actions">
|
||||
<button class="btn btn-primary" @click="addCertificate">添加证书</button>
|
||||
<div class="filter-dropdown">
|
||||
<select v-model="selectedExam" class="filter-select">
|
||||
<option value="">考试</option>
|
||||
<option value="exam1">期末考试</option>
|
||||
<option value="exam2">期中考试</option>
|
||||
<option value="exam3">模拟考试</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="search-box">
|
||||
<input type="text" placeholder="请输入关键词" v-model="searchKeyword" />
|
||||
<button class="btn btn-search" @click="searchCertificates">搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 证书网格展示 -->
|
||||
<div class="certificate-grid">
|
||||
<div v-for="certificate in filteredCertificates" :key="certificate.id" class="certificate-card" @click="viewCertificateDetail(certificate)">
|
||||
<div class="certificate-thumbnail">
|
||||
<img :src="certificate.thumbnail" :alt="certificate.name" class="certificate-image" />
|
||||
|
||||
</div>
|
||||
<div class="certificate-info">
|
||||
<div class="certificate-name">{{ certificate.name }}</div>
|
||||
<div class="certificate-category">证书分类: {{ certificate.category }}</div>
|
||||
</div>
|
||||
<div class="file-menu">
|
||||
<button class="file-menu-btn" @click.stop="toggleFileMenu(certificate.id)">
|
||||
<img src="/images/teacher/more.png" alt="更多操作" class="more-icon" />
|
||||
</button>
|
||||
<div v-if="activeFileMenu === certificate.id" class="file-menu-dropdown">
|
||||
<div class="menu-item" @click.stop="downloadCertificate(certificate)">
|
||||
<img src="/images/teacher/download.png" alt="下载" class="menu-icon" />
|
||||
<span>下载</span>
|
||||
</div>
|
||||
<div class="menu-item" @click.stop="editCertificate(certificate)">
|
||||
<img src="/images/teacher/edit.png" alt="编辑" class="menu-icon" />
|
||||
<span>编辑</span>
|
||||
</div>
|
||||
<div class="menu-item" @click.stop="deleteCertificate(certificate)">
|
||||
<img src="/images/teacher/delete.png" alt="删除" class="menu-icon" />
|
||||
<span>删除</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加证书模态框 -->
|
||||
<div v-if="showAddModal" class="modal-overlay" @click="closeAddModal">
|
||||
<div class="modal-content" @click.stop>
|
||||
<div class="modal-header">
|
||||
<h3>添加证书</h3>
|
||||
<button class="modal-close" @click="closeAddModal">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>添加证书功能正在开发中...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
|
||||
const message = useMessage()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
// 搜索和筛选
|
||||
const searchKeyword = ref('')
|
||||
const selectedExam = ref('')
|
||||
|
||||
// 模态框控制
|
||||
const showAddModal = ref(false)
|
||||
const activeFileMenu = ref<number | null>(null)
|
||||
|
||||
// 证书数据
|
||||
const certificates = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '证书名称证书名称',
|
||||
category: '分类名称',
|
||||
title: '荣誉证书',
|
||||
issuer: '某某2021年大学生某某协会',
|
||||
badge: '第一名',
|
||||
thumbnail: '/images/teacher/certificate.png'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '优秀学员证书',
|
||||
category: '学习成果',
|
||||
title: '荣誉证书',
|
||||
issuer: '某某2021年大学生某某协会',
|
||||
badge: '优秀',
|
||||
thumbnail: '/images/teacher/certificate.png'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '技能认证证书',
|
||||
category: '技能认证',
|
||||
title: '荣誉证书',
|
||||
issuer: '某某2021年大学生某某协会',
|
||||
badge: '通过',
|
||||
thumbnail: '/images/teacher/certificate.png'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '竞赛获奖证书',
|
||||
category: '竞赛奖励',
|
||||
title: '荣誉证书',
|
||||
issuer: '某某2021年大学生某某协会',
|
||||
badge: '二等奖',
|
||||
thumbnail: '/images/teacher/certificate.png'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: '培训结业证书',
|
||||
category: '培训认证',
|
||||
title: '荣誉证书',
|
||||
issuer: '某某2021年大学生某某协会',
|
||||
badge: '结业',
|
||||
thumbnail: '/images/teacher/certificate.png'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: '项目完成证书',
|
||||
category: '项目认证',
|
||||
title: '荣誉证书',
|
||||
issuer: '某某2021年大学生某某协会',
|
||||
badge: '完成',
|
||||
thumbnail: '/images/teacher/certificate.png'
|
||||
}
|
||||
])
|
||||
|
||||
// 过滤后的证书
|
||||
const filteredCertificates = computed(() => {
|
||||
let filtered = certificates.value
|
||||
|
||||
if (searchKeyword.value) {
|
||||
filtered = filtered.filter((cert: any) =>
|
||||
cert.name.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
|
||||
cert.category.toLowerCase().includes(searchKeyword.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (selectedExam.value) {
|
||||
// 这里可以根据考试类型进行筛选
|
||||
filtered = filtered.filter((cert: any) => cert.category === selectedExam.value)
|
||||
}
|
||||
|
||||
return filtered
|
||||
})
|
||||
|
||||
// 方法
|
||||
const addCertificate = () => {
|
||||
// 从当前路径中提取课程ID
|
||||
const currentPath = route.path;
|
||||
const courseIdMatch = currentPath.match(/\/course-editor\/(\d+)/);
|
||||
const courseId = courseIdMatch ? courseIdMatch[1] : '1';
|
||||
|
||||
// 跳转到添加证书页面
|
||||
const targetPath = `/teacher/course-editor/${courseId}/certificate/add`;
|
||||
router.push(targetPath);
|
||||
}
|
||||
|
||||
const closeAddModal = () => {
|
||||
showAddModal.value = false
|
||||
}
|
||||
|
||||
const searchCertificates = () => {
|
||||
message.info('搜索证书: ' + searchKeyword.value)
|
||||
}
|
||||
|
||||
const toggleFileMenu = (id: number) => {
|
||||
console.log('点击了更多操作按钮,ID:', id)
|
||||
activeFileMenu.value = activeFileMenu.value === id ? null : id
|
||||
console.log('当前激活的菜单:', activeFileMenu.value)
|
||||
}
|
||||
|
||||
const downloadCertificate = (certificate: any) => {
|
||||
message.success(`下载证书: ${certificate.name}`)
|
||||
activeFileMenu.value = null
|
||||
}
|
||||
|
||||
const editCertificate = (certificate: any) => {
|
||||
message.info(`编辑证书: ${certificate.name}`)
|
||||
activeFileMenu.value = null
|
||||
}
|
||||
|
||||
const deleteCertificate = (certificate: any) => {
|
||||
if (confirm(`确定要删除证书 "${certificate.name}" 吗?`)) {
|
||||
const index = certificates.value.findIndex((c: any) => c.id === certificate.id)
|
||||
if (index > -1) {
|
||||
certificates.value.splice(index, 1)
|
||||
message.success('删除成功')
|
||||
}
|
||||
}
|
||||
activeFileMenu.value = null
|
||||
}
|
||||
|
||||
const viewCertificateDetail = (certificate: any) => {
|
||||
console.log('点击了证书卡片:', certificate);
|
||||
// 从当前路径中提取课程ID
|
||||
const currentPath = route.path;
|
||||
console.log('当前路径:', currentPath);
|
||||
const courseIdMatch = currentPath.match(/\/course-editor\/(\d+)/);
|
||||
const courseId = courseIdMatch ? courseIdMatch[1] : '1';
|
||||
console.log('提取的课程ID:', courseId);
|
||||
|
||||
const targetPath = `/teacher/course-editor/${courseId}/certificate/detail/${certificate.id}`;
|
||||
console.log('目标路径:', targetPath);
|
||||
|
||||
router.push(targetPath);
|
||||
}
|
||||
|
||||
// 点击外部关闭更多操作菜单
|
||||
const closeFileMenu = () => {
|
||||
activeFileMenu.value = null
|
||||
}
|
||||
|
||||
// 监听点击事件
|
||||
document.addEventListener('click', closeFileMenu)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.certificate-management {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 顶部工具栏 */
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-right: 25px;
|
||||
background: #fff;
|
||||
padding: 30px 0 20px 30px;
|
||||
border-bottom: 2px solid #F6F6F6;
|
||||
}
|
||||
|
||||
.toolbar h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.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-search {
|
||||
background: #0288D1;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 6px 16px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn-search:hover {
|
||||
background: #0277bd;
|
||||
}
|
||||
|
||||
/* 筛选下拉框 */
|
||||
.filter-dropdown {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #F1F3F4;
|
||||
border-radius: 2px;
|
||||
font-size: 14px;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.filter-select:focus {
|
||||
border-color: #0288D1;
|
||||
}
|
||||
|
||||
/* 搜索框 */
|
||||
.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;
|
||||
}
|
||||
|
||||
/* 证书网格 */
|
||||
.certificate-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 20px;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
/* 证书卡片 */
|
||||
.certificate-card {
|
||||
padding: 50px 20px 10px 20px;
|
||||
position: relative;
|
||||
background: white;
|
||||
border: 1.5px solid #D8D8D8;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
min-height: 215px;
|
||||
}
|
||||
|
||||
.certificate-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
background-color: #F3F5F8;
|
||||
}
|
||||
|
||||
/* 证书缩略图 */
|
||||
.certificate-thumbnail {
|
||||
position: relative;
|
||||
height: 120px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.certificate-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.certificate-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
/* 证书信息 */
|
||||
.certificate-info {
|
||||
padding: 5px 20px;
|
||||
}
|
||||
|
||||
.certificate-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.certificate-category {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 文件菜单 */
|
||||
.file-menu {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.file-menu-btn {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.more-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.file-menu-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
z-index: 20;
|
||||
min-width: 60px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 6px 10px;
|
||||
font-size: 10px;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
object-fit: contain;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.menu-item:first-child {
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.menu-item:last-child {
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
|
||||
/* 模态框 */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.modal-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1400px) {
|
||||
.certificate-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 15px;
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.certificate-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 15px;
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.certificate-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 15px;
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.toolbar {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.toolbar-actions {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.certificate-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.certificate-thumbnail {
|
||||
height: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.toolbar-actions {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.certificate-grid {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
475
src/views/teacher/certificate/CertificateNew.vue
Normal file
@ -0,0 +1,475 @@
|
||||
<template>
|
||||
<div class="certificate-new">
|
||||
<div class="top-section">
|
||||
<h1 class="page-title">新建证书11</h1>
|
||||
|
||||
<!-- 右侧区域 -->
|
||||
<div class="right-section" :class="{ 'collapsed': isCollapsed }">
|
||||
<div class="content">
|
||||
<!-- 顶部按钮 -->
|
||||
<div class="header-buttons">
|
||||
<button class="btn btn-preview">证书预览</button>
|
||||
<button class="btn btn-save">保存</button>
|
||||
</div>
|
||||
|
||||
<!-- 证书背景 -->
|
||||
<div class="section">
|
||||
<h3 class="section-title">证书背景</h3>
|
||||
<div class="color-palette">
|
||||
<div class="color-item rainbow" :class="{ 'selected': selectedColor === customColor }" @click="showColorPicker = !showColorPicker">
|
||||
<input
|
||||
v-if="showColorPicker"
|
||||
type="color"
|
||||
:value="customColor"
|
||||
@input="selectCustomColor($event.target.value)"
|
||||
class="color-input-inline"
|
||||
@click.stop
|
||||
/>
|
||||
</div>
|
||||
<div class="color-item black" :class="{ 'selected': selectedColor === '#000000' }" @click="selectColor('#000000')"></div>
|
||||
<div class="color-item dark-gray" :class="{ 'selected': selectedColor === '#333333' }" @click="selectColor('#333333')"></div>
|
||||
<div class="color-item gray" :class="{ 'selected': selectedColor === '#666666' }" @click="selectColor('#666666')"></div>
|
||||
<div class="color-item light-gray" :class="{ 'selected': selectedColor === '#999999' }" @click="selectColor('#999999')"></div>
|
||||
<div class="color-item white" :class="{ 'selected': selectedColor === '#ffffff' }" @click="selectColor('#ffffff')"></div>
|
||||
<div class="color-item red" :class="{ 'selected': selectedColor === '#ff0000' }" @click="selectColor('#ff0000')"></div>
|
||||
<div class="color-item orange" :class="{ 'selected': selectedColor === '#ff8000' }" @click="selectColor('#ff8000')"></div>
|
||||
<div class="color-item pink" :class="{ 'selected': selectedColor === '#ff80ff' }" @click="selectColor('#ff80ff')"></div>
|
||||
<div class="color-item purple" :class="{ 'selected': selectedColor === '#8000ff' }" @click="selectColor('#8000ff')"></div>
|
||||
<div class="color-item green" :class="{ 'selected': selectedColor === '#00ff00' }" @click="selectColor('#00ff00')"></div>
|
||||
<div class="color-item teal" :class="{ 'selected': selectedColor === '#00ffff' }" @click="selectColor('#00ffff')"></div>
|
||||
<div class="color-item light-blue" :class="{ 'selected': selectedColor === '#80ffff' }" @click="selectColor('#80ffff')"></div>
|
||||
<div class="color-item blue" :class="{ 'selected': selectedColor === '#0080ff' }" @click="selectColor('#0080ff')"></div>
|
||||
</div>
|
||||
<div class="template-preview">
|
||||
<div class="certificate-template">
|
||||
<img src="/images/teacher/certificate.png" alt="证书模板" />
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-replace btn-full-width">替换背景图</button>
|
||||
</div>
|
||||
|
||||
<!-- 添加元素 -->
|
||||
<div class="section">
|
||||
<h3 class="section-title">添加元素</h3>
|
||||
<div class="element-buttons">
|
||||
<button class="btn btn-add">添加文字</button>
|
||||
<button class="btn btn-add">添加图片</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 证书信息 -->
|
||||
<div class="section">
|
||||
<h3 class="section-title">证书信息</h3>
|
||||
<div class="info-fields">
|
||||
<div class="field">证书编号</div>
|
||||
<div class="field">发证日期</div>
|
||||
<div class="field">颁发机构</div>
|
||||
<div class="field">有效期至</div>
|
||||
<div class="field">核验二维码</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 学员信息 -->
|
||||
<div class="section">
|
||||
<h3 class="section-title">学员信息</h3>
|
||||
<div class="info-fields">
|
||||
<div class="field">学员姓名</div>
|
||||
<div class="field">学员帐号</div>
|
||||
<div class="field">班级名称</div>
|
||||
<div class="field">自定义文字</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 考试信息 -->
|
||||
<div class="section">
|
||||
<h3 class="section-title">考试信息</h3>
|
||||
<div class="info-fields">
|
||||
<div class="field">考试名称</div>
|
||||
<div class="field">考试分数</div>
|
||||
<div class="field">考试评语</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 收起/展开按钮 -->
|
||||
<div class="collapse-button" @click="toggleCollapse">
|
||||
<img src="/images/teacher/上传2.png" alt="收起/展开" class="arrow" :class="{ 'collapsed': isCollapsed }" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="certificate-content" :style="{ backgroundColor: selectedColor }">
|
||||
<img src="/images/teacher/certificate.png" alt="">
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
// 保存原始样式
|
||||
let originalPadding = ''
|
||||
|
||||
// 右侧面板收起状态
|
||||
const isCollapsed = ref(false)
|
||||
|
||||
// 选中的背景颜色
|
||||
const selectedColor = ref('#ffffff')
|
||||
|
||||
// 自定义颜色
|
||||
const customColor = ref('#ff0000')
|
||||
|
||||
// 显示颜色选择器
|
||||
const showColorPicker = ref(false)
|
||||
|
||||
// 切换收起/展开状态
|
||||
const toggleCollapse = () => {
|
||||
isCollapsed.value = !isCollapsed.value
|
||||
}
|
||||
|
||||
// 选择背景颜色
|
||||
const selectColor = (color: string) => {
|
||||
selectedColor.value = color
|
||||
}
|
||||
|
||||
// 选择自定义颜色
|
||||
const selectCustomColor = (color: string) => {
|
||||
customColor.value = color
|
||||
selectedColor.value = color
|
||||
showColorPicker.value = false
|
||||
}
|
||||
|
||||
|
||||
// 获取URL参数
|
||||
onMounted(() => {
|
||||
console.log('新建证书页面参数:', route.query)
|
||||
|
||||
// 动态修改上级容器的内边距,只影响当前页面
|
||||
const routerViewContainer = document.querySelector('.router-view-container') as HTMLElement
|
||||
if (routerViewContainer) {
|
||||
// 保存原始样式
|
||||
originalPadding = routerViewContainer.style.padding
|
||||
// 设置新样式
|
||||
routerViewContainer.style.padding = '0'
|
||||
}
|
||||
})
|
||||
|
||||
// 组件卸载时恢复原始样式
|
||||
onUnmounted(() => {
|
||||
const routerViewContainer = document.querySelector('.router-view-container') as HTMLElement
|
||||
if (routerViewContainer) {
|
||||
routerViewContainer.style.padding = originalPadding
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 只在当前页面覆盖上级容器的内边距 */
|
||||
.certificate-new:deep(.router-view-container) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.certificate-new:deep(.router-view-container.full-width) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* 确保页面内容不受上级容器影响 */
|
||||
.certificate-new {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background: #F6F6F6;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.top-section {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.right-section {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 240px;
|
||||
height: 100vh;
|
||||
background: #fff;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.right-section.collapsed {
|
||||
transform: translateX(240px);
|
||||
}
|
||||
|
||||
.collapse-button {
|
||||
position: absolute;
|
||||
left: -40px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
height: 0;
|
||||
width: 60px;
|
||||
border-bottom: 20px solid #fff;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
transform: rotate(-90deg);
|
||||
transform-origin: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.collapse-button:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
margin-top: 20px;
|
||||
width: 10px;
|
||||
height: 6px;
|
||||
transition: transform 0.3s ease;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.arrow.collapsed {
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: auto;
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
min-width: 90px;
|
||||
padding: 6px 16px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #E6E6E6;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-preview {
|
||||
background: #E3F2FD;
|
||||
color: #0288D1;
|
||||
border-color: #0288D1;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
background: #0288D1;
|
||||
color: #fff;
|
||||
border-color: #0288D1;
|
||||
}
|
||||
|
||||
.btn-add,
|
||||
.btn-replace {
|
||||
background: #F5F5F5;
|
||||
color: #666666;
|
||||
font-size: 12px;
|
||||
border: none;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.btn-full-width {
|
||||
width: 100%;
|
||||
border: none;
|
||||
color: #666666;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.color-palette {
|
||||
padding: 10px;
|
||||
background-color: #F7F8FA;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 4px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.color-item {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
border: 1px solid #E6E6E6;
|
||||
}
|
||||
|
||||
.color-item.rainbow {
|
||||
background: linear-gradient(45deg, #ff0000, #ff8000, #ffff00, #80ff00, #00ff00, #00ff80, #00ffff, #0080ff, #0000ff, #8000ff, #ff00ff, #ff0080);
|
||||
}
|
||||
|
||||
.color-item.black {
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.color-item.dark-gray {
|
||||
background: #333;
|
||||
}
|
||||
|
||||
.color-item.gray {
|
||||
background: #666;
|
||||
}
|
||||
|
||||
.color-item.light-gray {
|
||||
background: #999;
|
||||
}
|
||||
|
||||
.color-item.white {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.color-item.red {
|
||||
background: #ff0000;
|
||||
}
|
||||
|
||||
.color-item.orange {
|
||||
background: #ff8000;
|
||||
}
|
||||
|
||||
.color-item.pink {
|
||||
background: #ff80ff;
|
||||
}
|
||||
|
||||
.color-item.purple {
|
||||
background: #8000ff;
|
||||
}
|
||||
|
||||
.color-item.green {
|
||||
background: #00ff00;
|
||||
}
|
||||
|
||||
.color-item.teal {
|
||||
background: #00ffff;
|
||||
}
|
||||
|
||||
.color-item.light-blue {
|
||||
background: #80ffff;
|
||||
}
|
||||
|
||||
.color-item.blue {
|
||||
background: #0080ff;
|
||||
}
|
||||
|
||||
.color-item.selected {
|
||||
border: 2px solid #0288D1;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.template-preview {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.certificate-template {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
background-color: #F7F8FA;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.certificate-template img {
|
||||
width: 111px;
|
||||
height: 83px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.element-buttons {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.info-fields {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.field {
|
||||
padding: 7px 12px;
|
||||
background: #F7F8FA;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
color: #666666;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.field:hover {
|
||||
background: #E8E8E8;
|
||||
}
|
||||
|
||||
/* 内联颜色选择器样式 */
|
||||
.color-input-inline {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.color-item.rainbow {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.certificate-content {
|
||||
width: 1000px;
|
||||
height: 89vh;
|
||||
margin: 0 auto;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.certificate-content img {
|
||||
width: 800px;
|
||||
|
||||
}
|
||||
</style>
|
36
src/views/teacher/certificate/CertificateQuery.vue
Normal file
@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="certificate-query">
|
||||
<div class="content-placeholder">
|
||||
<h2>证书查询</h2>
|
||||
<p>证书查询功能正在开发中...</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 证书查询逻辑
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.certificate-query {
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
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>
|
36
src/views/teacher/certificate/CertificateTemplate.vue
Normal file
@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="certificate-template">
|
||||
<div class="content-placeholder">
|
||||
<h2>证书模板管理1</h2>
|
||||
<p>证书模板管理功能正在开发中...</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 证书模板管理逻辑
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.certificate-template {
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
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>
|
97
src/views/teacher/certificate/index.vue
Normal file
@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<div class="certificate-index">
|
||||
<div class="content-placeholder">
|
||||
<h2>证书管理</h2>
|
||||
<p>欢迎使用证书管理系统</p>
|
||||
<div class="certificate-menu">
|
||||
<div class="menu-item" @click="navigateTo('/teacher/certificate/management')">
|
||||
<h3>证书管理</h3>
|
||||
<p>管理已颁发的证书</p>
|
||||
</div>
|
||||
<div class="menu-item" @click="navigateTo('/teacher/certificate/template')">
|
||||
<h3>证书模板</h3>
|
||||
<p>管理证书模板</p>
|
||||
</div>
|
||||
<div class="menu-item" @click="navigateTo('/teacher/certificate/issuance')">
|
||||
<h3>证书颁发</h3>
|
||||
<p>颁发新证书</p>
|
||||
</div>
|
||||
<div class="menu-item" @click="navigateTo('/teacher/certificate/query')">
|
||||
<h3>证书查询</h3>
|
||||
<p>查询证书信息</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const navigateTo = (path: string) => {
|
||||
router.push(path)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.certificate-index {
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.content-placeholder {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.content-placeholder h2 {
|
||||
font-size: 28px;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.content-placeholder p {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.certificate-menu {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
padding: 30px 20px;
|
||||
border: 1px solid #e6e6e6;
|
||||
border-radius: 8px;
|
||||
background: #fafafa;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
border-color: #0288D1;
|
||||
background: #E2F5FF;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(2, 136, 209, 0.15);
|
||||
}
|
||||
|
||||
.menu-item h3 {
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.menu-item p {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
@ -226,6 +226,7 @@ const handleConfirm = () => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
}
|
||||
.button-text1{
|
||||
color: #0288D1 ;
|
||||
@ -398,6 +399,7 @@ const handleConfirm = () => {
|
||||
/* 字体居中 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* 小手 */
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
|
@ -146,7 +146,9 @@ const hideSidebar = computed(() => {
|
||||
'add-question', // 新增试题页面
|
||||
'add-homework', // 添加作业页面
|
||||
'template-import',
|
||||
'review/'
|
||||
'review/',
|
||||
'certificate/detail/',
|
||||
'certificate/add'
|
||||
]
|
||||
|
||||
// 检查当前路径是否包含需要隐藏侧边栏的路径
|
||||
|
@ -145,6 +145,7 @@ const handleConfirm = () => {
|
||||
background-size: 100% 100%;
|
||||
margin: 0 auto;
|
||||
border-radius: 8px;
|
||||
padding-top: 20px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
@ -169,7 +170,7 @@ const handleConfirm = () => {
|
||||
background-size: 100% 100%;
|
||||
margin: 46px 0 0 22px;
|
||||
border: 1px solid rgba(204, 204, 204, 1);
|
||||
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.upload-label {
|
||||
@ -354,7 +355,9 @@ const handleConfirm = () => {
|
||||
.action-buttons {
|
||||
width: 147px;
|
||||
height: 32px;
|
||||
margin: 103px 0 23px 905px;
|
||||
margin: 103px 0 0 905px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.cancel-btn-container {
|
||||
@ -370,6 +373,9 @@ const handleConfirm = () => {
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.cancel-btn:hover {
|
||||
@ -387,7 +393,6 @@ const handleConfirm = () => {
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
line-height: 22px;
|
||||
margin: 5px 0 0 17px;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
@ -398,6 +403,9 @@ const handleConfirm = () => {
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.confirm-btn:hover {
|
||||
@ -415,7 +423,6 @@ const handleConfirm = () => {
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
line-height: 22px;
|
||||
margin: 5px 0 0 17px;
|
||||
}
|
||||
|
||||
/* 添加按钮悬停效果 */
|
||||
|