<template>
<div class="article-form">
<el-form :model="form" label-width="80px">
<el-form-item label="文章标题" prop="title" required>
<el-input v-model="form.title"></el-input>
</el-form-item>
<el-form-item label="文章内容" prop="content" required>
<el-input type="textarea" v-model="form.content" rows="5"></el-input>
</el-form-item>
<el-form-item label="文章图片">
<el-upload
class="avatar-uploader"
action="/api/upload"
:show-file-list="false"
:auto-upload="false"
:on-change="handleChange"
:on-success="handleSuccess"
ref="upload"
>
<img v-if="form.imageUrl" :src="form.imageUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
<el-button size="small" @click="submitUpload">添加图片</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm">保存文章</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
data() {
return {
form: {
title: '',
content: '',
imageUrl: '',
imageFile: null
}
}
},
methods: {
handleChange(file) {
const isImage = file.raw.type.includes('image/')
const isLt2M = file.size / 1024 / 1024 < 2
if (!isImage) {
this.$message.error('只能上传图片文件!')
return false
}
if (!isLt2M) {
this.$message.error('图片大小不能超过2MB!')
return false
}
const reader = new FileReader()
reader.onload = (e) => {
this.form.imageUrl = e.target.result
}
reader.readAsDataURL(file.raw)
this.form.imageFile = file.raw
return false
},
handleSuccess(response) {
this.form.imageUrl = response.data.url
this.$message.success('图片上传成功!')
// 图片上传成功后自动提交表单
this.submitFormData()
},
submitUpload() {
if (!this.form.imageFile) {
this.$message.warning('请先选择图片!')
return
}
this.$refs.upload.submit()
},
submitForm() {
if (!this.form.title || !this.form.content) {
this.$message.error('标题和内容为必填项!')
return
}
if (this.form.imageFile && !this.form.imageUrl.startsWith('http')) {
this.submitUpload()
} else {
this.submitFormData()
}
},
submitFormData() {
this.$axios.post('/api/article', {
title: this.form.title,
content: this.form.content,
image: this.form.imageUrl.startsWith('http') ? this.form.imageUrl : null
}).then(() => {
this.$message.success('文章保存成功!')
}).catch(error => {
this.$message.error('保存失败: ' + error.message)
})
}
}
}
</script>
<style>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>
代码
生成
<template>
<div class="article-form">
<el-form :model="form" :rules="rules" ref="formRef">
<!-- 标题字段 -->
<el-form-item label="文章标题" prop="title">
<el-input v-model="form.title"></el-input>
</el-form-item>
<!-- 内容字段 -->
<el-form-item label="文章内容" prop="content">
<el-input type="textarea" v-model="form.content" rows="5"></el-input>
</el-form-item>
<!-- 图片上传 -->
<el-form-item label="封面图片">
<el-upload
class="avatar-uploader"
action="/api/upload"
:show-file-list="false"
:auto-upload="false"
:on-change="handleChange"
:on-success="handleSuccess"
ref="upload"
>
<img v-if="form.imageUrl" :src="form.imageUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm">保存文章</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
data() {
return {
form: {
title: '',
content: '',
imageUrl: '',
imageFile: null
},
rules: {
title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
content: [{ required: true, message: '请输入内容', trigger: 'blur' }]
}
}
},
methods: {
handleChange(file) {
const isImage = file.raw.type.includes('image/');
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isImage) {
this.$message.error('只能上传图片文件!');
return false;
}
if (!isLt2M) {
this.$message.error('图片大小不能超过2MB!');
return false;
}
// 本地预览
const reader = new FileReader();
reader.onload = (e) => {
this.form.imageUrl = e.target.result;
};
reader.readAsDataURL(file.raw);
this.form.imageFile = file.raw;
return false;
},
handleSuccess(response) {
if (response.code === 200) {
this.form.imageUrl = response.data.url;
this.submitArticleData();
} else {
this.$message.error('图片上传失败');
}
},
submitForm() {
this.$refs.formRef.validate(valid => {
if (!valid) return;
if (this.form.imageFile) {
// 有图片需要先上传
this.$refs.upload.submit();
} else {
// 无图片直接提交
this.submitArticleData();
}
});
},
submitArticleData() {
this.$axios.post('/api/article', {
title: this.form.title,
content: this.form.content,
image: this.form.imageUrl.startsWith('http') ? this.form.imageUrl : null
}).then(() => {
this.$message.success('保存成功');
}).catch(error => {
this.$message.error('保存失败: ' + error.message);
});
}
}
}
</script>
<style>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>
暂存
<template>
<div class="editor-container">
<!-- 工具栏 -->
<div style="border: 1px solid #ccc">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editor"
:defaultConfig="toolbarConfig"
:mode="mode"
/>
</div>
<div style="height: 500px; overflow-y: hidden">
<Editor
v-model="html"
:defaultConfig="editorConfig"
:mode="mode"
@onCreated="onCreated"
@onChange="onChange"
/>
</div>
</div>
</template>
<script>
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import '@wangeditor/editor/dist/css/style.css'
import axios from 'axios'
export default {
name: 'WangEditor',
components: { Editor, Toolbar },
props: {
value: {
type: String,
default: ''
}
},
data() {
return {
editor: null,
html: this.value,
mode: 'default',
pendingImages: [], // 暂存待上传的图片
toolbarConfig: {
excludeKeys: [
'uploadVideo',
]
},
editorConfig: {
placeholder: '请输入内容...',
MENU_CONF: {
uploadImage: {
// 自定义图片上传逻辑
customUpload: async (file, insertFn) => {
// 将图片暂存到pendingImages数组
const imageId = `pending_${Date.now()}`
const reader = new FileReader()
reader.onload = (e) => {
// 使用base64格式显示图片预览
const base64Url = e.target.result
insertFn(base64Url, file.name, base64Url)
// 保存原始文件对象和相关信息
this.pendingImages.push({
id: imageId,
file,
tempUrl: base64Url
})
}
reader.readAsDataURL(file)
}
}
}
}
}
},
methods: {
onCreated(editor) {
this.editor = Object.seal(editor)
},
onChange(editor) {
console.log('onChange', editor.getHtml())
},
// 保存并上传所有图片
async saveAllImages() {
if (this.pendingImages.length === 0) {
this.$emit('save', this.html)
return
}
try {
const formData = new FormData()
// 准备批量上传的数据
this.pendingImages.forEach((img, index) => {
formData.append(`files[${index}]`, img.file)
})
// 批量上传图片
const response = await axios.post('/api/batch-upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
// 替换编辑器中的临时URL
const htmlContent = this.html
let newHtml = htmlContent
response.data.urls.forEach((urlObj, index) => {
const pendingImg = this.pendingImages[index]
if (pendingImg) {
newHtml = newHtml.replace(
new RegExp(pendingImg.tempUrl, 'g'),
urlObj.url
)
}
})
// 更新编辑器内容
this.html = newHtml
this.pendingImages = []
// 触发保存事件
this.$emit('save', newHtml)
} catch (error) {
console.error('批量上传图片失败:', error)
alert('图片上传失败,请重试')
}
},
getHtml() {
return this.html
},
getText() {
return this.editor.getText()
},
destroyEditor() {
this.editor.destroy()
this.editor = null
}
},
beforeDestroy() {
if (this.editor) {
this.editor.destroy()
}
}
}
</script>
<template>
<div>
<wang-editor v-model="content" @save="handleSave" />
</div>
</template>
<script>
import WangEditor from './WangEditor.vue'
export default {
components: { WangEditor },
data() {
return {
content: '<p>初始内容</p>'
}
},
methods: {
handleSave(htmlContent) {
console.log('保存的内容:', htmlContent)
// 这里可以执行保存到服务器的操作
}
}
}
</script>