<template>
|
<div class="news-form">
|
<el-card>
|
<template #header>
|
<div class="card-header">
|
<span>{{ isEdit ? '编辑新闻' : '新增新闻' }}</span>
|
<el-button @click="goBack">返回</el-button>
|
</div>
|
</template>
|
|
<el-form
|
ref="formRef"
|
:model="form"
|
:rules="rules"
|
label-width="120px"
|
v-loading="loading"
|
@submit.prevent
|
>
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="新闻标题" prop="title">
|
<el-input v-model="form.title" placeholder="请输入新闻标题" maxlength="100" />
|
</el-form-item>
|
</el-col>
|
|
<el-col :span="12">
|
<el-form-item label="作者" prop="author">
|
<el-input v-model="form.author" placeholder="请输入作者姓名" maxlength="50" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-form-item label="摘要" prop="summary">
|
<el-input
|
v-model="form.summary"
|
type="textarea"
|
:rows="3"
|
placeholder="请输入新闻摘要"
|
maxlength="500"
|
/>
|
</el-form-item>
|
|
<el-form-item label="封面图片" prop="coverImage">
|
<div class="cover-upload-container">
|
<el-upload
|
class="cover-uploader"
|
:action="uploadUrl"
|
:headers="uploadHeaders"
|
:show-file-list="false"
|
:on-success="handleCoverUploadSuccess"
|
:before-upload="beforeCoverUpload"
|
>
|
<img v-if="form.coverImage" :src="form.coverImage" class="cover" />
|
<div v-else class="cover-uploader-placeholder">
|
<i class="el-icon-plus cover-uploader-icon"></i>
|
<div class="cover-uploader-text">点击上传图片</div>
|
</div>
|
</el-upload>
|
</div>
|
</el-form-item>
|
|
<el-form-item label="新闻内容" prop="content">
|
<!-- 添加工具栏容器 -->
|
<div class="editor-container">
|
<div ref="toolbarRef" class="editor-toolbar"></div>
|
<div ref="editorRef" class="editor-content"></div>
|
</div>
|
</el-form-item>
|
|
<el-form-item label="状态" prop="state">
|
<el-radio-group v-model="form.state">
|
<el-radio :label="0">草稿</el-radio>
|
<el-radio :label="1">发布</el-radio>
|
<el-radio :label="2">关闭</el-radio>
|
</el-radio-group>
|
</el-form-item>
|
|
<el-form-item>
|
<el-button type="primary" @click="handleSubmit" :loading="submitting">
|
{{ isEdit ? '更新' : '创建' }}
|
</el-button>
|
<el-button @click="goBack">取消</el-button>
|
</el-form-item>
|
</el-form>
|
</el-card>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, onMounted, onBeforeUnmount, shallowRef } from 'vue'
|
import { useRouter, useRoute } from 'vue-router'
|
import { ElMessage } from 'element-plus'
|
import { getNews, saveNews } from '@/api/news'
|
import { getToken } from '@/utils/auth'
|
// 使用wangEditor的核心包而不是Vue包装器
|
import { createEditor, createToolbar } from '@wangeditor/editor'
|
import '@wangeditor/editor/dist/css/style.css'
|
|
const router = useRouter()
|
const route = useRoute()
|
|
// 编辑器实例
|
const editorRef = ref()
|
const toolbarRef = ref()
|
const editorInstance = shallowRef()
|
const toolbarInstance = shallowRef()
|
|
const loading = ref(false)
|
const submitting = ref(false)
|
const formRef = ref()
|
|
// 上传配置 - 修正URL路径,添加/api前缀
|
const uploadUrl = `${import.meta.env.VITE_API_BASE_URL || ''}/api/upload/image`
|
const uploadHeaders = {
|
Authorization: `Bearer ${getToken()}`
|
}
|
|
// 表单数据
|
const form = ref({
|
id: null,
|
title: '',
|
content: '',
|
summary: '',
|
coverImage: '',
|
author: '',
|
state: 1
|
})
|
|
// 表单验证规则
|
const rules = {
|
title: [
|
{ required: true, message: '请输入新闻标题', trigger: 'blur' },
|
{ max: 100, message: '新闻标题不能超过100个字符', trigger: 'blur' }
|
],
|
author: [
|
{ required: true, message: '请输入作者姓名', trigger: 'blur' },
|
{ max: 50, message: '作者姓名不能超过50个字符', trigger: 'blur' }
|
],
|
content: [
|
{ required: true, message: '请输入新闻内容', trigger: 'blur' }
|
]
|
}
|
|
// 计算属性
|
const isEdit = ref(!!route.params.id)
|
|
// 初始化编辑器
|
const initEditor = () => {
|
if (!editorRef.value || !toolbarRef.value) return
|
|
// 创建编辑器
|
const editor = createEditor({
|
selector: editorRef.value,
|
html: form.value.content,
|
config: {
|
placeholder: '请输入新闻内容...',
|
MENU_CONF: {
|
'uploadImage': {
|
server: `${import.meta.env.VITE_API_BASE_URL || ''}/api/upload/image`,
|
fieldName: 'file',
|
headers: {
|
Authorization: `Bearer ${getToken()}`
|
},
|
maxFileSize: 2 * 1024 * 1024, // 2MB
|
base64LimitSize: 5 * 1024, // 5KB以下插入base64
|
// wangEditor期望的响应格式处理
|
onSuccess(file, res) {
|
console.log('图片上传成功', file, res)
|
},
|
onError(file, res) {
|
console.error('图片上传失败', file, res)
|
ElMessage.error('图片上传失败: ' + (res?.message || res?.data?.message || '未知错误'))
|
}
|
}
|
},
|
onChange(editor) {
|
// 当编辑器内容改变时,更新表单数据
|
form.value.content = editor.getHtml()
|
}
|
}
|
})
|
|
// 创建工具栏
|
const toolbar = createToolbar({
|
editor: editor,
|
selector: toolbarRef.value,
|
config: {
|
// 工具栏配置
|
}
|
})
|
|
editorInstance.value = editor
|
toolbarInstance.value = toolbar
|
}
|
|
// 封面图片上传前检查
|
const beforeCoverUpload = (file) => {
|
const isJPG = file.type === 'image/jpeg' || file.type === 'image/png'
|
const isLt2M = file.size / 1024 / 1024 < 2
|
|
if (!isJPG) {
|
ElMessage.error('封面图片只能是 JPG 或 PNG 格式!')
|
}
|
if (!isLt2M) {
|
ElMessage.error('封面图片大小不能超过 2MB!')
|
}
|
return isJPG && isLt2M
|
}
|
|
// 封面图片上传成功处理
|
const handleCoverUploadSuccess = (response) => {
|
// 处理后端返回的新格式
|
if (response && response.errno === 0 && response.data) {
|
form.value.coverImage = response.data.url
|
ElMessage.success('封面图片上传成功')
|
} else if (response && response.success) {
|
// 兼容旧格式
|
form.value.coverImage = response.data?.fullUrl || response.data?.url || response.url
|
ElMessage.success('封面图片上传成功')
|
} else {
|
ElMessage.error('封面图片上传失败: ' + (response?.message || '未知错误'))
|
}
|
}
|
|
// 加载新闻数据(编辑模式)
|
const loadNews = async () => {
|
if (!isEdit.value) return
|
|
try {
|
loading.value = true
|
const news = await getNews(route.params.id)
|
|
if (news) {
|
form.value = {
|
id: news.id,
|
title: news.title,
|
content: news.content,
|
summary: news.summary,
|
coverImage: news.coverImage,
|
author: news.author,
|
state: news.state
|
}
|
|
// 更新编辑器内容
|
if (editorInstance.value) {
|
editorInstance.value.setHtml(news.content)
|
}
|
}
|
} catch (error) {
|
console.error('加载新闻数据失败:', error)
|
ElMessage.error('加载新闻数据失败')
|
} finally {
|
loading.value = false
|
}
|
}
|
|
// 提交表单
|
const handleSubmit = async () => {
|
if (submitting.value) return
|
|
try {
|
await formRef.value.validate()
|
|
submitting.value = true
|
|
// 准备保存数据
|
const saveData = {
|
title: form.value.title,
|
content: form.value.content,
|
summary: form.value.summary,
|
coverImage: form.value.coverImage,
|
author: form.value.author,
|
state: form.value.state
|
}
|
|
// 如果是编辑模式,添加id字段
|
if (isEdit.value && form.value.id) {
|
saveData.id = form.value.id
|
}
|
|
const result = await saveNews(saveData)
|
|
ElMessage.success(isEdit.value ? '更新成功' : '创建成功')
|
|
// 保存成功后返回列表页面
|
goBack()
|
} catch (error) {
|
console.error('保存新闻失败:', error)
|
if (error.message) {
|
ElMessage.error('保存失败: ' + error.message)
|
} else {
|
ElMessage.error('保存失败: 未知错误')
|
}
|
} finally {
|
submitting.value = false
|
}
|
}
|
|
// 返回
|
const goBack = () => {
|
router.push('/news')
|
}
|
|
// 组件销毁前清理编辑器
|
onBeforeUnmount(() => {
|
if (editorInstance.value) {
|
editorInstance.value.destroy()
|
}
|
})
|
|
// 生命周期
|
onMounted(async () => {
|
await loadNews()
|
// 确保DOM已渲染后再初始化编辑器
|
setTimeout(() => {
|
initEditor()
|
}, 100)
|
})
|
</script>
|
|
<style scoped>
|
.news-form {
|
padding: 20px;
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
/* 封面图片上传容器 */
|
.cover-upload-container {
|
display: inline-block;
|
}
|
|
.cover-uploader .el-upload {
|
border: 2px dashed #d9d9d9;
|
border-radius: 6px;
|
cursor: pointer;
|
position: relative;
|
overflow: hidden;
|
transition: .2s;
|
width: 178px;
|
height: 178px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
background-color: #fafafa;
|
}
|
|
.cover-uploader .el-upload:hover {
|
border-color: #409eff;
|
background-color: #f5f9ff;
|
}
|
|
.cover-uploader-placeholder {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
width: 100%;
|
height: 100%;
|
}
|
|
.cover-uploader-icon {
|
font-size: 28px;
|
color: #8c939d;
|
margin-bottom: 8px;
|
}
|
|
.cover-uploader-text {
|
font-size: 12px;
|
color: #666;
|
text-align: center;
|
}
|
|
.cover {
|
width: 178px;
|
height: 178px;
|
display: block;
|
object-fit: cover;
|
}
|
|
/* 编辑器容器样式 */
|
.editor-container {
|
border: 1px solid #dcdfe6;
|
border-radius: 4px;
|
}
|
|
.editor-toolbar {
|
border-bottom: 1px solid #dcdfe6;
|
background-color: #f5f7fa;
|
}
|
|
.editor-content {
|
height: 400px;
|
overflow-y: auto;
|
background-color: #fff;
|
}
|
|
/* 响应式设计 */
|
@media (max-width: 768px) {
|
.editor-content {
|
height: 300px;
|
}
|
}
|
</style>
|