项目地址:https://gitee.com/liangPromise/vue-app-toutiaoAdmin/tree/article/
创建组件并配置路由
1、创建 src/views/publish/index.vue
组件
2、配置页面路由
{
path: '/home',
component: () => import('@/views/home/Home.vue'),
children: [
{
path: '',
component: () => import('@/views/home/indexHoem.vue')
},
{
name: 'article',
path: 'article',
component: () => import('@/views/article')
},
{
// 发布文章路由
name: 'publish',
path: 'publish',
component: () => import('@/views/publish')
}
]
}
页面布局和绑定数据
使用element-ui布局 另外还使用到了富文本编辑器
富文本编辑器的使用具体看我另一篇文章关于项目中的富文本编辑器的使用方法
根据后端接口的要求处理绑定数据绑定
<template>
<el-card class="box-card">
<div slot="header" class="clearfix">
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>发布文章</el-breadcrumb-item>
</el-breadcrumb>
</div>
<el-form :model="form" :rules="rules" ref="ruleForm" label-width="100px">
<el-form-item label="标题:" prop="title">
<el-row>
<el-col :span="10">
<el-input v-model="form.title"></el-input>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="内容:" prop="content">
<el-tiptap :extensions="extensions" v-model="form.content"></el-tiptap>
</el-form-item>
<el-form-item label="封面:" prop="cover.type">
<el-radio-group v-model="form.cover.type">
<el-radio :label="1">单图</el-radio>
<el-radio :label="3">三图</el-radio>
<el-radio :label="0">无图</el-radio>
<el-radio :label="-1">自动</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="频道:" prop="channel_id">
<el-select v-model="form.channel_id" placeholder="请选择频道">
<el-option
v-for="item in channels"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<!-- 条件渲染 根据isEdit显示发布或者修改 -->
<el-form-item v-if="!isEdit">
<el-button type="primary" @click="onSubmit(false)">发布</el-button>
<el-button @click="onSubmit(true)">存入草稿</el-button>
</el-form-item>
<el-form-item v-else>
<el-button type="primary" @click="editArticle">修改</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script>
import {
ElementTiptap,
// necessary extensions
Doc,
Text,
Paragraph,
Heading,
Bold,
Underline,
Italic,
Strike,
ListItem,
BulletList,
OrderedList,
Image,
CodeBlock,
Blockquote,
TextAlign,
Indent,
Table,
TableHeader,
TableCell,
TableRow,
TextColor,
HorizontalRule
// LineHeigh
} from 'element-tiptap'
import {
getChannels,
addArticle,
inquireArticle,
editArticle
} from '@/api/article.js'
// 上传图片接口
import { uploadingImg } from '@/api/images.js'
export default {
name: 'Publish',
components: {
'el-tiptap': ElementTiptap
},
data () {
return {
form: {
title: '',
content: '',
cover: {
type: 1,
images: []
},
channel_id: 2
},
// 频道信息
channels: [],
// 是否是修改文章 有id就是修改 没有就不是
isEdit: this.$route.query.id,
// 验证规则
rules: {
title: [
{ required: true, message: '请输入文章标题', trigger: 'blur' },
{ min: 5, max: 30, message: '长度在 5 到 30 个字符', trigger: 'blur' }
],
content: [{ required: true, message: '请输入内容', trigger: 'blur' }],
'cover.type': [
{ required: true, message: '请选择图片类型', trigger: 'blur' }
],
channel_id: [{ required: true, message: '请选择频道', trigger: 'blur' }]
},
extensions: [
new Doc(),
new Text(),
new Paragraph(),
new Heading({ level: 5 }),
new Bold({ bubble: true }), // render command-button in bubble menu.
new Underline({ bubble: true, menubar: false }), // render command-button in bubble menu but not in menubar.
new Italic(),
new Strike(),
new ListItem(),
new BulletList(),
new OrderedList(),
new CodeBlock(),
new Image({
// 默认会把图片生成 base64
async uploadRequest (file) {
const fd = new FormData()
fd.append('image', file)
const data = await uploadingImg(fd).catch(err => err)
console.log(data)
return data.data.data.url
}
}),
new Blockquote(),
new TextAlign(),
new Indent(),
new Table(),
new TableHeader(),
new TableCell(),
new TableRow(),
new HorizontalRule(),
new TextColor()
]
}
},
methods: {
// 发布按钮
onSubmit (draft) {
this.$refs.ruleForm.validate(async valid => {
if (!valid) return false
console.log(1231231313)
const data = await addArticle(this.form, draft).catch(err => err)
if (data.status !== 201) return this.$message.error('文章发布失败')
this.$message.success('文章发布成功')
this.$router.push({ path: '/home/article' })
})
},
// 修改按钮
editArticle () {
this.$refs.ruleForm.validate(async valid => {
if (!valid) return false
const data = await editArticle(this.form, this.isEdit).catch(err => err)
if (data.status !== 201) return this.$message.error('文章修改失败')
this.$message.success('文章修改成功')
this.$router.push({ path: '/home/article' })
})
},
// 获取频道信息
async getChannelsFn () {
this.loading = true
const data = await getChannels().catch(err => err)
if (data.status !== 200) return this.$message.error('频道信息获取失败')
this.channels = data.data.data.channels
this.loading = false
},
// 查询文章
async inquireArticleFn () {
const data = await inquireArticle(this.isEdit).catch(err => err)
if (data.status !== 200) return this.$message.error('文章信息获取失败')
this.form = data.data.data
}
},
created () {
// 获取频道信息
this.getChannelsFn()
// 如果是修改页面就要获取文章数据
if (this.isEdit) {
this.inquireArticleFn()
}
}
}
</script>
<style></style>
处理文章频道数据
一、展示频道列表
1、请求获取频道列表数据
2、绑定到模板中展示
二、将频道绑定到表单元素选择器
1、在 article 中添加频道数据字段
2、将数据绑定到频道选择器中
<el-form-item label="频道:" prop="channel_id">
<el-select v-model="form.channel_id" placeholder="请选择频道">
<el-option
v-for="item in channels"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
--------------------------------------------------------------------------------
// 获取频道信息
async getChannelsFn () {
const data = await getChannels().catch(err => err)
if (data.status !== 200) return this.$message.error('频道信息获取失败')
this.channels = data.data.data.channels
},
发布文章
一、封装数据接口
// 发布文章
const addArticle = (data, params) => {
console.log(data, params)
return request({
method: 'POST',
url: '/mp/v1_0/articles',
params: {
draft: params // 是否存为草稿(true 为草稿)
},
data
})
}
二、在发布文章组件中请求调用
1、加载请求方法
2、给发布和存入草稿绑定事件处理函数
3、处理函数如下
<!-- 条件渲染 根据isEdit显示发布或者修改 -->
<el-form-item v-if="!isEdit">
<el-button type="primary" @click="onSubmit(false)">发布</el-button>
<el-button @click="onSubmit(true)">存入草稿</el-button>
</el-form-item>
<el-form-item v-else>
<el-button type="primary" @click="editArticle">修改</el-button>
</el-form-item>
</el-form>
-----------------------------------------------------------
// 引入接口
import {
getChannels,
addArticle,
inquireArticle,
editArticle
} from '@/api/article.js'
------------------------------------------------------------
// 发布按钮
onSubmit (draft) {
this.$refs.ruleForm.validate(async valid => {
if (!valid) return false
console.log(1231231313)
const data = await addArticle(this.form, draft).catch(err => err)
if (data.status !== 201) return this.$message.error('文章发布失败')
this.$message.success('文章发布成功')
this.$router.push({ path: '/home/article' })
})
},
编辑文章
展示编辑文章内容
1、封装获取文章的数据接口
2、点击编辑,导航到发布文章页面
<el-button
type="danger"
icon="el-icon-delete"
@click="removeArticleFn(scope.row.id)"
circle
></el-button>
-----------------------------------------------------------
// 修改文章
editArticleFn (id) {
this.$router.push(`publish?id=${id}`)
}
3、在发布文章页面组件中,处理加载编辑文章内容
判断是发布还是修改
在发布文章模块中 因为我们修改和发布使用的是一个模块,所以我们要判断一下是否是修改如果是修改我么就显示修改的内容,如果不是就显示发布
在修改按钮中我们使用路由跳转的时候传第一个路由参数
因此我们可以根据这个路由参数来判断 如果有路由参数就是修改 没有就是发布
// 是否是修改文章 有id就是修改 没有就不是
isEdit: this.$route.query.id,
在结构中通过v-if来判断一下显示对应的按钮
<!-- 条件渲染 根据isEdit显示发布或者修改 -->
<el-form-item v-if="!isEdit">
<el-button type="primary" @click="onSubmit(false)">发布</el-button>
<el-button @click="onSubmit(true)">存入草稿</el-button>
</el-form-item>
<el-form-item v-else>
<el-button type="primary" @click="editArticle">修改</el-button>
</el-form-item>
在组件生命周期中我们通过created生命周期钩子在Dom元素创建之前就判断一下路由中有没有路由参数
created () {
// 获取频道信息
this.getChannelsFn()
// 如果是修改页面就要获取文章数据
if (this.isEdit) {
this.inquireArticleFn()
}
}
}
如果是修改我们通过这个文章的id获取到这篇文章的内容 把数据渲染到页面
// 查询文章
async inquireArticleFn () {
const data = await inquireArticle(this.isEdit).catch(err => err)
if (data.status !== 200) return this.$message.error('文章信息获取失败')
this.form = data.data.data
}
提交更新
1、封装更新文章的数据接口
2、加载请求方法
3、修改发布文章的处理逻辑
// 发布按钮
onSubmit (draft) {
this.$refs.ruleForm.validate(async valid => {
if (!valid) return false
console.log(1231231313)
const data = await addArticle(this.form, draft).catch(err => err)
if (data.status !== 201) return this.$message.error('文章发布失败')
this.$message.success('文章发布成功')
this.$router.push({ path: '/home/article' })
})
},
// 修改按钮
editArticle () {
this.$refs.ruleForm.validate(async valid => {
if (!valid) return false
const data = await editArticle(this.form, this.isEdit).catch(err => err)
if (data.status !== 201) return this.$message.error('文章修改失败')
this.$message.success('文章修改成功')
this.$router.push({ path: '/home/article' })
})
},
表单验证处理
验证规则配置对象:
// 验证规则
rules: {
title: [
{ required: true, message: '请输入文章标题', trigger: 'blur' },
{ min: 5, max: 30, message: '长度在 5 到 30 个字符', trigger: 'blur' }
],
content: [{ required: true, message: '请输入内容', trigger: 'blur' }],
'cover.type': [
{ required: true, message: '请选择图片类型', trigger: 'blur' }
],
channel_id: [{ required: true, message: '请选择频道', trigger: 'blur' }]
},
处理富文本编辑器中的图片
创建 src/api/image.js
封装数据接口
// 素材接口
import request from '@/utils/request.js'
const uploadingImg = data => {
// 一般文件上传的接口都要求把请求头中的Content-Type设置为multipart/form-data 但是我们使用axios上传文件的话不需要手动设置,你只要给data一个FormData对象即可。 new FormData()
return request.post('/mp/v1_0/user/images', data)
}
export { uploadingImg }
自定义图片上传到服务器
new Image({
// 默认会把图片生成 base64
async uploadRequest (file) {
new FormData()
const fd = new FormData()
fd.append('image', file)
const data = await uploadingImg(fd).catch(err => err)
return data.data.data.url // 将图片的地址返回
}
}),
// 一般文件上传的接口都要求把请求头中的Content-Type设置为multipart/form-data 但是我们使用axios上传文件的话不需要手动设置,你只要给data一个FormData对象即可。 new FormData()