|
@@ -0,0 +1,189 @@
|
|
|
+<template>
|
|
|
+ <div class="publish-container">
|
|
|
+ <el-card>
|
|
|
+ <div slot="header" class="clearfix">
|
|
|
+ <el-breadcrumb separator-class="el-icon-arrow-right">
|
|
|
+ <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
|
|
|
+ <el-breadcrumb-item>编辑文章</el-breadcrumb-item>
|
|
|
+ </el-breadcrumb>
|
|
|
+ </div>
|
|
|
+ <el-form ref="form" :rules="rules" :model="article" label-width="80px">
|
|
|
+ <el-form-item label="标题" prop="title">
|
|
|
+ <el-input v-model.trim="article.title"></el-input>
|
|
|
+ </el-form-item>
|
|
|
+ <!--
|
|
|
+ 由于编辑器属于三方组件 并不能直接通过简单配置实现自动校验
|
|
|
+ 需要我们手动编写
|
|
|
+ 1.找到触发表单校验的时机 blur失焦事件
|
|
|
+ 2.编写校验逻辑 (主动发起校验)
|
|
|
+ -->
|
|
|
+ <el-form-item label="内容:" prop="content">
|
|
|
+ <!-- 编辑器中输入的内容会自动绑定到article.content -->
|
|
|
+ <quillEditor :options="editorOption" v-model="article.content" @blur="editorBlur"></quillEditor>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="封面">
|
|
|
+ <el-radio-group v-model="article.cover.type">
|
|
|
+ <!-- 根据后端接口约定 -->
|
|
|
+ <el-radio :label="0">无图</el-radio>
|
|
|
+ <el-radio :label="1">单图</el-radio>
|
|
|
+ <el-radio :label="3">三图</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <div v-if="article.cover.type>0" class="cover-container">
|
|
|
+ <!--
|
|
|
+ 这种写法相当于既传递了属性又绑定的了事件 语法糖
|
|
|
+ 为了简化当前基于props数据的操作 基于props的双向绑定
|
|
|
+ img-url.sync => img-url属性 + 绑定了一个修改此属性的自定义事件
|
|
|
+ -->
|
|
|
+ <Cover :img-url.sync="article.cover.images[index]" v-for="(item,index) in article.cover.type" :key="index"></Cover>
|
|
|
+ </div>
|
|
|
+ <el-form-item label="频道" prop="channel_id">
|
|
|
+ <el-select v-model="article.channel_id" placeholder="请选择活动区域">
|
|
|
+ <el-option
|
|
|
+ v-for="channel in channels"
|
|
|
+ :key="channel.id"
|
|
|
+ :label="channel.name"
|
|
|
+ :value="channel.id"
|
|
|
+ ></el-option>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" @click="doValidate(false)">发表</el-button>
|
|
|
+ <el-button @click="doValidate(true)">存入草稿</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { getChannels, updateArticle, getArticle } from '@/api/articles'
|
|
|
+// 引入编辑器核心包和相关样式
|
|
|
+import { quillEditor } from 'vue-quill-editor'
|
|
|
+import 'quill/dist/quill.core.css'
|
|
|
+import 'quill/dist/quill.snow.css'
|
|
|
+import 'quill/dist/quill.bubble.css'
|
|
|
+import Cover from './Cover'
|
|
|
+export default {
|
|
|
+ components: {
|
|
|
+ quillEditor,
|
|
|
+ Cover
|
|
|
+ },
|
|
|
+ data () {
|
|
|
+ return {
|
|
|
+ channels: [], // 频道列表
|
|
|
+ // 按照接口文档的格式 做一个参数收敛 包裹到一个对象中
|
|
|
+ // 这个对象内部包含了所有需要向后端发送的字段
|
|
|
+ article: {
|
|
|
+ title: '', // 文章标题
|
|
|
+ content: '', // 文章内容
|
|
|
+ cover: {
|
|
|
+ type: 0, // 封面图片的张数
|
|
|
+ images: [] // 封面的地址 属性现在属于当前组件 将来里面的数据要发送给后端
|
|
|
+ },
|
|
|
+ channel_id: '' // 频道id
|
|
|
+ },
|
|
|
+ // 定义规则
|
|
|
+ rules: {
|
|
|
+ title: [
|
|
|
+ { required: true, message: '文章标题为必填字段', trigger: 'blur' },
|
|
|
+ { min: 5, max: 30, message: '文章标题长度必须在5-30个字符之间', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ content: [
|
|
|
+ { required: true, message: '请输入文章内容', trigger: 'blur' },
|
|
|
+ { min: 20, max: 30000, message: '最少20个字', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ channel_id: [
|
|
|
+ { required: true, message: '请选择文章频道', trigger: 'change' }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ // 编辑器的配置
|
|
|
+ editorOption: {
|
|
|
+ placeholder: '哈哈哈哈哈', // 占位文字
|
|
|
+ modules: {
|
|
|
+ toolbar: [
|
|
|
+ ['bold', 'italic', 'underline', 'strike'],
|
|
|
+ ['blockquote', 'code-block'],
|
|
|
+ [{ header: 1 }, { header: 2 }],
|
|
|
+ [{ list: 'ordered' }, { list: 'bullet' }],
|
|
|
+ [{ indent: '-1' }, { indent: '+1' }]
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ hGetChannels () {
|
|
|
+ getChannels().then(res => {
|
|
|
+ this.channels = res.data.data.channels
|
|
|
+ })
|
|
|
+ },
|
|
|
+ hCreateArticle (draft) {
|
|
|
+ // 1.准备接口参数
|
|
|
+ const params = {
|
|
|
+ draft
|
|
|
+ }
|
|
|
+ // this.article 等同于data参数
|
|
|
+ // 2.发送接口请求
|
|
|
+ const id = this.$route.query.id
|
|
|
+ updateArticle(id, params, this.article).then(res => {
|
|
|
+ console.log(res)
|
|
|
+ })
|
|
|
+ },
|
|
|
+ editorBlur () {
|
|
|
+ console.log('当前编辑器失焦了')
|
|
|
+ // 在这里我们可以主动做一些校验逻辑
|
|
|
+ // 手动调用方法 主动发起对特定表单的校验 validateField
|
|
|
+ this.$refs.form.validateField('content')
|
|
|
+ // 总结
|
|
|
+ /**
|
|
|
+ * 1.给编辑器三方组件绑定了自定义事件 @blur, 绑定了一个methods中的函数
|
|
|
+ * 2.在blur事件触发时,在回调函数中主动触发表单校验
|
|
|
+ * 兜底校验 (对有所的表单进行统一校验) this.$refs.form.validate()
|
|
|
+ * 针对特定字段做校验 (针对特定字段) this.$refs.form.validateField('需要校验的字段名称')
|
|
|
+ */
|
|
|
+ },
|
|
|
+ // 兜底校验 防止用户直接点击发表按钮
|
|
|
+ doValidate (draft) {
|
|
|
+ // 另加一个对于type 和 images长度是否匹配的校验
|
|
|
+ if (this.article.cover.type !== this.article.cover.images.length) {
|
|
|
+ this.$message({
|
|
|
+ type: 'warning',
|
|
|
+ message: '封面图的个数和类型必须一致'
|
|
|
+ })
|
|
|
+ // 手动触发对于cover的校验
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ this.$refs.form.validate((valid) => {
|
|
|
+ // valid 所有表单项都通过校验返回true
|
|
|
+ // 有一个不满足条件返回false
|
|
|
+ if (!valid) {
|
|
|
+ return false
|
|
|
+ } else {
|
|
|
+ // 正式完成接口调用
|
|
|
+ this.hCreateArticle(draft)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ // 获取文章数据
|
|
|
+ hGetArticle () {
|
|
|
+ const id = this.$route.query.id
|
|
|
+ getArticle(id).then(res => {
|
|
|
+ console.log(res)
|
|
|
+ this.article = res.data.data
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted () {
|
|
|
+ this.hGetChannels()
|
|
|
+ this.hGetArticle()
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="less" scoped>
|
|
|
+ .cover-container{
|
|
|
+ margin-left: 70px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ }
|
|
|
+</style>
|