1045 lines
30 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="course-create-container">
<div class="form-container">
<!-- 表单内容 -->
<div class="form-content">
<div class="header-left">
<n-button quaternary circle size="large" @click="goBack">
<template #icon>
<n-icon>
<ArrowBackOutline />
</n-icon>
</template>
</n-button>
<h2 class="page-title">{{ pageTitle }}</h2>
</div>
<!-- 上半部分两列布局 -->
<div class="form-row">
<!-- 左列 -->
<div class="form-column">
<!-- 课程名称 -->
<div class="form-item">
<label class="form-label required">课程名称:</label>
<n-input v-model:value="formData.courseName" placeholder="请输入课程名称" class="form-input" />
</div>
<!-- 课程开始时间 -->
<div class="form-item">
<label class="form-label required">课程开始时间:</label>
<n-date-picker v-model:value="formData.startTime" type="datetime" placeholder="选择时间" class="form-input" />
</div>
<!-- 主讲老师 -->
<div class="form-item">
<label class="form-label required">主讲老师:</label>
<n-select v-model:value="formData.instructors" multiple :options="instructorOptions" placeholder="请选择主讲老师"
class="form-input" />
</div>
<!-- 参与学员 -->
<div class="form-item">
<label class="form-label required">参与学员:</label>
<div class="radio-container">
<label class="radio-option">
<input type="radio" name="studentType" value="all" v-model="formData.studentType"
class="radio-input" />
<span class="radio-label">全部学员</span>
</label>
<label class="radio-option">
<input type="radio" name="studentType" value="partial" v-model="formData.studentType"
class="radio-input" />
<span class="radio-label">仅部分学员</span>
</label>
</div>
</div>
<!-- 选择班级 -->
<div class="form-item" v-show="formData.studentType === 'partial'">
<label class="form-label required">选择班级:</label>
<n-select v-model:value="formData.selectedClasses" multiple :options="classOptions"
placeholder="选择班级(可多选)" class="form-input" />
</div>
</div>
<!-- 右列 -->
<div class="form-column">
<!-- 课程分类 -->
<div class="form-item">
<label class="form-label required">课程分类:</label>
<n-select v-model:value="formData.courseCategory" multiple :options="categoryOptions" placeholder="分类名称"
class="form-input" />
</div>
<!-- 排序 -->
<!-- <div class="form-item">
<label class="form-label required">排序:</label>
<n-input v-model:value="formData.sort" placeholder="请输入排序值" class="form-input" />
</div> -->
<!-- 课程结束时间 -->
<div class="form-item">
<label class="form-label required">课程结束时间:</label>
<n-date-picker v-model:value="formData.endTime" type="datetime" placeholder="选择时间" class="form-input" />
</div>
</div>
</div>
<!-- 中间部分课程封面 -->
<div class="form-section">
<div class="form-item">
<label class="form-label required">课程封面:</label>
<div class="cover-upload-container">
<div class="upload-area" @click="triggerFileUpload">
<template v-if="!previewUrl">
<div class="upload-icon">+</div>
<span class="upload-text">上传</span>
</template>
<div v-else class="preview-container">
<img :src="previewUrl" alt="课程封面预览" class="preview-image" />
<div class="delete-icon" @click.stop="clearUpload">×</div>
</div>
</div>
<input ref="fileInput" type="file" accept="image/*" @change="handleFileChange" style="display: none;" />
</div>
</div>
</div>
<!-- 课程简介 -->
<div class="form-text">
<label class="form-label required">课程简介:</label>
<div class="rich-editor-container">
<div class="editor-container">
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" :defaultConfig="toolbarConfig"
:mode="mode" />
<Editor style="height: 300px; overflow-y: hidden;" v-model="formData.courseDescription"
:defaultConfig="editorConfig" :mode="mode" @onCreated="handleCreated" />
</div>
</div>
</div>
<!-- 下半部分设置选项 -->
<div class="form-section">
<!-- 离开页面停止播放 -->
<div class="form-item btn-label">
<label class="form-label required">离开页面停止播放</label>
<div class="setting-container">
<n-switch v-model:value="formData.stopOnLeave" class="form-toggle" />
</div>
</div>
<!-- 视频倍数播放 -->
<div class="form-item btn-label">
<label class="form-label required">视频倍数播放</label>
<div class="setting-container">
<n-switch v-model:value="formData.videoSpeedControl" class="form-toggle" />
</div>
</div>
<!-- 显示视频文本 -->
<div class="form-item btn-label">
<label class="form-label required">显示视频文本</label>
<div class="setting-container">
<n-switch v-model:value="formData.showVideoText" class="form-toggle" />
</div>
</div>
<!-- 积分设置 -->
<div class="form-item form-integral" v-if="false">
<label class="form-label required">积分设置</label>
<div class="setting-container">
<n-switch v-model:value="formData.pointsEnabled" class="form-toggle" />
<span class="setting-label">学完可获得</span>
<n-input-number v-model:value="formData.earnPoints" :min="0" :max="1000" class="score-input"
:show-button="false" />
<span class="setting-label">积分</span>
<span class="setting-label">本课程需</span>
<n-input-number v-model:value="formData.requiredPoints" :min="0" :max="1000" class="score-input"
:show-button="false" />
<span class="setting-label">积分兑换</span>
</div>
</div>
</div>
</div>
<!-- 底部按钮 -->
<div class="form-footer">
<n-button class="cancel-btn" @click="handleCancel">取消</n-button>
<n-button type="primary" class="save-btn" @click="handleSubmit">保存</n-button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, shallowRef, onBeforeUnmount, onMounted, computed, Ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useMessage } from 'naive-ui'
import { ArrowBackOutline } from '@vicons/ionicons5';
import {
NInput,
NDatePicker,
NSelect,
NSwitch,
NButton,
NInputNumber
} from 'naive-ui'
import '@wangeditor/editor/dist/css/style.css'
// @ts-ignore
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import TeachCourseApi from '@/api/modules/teachCourse'
import UploadApi from '@/api/modules/upload'
import CourseApi from '@/api/modules/course';
import { useCourseStore } from '@/stores/course'
const router = useRouter()
const route = useRoute()
const message = useMessage()
const courseStore = useCourseStore()
// 判断是否为编辑模式
const isEditMode = computed(() => !!route.params.id)
const courseId = computed(() => route.params.id as string)
// 页面标题
const pageTitle = computed(() => isEditMode.value ? '编辑课程' : '创建课程')
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef()
// 文件上传相关
const fileInput = ref<HTMLInputElement>()
const previewUrl = ref('')
// 待设置的课程描述内容(用于富文本编辑器初始化后设置)
const pendingCourseDescription = ref('')
const toolbarConfig = {}
const editorConfig = { placeholder: '请输入内容...' }
const mode = 'default'
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
const handleCreated = (editor: any) => {
editorRef.value = editor // 记录 editor 实例,重要!
// 如果有待设置的内容,立即设置到编辑器
if (pendingCourseDescription.value) {
editor.setHtml(pendingCourseDescription.value);
formData.courseDescription = pendingCourseDescription.value;
pendingCourseDescription.value = ''; // 清空待设置内容
}
}
// 表单数据
const formData = reactive({
courseName: '',
courseCategory: [] as string[] | number[], // 改为 null 以显示 placeholder
instructors: [] as string[],
// sort: null as string | null, // 改为 null 以显示 placeholder
startTime: null as number | null,
endTime: null as number | null,
studentType: 'all' as 'all' | 'partial', // 'all' 或 'partial'
selectedClasses: [] as string[],
courseCover: null as File | null,
courseDescription: '',
// 视频设置
stopOnLeave: false,
videoSpeedControl: false,
showVideoText: false,
// 积分设置
pointsEnabled: true,
earnPoints: 60,
requiredPoints: 60
})
// 加载课程数据
const loadCourseData = async () => {
try {
// 首先检查store中是否有课程编辑数据
const storeCourseData = courseStore.courseEditData;
if (storeCourseData) {
// 字段映射:从后端数据映射到前端表单字段
formData.courseName = storeCourseData.name || storeCourseData.courseName || '';
// 处理课程分类:如果是字符串则分割为数组,如果是数组则直接使用
const categoryData = storeCourseData.categoryId || storeCourseData.courseCategory;
if (typeof categoryData === 'string' && categoryData) {
formData.courseCategory = categoryData.split(',').map(id => Number(id));
} else if (Array.isArray(categoryData)) {
formData.courseCategory = categoryData;
} else {
formData.courseCategory = [];
}
formData.instructors = storeCourseData.instructors || [];
formData.startTime = storeCourseData.start_time || storeCourseData.startTime || null;
formData.endTime = storeCourseData.end_time || storeCourseData.endTime || null;
formData.studentType = (storeCourseData.type === 1 || storeCourseData.studentType === 'partial') ? 'partial' : 'all';
formData.selectedClasses = storeCourseData.target ? storeCourseData.target.split(',') : (storeCourseData.selectedClasses || []);
const tempCourseDescription = storeCourseData.description || storeCourseData.courseDescription || '';
if (tempCourseDescription) {
if (editorRef.value) {
editorRef.value.setHtml(tempCourseDescription);
formData.courseDescription = tempCourseDescription;
} else {
pendingCourseDescription.value = tempCourseDescription;
}
}
// 视频设置选项从后端字段映射到前端字段数字0/1转布尔值
formData.stopOnLeave = storeCourseData.pauseExit !== undefined ?
Boolean(Number(storeCourseData.pauseExit)) : true;
formData.videoSpeedControl = storeCourseData.allowSpeed !== undefined ?
Boolean(Number(storeCourseData.allowSpeed)) : false;
formData.showVideoText = storeCourseData.showSubtitle !== undefined ?
Boolean(Number(storeCourseData.showSubtitle)) : true;
// 积分设置
formData.pointsEnabled = storeCourseData.pointsEnabled !== undefined ? storeCourseData.pointsEnabled : true;
formData.earnPoints = storeCourseData.earnPoints || 60;
formData.requiredPoints = storeCourseData.requiredPoints || 60;
if (storeCourseData.cover || storeCourseData.courseCover) {
previewUrl.value = storeCourseData.cover || storeCourseData.courseCover;
formData.courseCover = null;
}
message.success('课程数据加载成功');
return; // 如果从store获取到数据就不需要再调用API
}
// 如果store中没有数据检查是否有通过路由传递的课程数据向后兼容
const routeCourseData = route.query.courseData;
if (routeCourseData) {
try {
const courseData = JSON.parse(routeCourseData as string);
// 字段映射:从后端数据映射到前端表单字段
formData.courseName = courseData.name || courseData.courseName || '';
// 处理课程分类:如果是字符串则分割为数组,如果是数组则直接使用
const categoryData = courseData.categoryId || courseData.courseCategory;
if (typeof categoryData === 'string' && categoryData) {
formData.courseCategory = categoryData.split(',').map(id => Number(id));
} else if (Array.isArray(categoryData)) {
formData.courseCategory = categoryData;
} else {
formData.courseCategory = [];
}
formData.instructors = courseData.instructors || [];
formData.startTime = courseData.start_time || courseData.startTime || null;
formData.endTime = courseData.end_time || courseData.endTime || null;
formData.studentType = (courseData.type === 1 || courseData.studentType === 'partial') ? 'partial' : 'all';
formData.selectedClasses = courseData.target ? courseData.target.split(',') : (courseData.selectedClasses || []);
// 处理富文本编辑器内容
const tempCourseDescription = courseData.description || courseData.courseDescription || '';
if (tempCourseDescription) {
if (editorRef.value) {
// 编辑器已经初始化,直接设置
editorRef.value.setHtml(tempCourseDescription);
formData.courseDescription = tempCourseDescription;
} else {
// 编辑器还未初始化,保存到待设置变量
pendingCourseDescription.value = tempCourseDescription;
}
}
// 视频设置选项从后端字段映射到前端字段数字0/1转布尔值
formData.stopOnLeave = courseData.pauseExit !== undefined ?
Boolean(Number(courseData.pauseExit)) : true;
formData.videoSpeedControl = courseData.allowSpeed !== undefined ?
Boolean(Number(courseData.allowSpeed)) : false;
formData.showVideoText = courseData.showSubtitle !== undefined ?
Boolean(Number(courseData.showSubtitle)) : true;
// 积分设置(使用默认值或数据值)
formData.pointsEnabled = courseData.pointsEnabled !== undefined ? courseData.pointsEnabled : true;
formData.earnPoints = courseData.earnPoints || 60;
formData.requiredPoints = courseData.requiredPoints || 60;
// 如果有课程封面设置预览URL
if (courseData.cover || courseData.courseCover) {
previewUrl.value = courseData.cover || courseData.courseCover;
formData.courseCover = null; // 现有URL不是新文件
}
message.success('课程数据加载成功');
return; // 如果从路由获取到数据就不需要再调用API
} catch (parseError) {
// 如果解析失败,继续使用原来的模拟数据逻辑
}
}
// 如果既没有store数据也没有路由数据保持表单为空状态以供新建课程
} catch (error) {
message.error('加载课程数据失败')
}
}
// 组件挂载时处理
onMounted(() => {
// 如果是编辑模式有courseId、有通过路由传递的课程数据或者store中有数据
if (isEditMode.value && courseId.value) {
loadCourseData()
} else if (route.query.courseData || courseStore.courseEditData) {
// 即使没有ID也尝试从路由数据或store加载用于从列表页面直接编辑的情况
loadCourseData()
}
})
// 课程分类选项
const categoryOptions: Ref<{ label: string; value: number }[]> = ref([])
// 讲师选项
const instructorOptions = ref([] as { label: string; value: string }[])
// 班级选项
const classOptions = [
{ label: '前端开发班', value: 'frontend-class' },
{ label: '后端开发班', value: 'backend-class' },
{ label: 'AI算法班', value: 'ai-class' },
{ label: '全栈开发班', value: 'fullstack-class' }
]
// 文件上传相关方法
const triggerFileUpload = () => {
fileInput.value?.click()
}
const handleFileChange = (event: Event) => {
const input = event.target as HTMLInputElement
if (input.files && input.files.length > 0) {
const file = input.files[0]
// 验证文件类型
if (!file.type.startsWith('image/')) {
message.error('请上传图片文件')
return
}
// 验证文件大小限制为2MB
if (file.size > 2 * 1024 * 1024) {
message.error('图片大小不能超过2MB')
return
}
// 创建预览URL
previewUrl.value = URL.createObjectURL(file)
// 设置新的文件对象,这样在提交时就知道需要上传
formData.courseCover = file
}
}
const clearUpload = () => {
// 只有当previewUrl是本地创建的URL时才需要释放
if (previewUrl.value && previewUrl.value.startsWith('blob:')) {
URL.revokeObjectURL(previewUrl.value)
}
// 清除预览和表单数据
previewUrl.value = ''
formData.courseCover = null
// 重置文件输入框
if (fileInput.value) {
fileInput.value.value = ''
}
}
// 取消
const handleCancel = () => {
// 取消时清除缓存数据
courseStore.clearCourseEditData();
router.back()
}
// 格式化时间为 YYYY-MM-DD HH:mm:ss 格式
const formatDateTime = (timestamp: number): string => {
const date = new Date(timestamp)
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
const hours = date.getHours().toString().padStart(2, '0')
const minutes = date.getMinutes().toString().padStart(2, '0')
const seconds = date.getSeconds().toString().padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
// 保存
const handleSubmit = async () => {
try {
// 表单验证
if (!formData.courseName) {
message.error('请输入课程名称')
return
}
if (!formData.courseCategory || formData.courseCategory.length === 0) {
message.error('请选择课程分类')
return
}
if (formData.instructors.length === 0) {
message.error('请选择主讲老师')
return
}
if (!formData.startTime) {
message.error('请选择课程开始时间')
return
}
if (!formData.endTime) {
message.error('请选择课程结束时间')
return
}
if (!formData.courseCover && !previewUrl.value) {
message.error('请上传课程封面')
return
}
// 如果选择了仅部分学员,必须选择班级
if (formData.studentType === 'partial' && formData.selectedClasses.length === 0) {
message.error('请选择参与的班级')
return
}
message.loading('正在保存课程信息...')
let coverUrl = previewUrl.value
// 只有在有新的文件File对象时才需要上传
// 如果 courseCover 是 File 对象,说明用户选择了新文件,需要上传
// 如果 courseCover 是 null 但 previewUrl 有值,说明是编辑模式使用现有图片
if (formData.courseCover && formData.courseCover instanceof File) {
try {
const uploadResponse = await UploadApi.uploadCourseThumbnail(formData.courseCover)
if (uploadResponse.data.success) {
coverUrl = uploadResponse.data.message
} else {
message.error('课程封面上传失败')
return
}
} catch (uploadError) {
message.error('课程封面上传失败,请重试')
return
}
}
// 构建API请求参数
const createCourseData = {
name: formData.courseName,
cover: coverUrl,
description: formData.courseDescription,
type: formData.studentType === 'all' ? 0 : 1,
categoryId: Array.isArray(formData.courseCategory) ? formData.courseCategory.join(',') : formData.courseCategory,
target: formData.studentType === 'all' ? '' : formData.selectedClasses.join(','), // target 字段处理
start_time: formData.startTime ? formatDateTime(formData.startTime) : null,
end_time: formData.endTime ? formatDateTime(formData.endTime) : null,
pauseExit: formData.stopOnLeave ? '1' : '0', // 离开页面停止播放
allowSpeed: formData.videoSpeedControl ? '1' : '0', // 视频倍数播放
showSubtitle: formData.showVideoText ? '1' : '0', // 显示视频文本
// 其他可选字段
school: null,
video: null,
difficulty: null,
subject: null,
outline: null,
prerequisite: null,
reference: null,
arrangement: null,
enroll_count: null,
max_enroll: null,
status: 1, // 默认状态为进行中
question: null
}
if (isEditMode.value) {
// 编辑模式
const editData = {
id: courseId.value,
...createCourseData
}
const response = await TeachCourseApi.editCourse(editData)
if (response.data.code === 200) {
message.success('课程更新成功!')
// 清除缓存数据
courseStore.clearCourseEditData();
// 返回到课程管理页面
router.push('/teacher/course-management')
} else {
message.error(response.message || '更新失败,请重试')
}
} else {
// 创建模式
const response = await TeachCourseApi.createCourse(createCourseData)
if (response.data.code === 200) {
message.success('课程创建成功!')
// 清除缓存数据
courseStore.clearCourseEditData();
// 返回到课程管理页面
router.push('/teacher/course-management')
} else {
message.error(response.message || '创建失败,请重试')
}
}
} catch (error) {
const errorMessage = isEditMode.value ? '更新失败,请重试' : '创建失败,请重试'
message.error(errorMessage)
}
}
const goBack = () => {
// 返回时清除缓存数据
courseStore.clearCourseEditData();
router.back()
}
// 获取课程分类
const getCourseList = () => {
CourseApi.getCategories().then(response => {
categoryOptions.value = response.data.map((category: any) => ({
label: category.name,
value: category.id
}))
// 初始化时设置为空数组,而不是单个值
if (formData.courseCategory.length === 0 && categoryOptions.value.length > 0) {
formData.courseCategory = [];
}
}).catch(error => {
console.error('获取课程列表失败:', error)
})
}
// 获取老师
const getTeacherList = () => {
TeachCourseApi.getTeacherList().then(response => {
instructorOptions.value = response.data.result.map((teacher: any) => ({
label: teacher.realname,
value: teacher.id
}))
}).catch(error => {
console.error('获取老师列表失败:', error)
})
}
onMounted(() => {
getCourseList()
getTeacherList()
})
</script>
<style scoped>
.course-create-container {
min-height: 100vh;
padding-bottom: 80px;
}
.form-container {
background: white;
overflow: hidden;
}
.form-content {
padding: 24px 24px 0 24px;
}
.header-left {
display: flex;
align-items: center;
gap: 16px;
}
.page-title {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #333;
}
.title {
margin: 0;
font-size: 20px;
font-weight: 500;
color: #333;
}
.header-actions {
display: flex;
gap: 12px;
}
.form-row {
display: flex;
gap: 100px;
margin-bottom: 32px;
}
.form-column {
flex: 1;
display: flex;
flex-direction: column;
gap: 20px;
}
.form-item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
gap: 10px;
}
.form-text {
display: flex;
flex-direction: row;
justify-content: flex-end;
gap: 10px;
}
.form-integral {
margin-top: 20px;
margin-bottom: 20px;
}
.form-label {
font-size: 14px;
color: #333;
font-weight: 500;
min-width: 125px;
flex-shrink: 0;
text-align: right;
}
.form-label.required::before {
content: '*';
color: #ff4d4f;
margin-right: 4px;
}
.form-input {
flex: 1;
min-width: 0;
}
/* 最简单的输入框高度统一方法 */
.form-input {
height: 42px;
}
/* 强制设置下拉菜单高度 */
.form-input :deep(.n-base-selection) {
--n-height: 42px !important;
height: 42px !important;
}
.form-input :deep(.n-base-selection-label) {
height: 42px !important;
line-height: 42px !important;
}
/* 强制设置时间选择器高度 */
.form-input :deep(.n-input__input) {
height: 42px !important;
}
.form-input :deep(.n-input__input-el) {
height: 42px !important;
line-height: 42px !important;
}
/* 隐藏下拉菜单的加载状态和清除按钮 */
.form-input :deep(.n-base-loading),
.form-input :deep(.n-base-clear) {
display: none !important;
}
/* 隐藏默认下拉箭头 */
.form-input :deep(.n-base-suffix__arrow) {
display: none !important;
}
/* 为下拉菜单添加自定义箭头 */
.form-input :deep(.n-base-selection) {
position: relative;
}
.form-input :deep(.n-base-selection)::after {
content: '';
position: absolute;
right: 14px;
top: 50%;
transform: translateY(-50%);
width: 13px;
height: 8px;
background-image: url('/images/teacher/箭头-灰.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
pointer-events: none;
z-index: 1;
}
/* 自定义时间选择器图标 */
.form-input :deep(.n-date-picker-icon svg) {
display: none !important;
}
.form-input :deep(.n-date-picker-icon) {
background-image: url('/images/teacher/日历.png') !important;
background-size: contain !important;
background-repeat: no-repeat !important;
background-position: center !important;
width: 14px !important;
height: 14px !important;
}
/* 多选标签样式调整 */
.form-input :deep(.n-tag) {
background-color: #F5F8FB !important;
border: none !important;
color: #666666 !important;
margin-top: 3px;
margin-bottom: 3px;
}
.form-input :deep(.n-tag__content) {
color: #666666 !important;
}
.form-input :deep(.n-tag__border) {
display: none !important;
}
/* 关闭按钮样式调整 */
.form-input :deep(.n-tag__close) {
width: 12px !important;
height: 12px !important;
}
.form-input :deep(.n-tag__close .n-base-icon) {
width: 12px !important;
height: 12px !important;
color: #999 !important;
}
.form-input :deep(.n-tag__close svg) {
width: 12px !important;
height: 12px !important;
}
.form-toggle {
margin-right: 8px;
}
.btn-label {
margin-top: 30px;
}
.toggle-container {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
}
.toggle-label {
font-size: 14px;
color: #666;
}
/* 单选按钮样式 */
.radio-container {
display: flex;
flex-direction: row;
gap: 20px;
flex: 1;
}
.radio-option {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.radio-input {
width: 16px;
height: 16px;
accent-color: #0288D1;
cursor: pointer;
}
.radio-label {
font-size: 14px;
color: #333;
cursor: pointer;
}
/* 课程封面上传区域 */
.cover-upload-container {
flex: 1;
min-width: 0;
}
.upload-area {
width: 160px;
height: 130px;
border: 2px dashed #d9d9d9;
border-radius: 6px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: border-color 0.3s;
background-color: #fafafa;
}
.upload-area:hover {
border-color: #0288D1;
}
.upload-icon {
font-size: 32px;
color: #999;
}
.upload-text {
font-size: 14px;
color: #666;
}
.preview-container {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.preview-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.delete-icon {
position: absolute;
top: 5px;
right: 5px;
width: 20px;
height: 20px;
background-color: rgba(0, 0, 0, 0.5);
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 14px;
}
.delete-icon:hover {
background-color: rgba(0, 0, 0, 0.7);
}
.form-section {
margin-bottom: 32px;
}
.rich-editor-container {
flex: 1;
border: 1px solid #D8D8D8;
overflow: hidden;
}
.setting-container {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
}
.setting-label {
font-size: 14px;
color: #666;
}
.score-input {
width: 80px;
}
.form-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: flex-end;
gap: 12px;
padding: 20px 24px;
background: #fff;
border-top: 1px solid #e6e6e6;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
.cancel-btn {
min-width: 60px;
height: 32px;
border: 1px solid #0288D1;
background: #E2F5FF;
color: #0288D1;
}
.cancel-btn:hover {
border-color: #0277BD;
background: #B3E5FC;
color: #0277BD;
}
.save-btn {
min-width: 60px;
height: 32px;
background: #0288D1;
border-color: #0288D1;
color: white;
}
.save-btn:hover {
background: #0277BD;
border-color: #0277BD;
}
/* 响应式设计 */
@media (max-width: 768px) {
.form-row {
flex-direction: column;
gap: 16px;
}
.form-content {
padding: 16px;
}
}
</style>