| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- <template>
- <div class="course-form-container">
- <h2 class="page-title">{{ isEdit ? '编辑课程' : '创建课程' }}</h2>
-
- <el-card shadow="never">
- <el-form
- ref="formRef"
- :model="form"
- :rules="rules"
- label-width="120px"
- scroll-to-error
- status-icon
- >
- <el-form-item label="课程名称" prop="name">
- <el-input v-model="form.name" placeholder="请输入课程名称" />
- </el-form-item>
-
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="课程分类" prop="categoryName">
- <el-select v-model="form.categoryName" placeholder="请选择课程分类" style="width: 100%">
- <el-option label="情感沟通" value="情感沟通" />
- <el-option label="恋爱技巧" value="恋爱技巧" />
- <el-option label="婚姻经营" value="婚姻经营" />
- <el-option label="情感修复" value="情感修复" />
- <el-option label="亲密关系" value="亲密关系" />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="讲师" prop="instructor">
- <el-input v-model="form.instructor" placeholder="请输入讲师姓名" />
- </el-form-item>
- </el-col>
- </el-row>
-
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="课程时长" prop="duration">
- <el-input-number v-model="form.duration" :min="0" placeholder="分钟" style="width: 100%" />
- <span style="margin-left: 10px; color: #999;">分钟</span>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="课程价格" prop="price">
- <el-input-number v-model="form.price" :min="0" :precision="2" style="width: 100%" />
- <span style="margin-left: 10px; color: #999;">元(0表示免费)</span>
- </el-form-item>
- </el-col>
- </el-row>
-
- <el-row :gutter="20">
- <el-col :span="12">
- <el-form-item label="课程评分" prop="rating">
- <el-rate v-model="form.rating" :max="5" allow-half />
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="是否推荐" prop="isRecommended">
- <el-switch v-model="form.isRecommended" :active-value="1" :inactive-value="0" />
- </el-form-item>
- </el-col>
- </el-row>
-
- <el-form-item label="课程封面" prop="coverImage">
- <div class="cover-upload-wrapper">
- <el-upload
- class="cover-uploader"
- :http-request="handleUpload"
- :show-file-list="false"
- :before-upload="beforeUpload"
- accept="image/*"
- >
- <img v-if="form.coverImage" :src="form.coverImage" class="cover-preview" />
- <el-icon v-else class="cover-uploader-icon"><Plus /></el-icon>
- </el-upload>
- <div class="cover-tips">
- <p>建议尺寸:750x420 像素,支持 JPG、PNG 格式,文件大小不超过 2MB</p>
- <el-button v-if="form.coverImage" size="small" type="danger" text @click="removeCover">删除封面</el-button>
- </div>
- </div>
- </el-form-item>
-
- <el-form-item label="课程描述" prop="description">
- <el-input v-model="form.description" type="textarea" :rows="4" placeholder="请输入课程简介" />
- </el-form-item>
-
- <el-form-item label="课程内容" prop="content">
- <el-input v-model="form.content" type="textarea" :rows="8" placeholder="请输入课程详细内容" />
- </el-form-item>
-
- <el-form-item label="状态" prop="status">
- <el-radio-group v-model="form.status">
- <el-radio :label="1">上架</el-radio>
- <el-radio :label="0">下架</el-radio>
- </el-radio-group>
- </el-form-item>
-
- <el-form-item>
- <el-button type="primary" :loading="submitLoading" @click="handleSubmit">
- {{ isEdit ? '保存修改' : '创建课程' }}
- </el-button>
- <el-button @click="$router.back()">取消</el-button>
- </el-form-item>
- </el-form>
- </el-card>
- </div>
- </template>
- <script setup>
- import { ref, reactive, onMounted, computed } from 'vue'
- import { useRoute, useRouter } from 'vue-router'
- import { ElMessage } from 'element-plus'
- import { Plus } from '@element-plus/icons-vue'
- import request from '@/utils/request'
- import { API_ENDPOINTS } from '@/config/api'
- const route = useRoute()
- const router = useRouter()
- const isEdit = ref(false)
- const submitLoading = ref(false)
- const formRef = ref(null)
- const form = reactive({
- id: null,
- name: '',
- categoryName: '',
- instructor: '',
- duration: 0,
- price: 0,
- rating: 4.5,
- isRecommended: 0,
- coverImage: '',
- description: '',
- content: '',
- status: 1
- })
- const rules = {
- name: [{ required: true, message: '请输入课程名称', trigger: 'blur' }],
- categoryName: [{ required: true, message: '请选择课程分类', trigger: 'change' }],
- instructor: [{ required: true, message: '请输入讲师姓名', trigger: 'blur' }],
- duration: [{ required: true, message: '请输入课程时长', trigger: 'blur' }],
- description: [{ required: true, message: '请输入课程描述', trigger: 'blur' }]
- }
- // 上传前检查
- const beforeUpload = (file) => {
- const isImage = file.type.startsWith('image/')
- const isLt2M = file.size / 1024 / 1024 < 2
- if (!isImage) {
- ElMessage.error('只能上传图片文件!')
- return false
- }
- if (!isLt2M) {
- ElMessage.error('图片大小不能超过 2MB!')
- return false
- }
- return true
- }
- // 上传图片
- const handleUpload = async (options) => {
- const formData = new FormData()
- formData.append('file', options.file)
-
- try {
- const response = await request.post(API_ENDPOINTS.UPLOAD_IMAGE, formData, {
- headers: { 'Content-Type': 'multipart/form-data' }
- })
-
- if (response.code === 200) {
- // 处理不同的返回格式
- let imageUrl = ''
- if (typeof response.data === 'string') {
- imageUrl = response.data
- } else if (response.data && response.data.url) {
- imageUrl = response.data.url
- } else if (response.data && response.data.path) {
- imageUrl = response.data.path
- } else {
- imageUrl = String(response.data || '')
- }
-
- form.coverImage = imageUrl
- ElMessage.success('封面上传成功')
- } else {
- ElMessage.error(response.message || '封面上传失败')
- }
- } catch (error) {
- console.error('上传异常:', error)
- ElMessage.error('封面上传失败,请重试')
- }
- }
- // 删除封面
- const removeCover = () => {
- form.coverImage = ''
- ElMessage.success('封面已删除')
- }
- const loadDetail = async (id) => {
- try {
- const response = await request.get(`${API_ENDPOINTS.COURSE_DETAIL}/${id}`)
- if (response.code === 200) {
- const data = response.data
- // 映射API返回的下划线字段到表单的驼峰字段
- Object.assign(form, {
- id: data.id,
- name: data.name || '',
- categoryName: data.categoryName || data.category_name || '',
- instructor: data.instructor || data.teacher_name || data.teacherName || '',
- duration: data.duration || 0,
- price: data.price || 0,
- rating: data.rating || 4.5,
- isRecommended: data.isRecommended !== undefined ? data.isRecommended : (data.is_recommended !== undefined ? data.is_recommended : 0),
- coverImage: data.coverImage || data.cover_image || data.cover || '',
- description: data.description || '',
- content: data.content || '',
- status: data.status !== undefined ? data.status : 1
- })
- }
- } catch (error) {
- console.error('加载详情失败:', error)
- }
- }
- const handleSubmit = async () => {
- if (!formRef.value) return
-
- try {
- // 表单验证
- await formRef.value.validate()
-
- submitLoading.value = true
- const url = isEdit.value ? `${API_ENDPOINTS.COURSE_UPDATE}/${form.id}` : API_ENDPOINTS.COURSE_CREATE
- const method = isEdit.value ? 'put' : 'post'
-
- // 将前端的驼峰字段转换为后端期望的 snake_case 格式
- const submitData = {
- id: form.id,
- name: form.name,
- category_name: form.categoryName, // 转换为下划线格式
- instructor: form.instructor,
- teacher_name: form.instructor, // 兼容后端字段
- duration: form.duration,
- price: form.price,
- rating: form.rating,
- is_recommended: form.isRecommended,
- cover_image: form.coverImage,
- description: form.description,
- content: form.content,
- status: form.status
- }
-
- const response = await request[method](url, submitData)
- if (response.code === 200) {
- ElMessage.success(isEdit.value ? '更新成功' : '创建成功')
- router.push('/course/list')
- } else {
- ElMessage.error(response.message || (isEdit.value ? '更新失败' : '创建失败'))
- }
- } catch (error) {
- // 判断是表单验证错误还是请求错误
- if (error && typeof error === 'object' && !error.response) {
- // 表单验证错误
- console.log('表单验证失败:', error)
- const firstError = Object.values(error)[0]
- if (firstError && firstError[0] && firstError[0].message) {
- ElMessage.warning(firstError[0].message)
- } else {
- ElMessage.warning('请检查表单必填项')
- }
- } else {
- // 请求错误
- console.error('提交失败:', error)
- ElMessage.error(error.response?.data?.message || (isEdit.value ? '更新失败' : '创建失败'))
- }
- } finally {
- submitLoading.value = false
- }
- }
- onMounted(() => {
- if (route.params.id) {
- isEdit.value = true
- loadDetail(route.params.id)
- }
- })
- </script>
- <style scoped>
- .course-form-container { padding: 0; }
- .page-title { font-size: 24px; font-weight: bold; color: #333; margin: 0 0 20px 0; }
- .cover-upload-wrapper {
- display: flex;
- align-items: flex-start;
- gap: 20px;
- }
- .cover-uploader {
- border: 1px dashed #d9d9d9;
- border-radius: 6px;
- cursor: pointer;
- position: relative;
- overflow: hidden;
- transition: all 0.3s;
- }
- .cover-uploader:hover {
- border-color: #409eff;
- }
- .cover-uploader-icon {
- font-size: 28px;
- color: #8c939d;
- width: 178px;
- height: 100px;
- display: flex;
- align-items: center;
- justify-content: center;
- background-color: #fbfdff;
- }
- .cover-preview {
- width: 178px;
- height: 100px;
- display: block;
- object-fit: cover;
- }
- .cover-tips {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 10px;
- }
- .cover-tips p {
- margin: 0;
- color: #909399;
- font-size: 12px;
- line-height: 1.5;
- }
- </style>
|