ArticleEdit.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. <template>
  2. <div class="publish-container">
  3. <el-card>
  4. <div slot="header" class="clearfix">
  5. <el-breadcrumb separator-class="el-icon-arrow-right">
  6. <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
  7. <el-breadcrumb-item>编辑文章</el-breadcrumb-item>
  8. </el-breadcrumb>
  9. </div>
  10. <el-form ref="form" :rules="rules" :model="article" label-width="80px">
  11. <el-form-item label="标题" prop="title">
  12. <el-input v-model.trim="article.title"></el-input>
  13. </el-form-item>
  14. <!--
  15. 由于编辑器属于三方组件 并不能直接通过简单配置实现自动校验
  16. 需要我们手动编写
  17. 1.找到触发表单校验的时机 blur失焦事件
  18. 2.编写校验逻辑 (主动发起校验)
  19. -->
  20. <el-form-item label="内容:" prop="content">
  21. <!-- 编辑器中输入的内容会自动绑定到article.content -->
  22. <quillEditor :options="editorOption" v-model="article.content" @blur="editorBlur"></quillEditor>
  23. </el-form-item>
  24. <el-form-item label="封面">
  25. <el-radio-group v-model="article.cover.type">
  26. <!-- 根据后端接口约定 -->
  27. <el-radio :label="0">无图</el-radio>
  28. <el-radio :label="1">单图</el-radio>
  29. <el-radio :label="3">三图</el-radio>
  30. </el-radio-group>
  31. </el-form-item>
  32. <div v-if="article.cover.type>0" class="cover-container">
  33. <!--
  34. 这种写法相当于既传递了属性又绑定的了事件 语法糖
  35. 为了简化当前基于props数据的操作 基于props的双向绑定
  36. img-url.sync => img-url属性 + 绑定了一个修改此属性的自定义事件
  37. -->
  38. <Cover :img-url.sync="article.cover.images[index]" v-for="(item,index) in article.cover.type" :key="index"></Cover>
  39. </div>
  40. <el-form-item label="频道" prop="channel_id">
  41. <el-select v-model="article.channel_id" placeholder="请选择活动区域">
  42. <el-option
  43. v-for="channel in channels"
  44. :key="channel.id"
  45. :label="channel.name"
  46. :value="channel.id"
  47. ></el-option>
  48. </el-select>
  49. </el-form-item>
  50. <el-form-item>
  51. <el-button type="primary" @click="doValidate(false)">发表</el-button>
  52. <el-button @click="doValidate(true)">存入草稿</el-button>
  53. </el-form-item>
  54. </el-form>
  55. </el-card>
  56. </div>
  57. </template>
  58. <script>
  59. import { getChannels, updateArticle, getArticle } from '@/api/articles'
  60. // 引入编辑器核心包和相关样式
  61. import { quillEditor } from 'vue-quill-editor'
  62. import 'quill/dist/quill.core.css'
  63. import 'quill/dist/quill.snow.css'
  64. import 'quill/dist/quill.bubble.css'
  65. import Cover from './Cover'
  66. export default {
  67. components: {
  68. quillEditor,
  69. Cover
  70. },
  71. data () {
  72. return {
  73. channels: [], // 频道列表
  74. // 按照接口文档的格式 做一个参数收敛 包裹到一个对象中
  75. // 这个对象内部包含了所有需要向后端发送的字段
  76. article: {
  77. title: '', // 文章标题
  78. content: '', // 文章内容
  79. cover: {
  80. type: 0, // 封面图片的张数
  81. images: [] // 封面的地址 属性现在属于当前组件 将来里面的数据要发送给后端
  82. },
  83. channel_id: '' // 频道id
  84. },
  85. // 定义规则
  86. rules: {
  87. title: [
  88. { required: true, message: '文章标题为必填字段', trigger: 'blur' },
  89. { min: 5, max: 30, message: '文章标题长度必须在5-30个字符之间', trigger: 'blur' }
  90. ],
  91. content: [
  92. { required: true, message: '请输入文章内容', trigger: 'blur' },
  93. { min: 20, max: 30000, message: '最少20个字', trigger: 'blur' }
  94. ],
  95. channel_id: [
  96. { required: true, message: '请选择文章频道', trigger: 'change' }
  97. ]
  98. },
  99. // 编辑器的配置
  100. editorOption: {
  101. placeholder: '哈哈哈哈哈', // 占位文字
  102. modules: {
  103. toolbar: [
  104. ['bold', 'italic', 'underline', 'strike'],
  105. ['blockquote', 'code-block'],
  106. [{ header: 1 }, { header: 2 }],
  107. [{ list: 'ordered' }, { list: 'bullet' }],
  108. [{ indent: '-1' }, { indent: '+1' }]
  109. ]
  110. }
  111. }
  112. }
  113. },
  114. methods: {
  115. hGetChannels () {
  116. getChannels().then(res => {
  117. this.channels = res.data.data.channels
  118. })
  119. },
  120. hCreateArticle (draft) {
  121. // 1.准备接口参数
  122. const params = {
  123. draft
  124. }
  125. // this.article 等同于data参数
  126. // 2.发送接口请求
  127. const id = this.$route.query.id
  128. updateArticle(id, params, this.article).then(res => {
  129. console.log(res)
  130. })
  131. },
  132. editorBlur () {
  133. console.log('当前编辑器失焦了')
  134. // 在这里我们可以主动做一些校验逻辑
  135. // 手动调用方法 主动发起对特定表单的校验 validateField
  136. this.$refs.form.validateField('content')
  137. // 总结
  138. /**
  139. * 1.给编辑器三方组件绑定了自定义事件 @blur, 绑定了一个methods中的函数
  140. * 2.在blur事件触发时,在回调函数中主动触发表单校验
  141. * 兜底校验 (对有所的表单进行统一校验) this.$refs.form.validate()
  142. * 针对特定字段做校验 (针对特定字段) this.$refs.form.validateField('需要校验的字段名称')
  143. */
  144. },
  145. // 兜底校验 防止用户直接点击发表按钮
  146. doValidate (draft) {
  147. // 另加一个对于type 和 images长度是否匹配的校验
  148. if (this.article.cover.type !== this.article.cover.images.length) {
  149. this.$message({
  150. type: 'warning',
  151. message: '封面图的个数和类型必须一致'
  152. })
  153. // 手动触发对于cover的校验
  154. return false
  155. }
  156. this.$refs.form.validate((valid) => {
  157. // valid 所有表单项都通过校验返回true
  158. // 有一个不满足条件返回false
  159. if (!valid) {
  160. return false
  161. } else {
  162. // 正式完成接口调用
  163. this.hCreateArticle(draft)
  164. }
  165. })
  166. },
  167. // 获取文章数据
  168. hGetArticle () {
  169. const id = this.$route.query.id
  170. getArticle(id).then(res => {
  171. console.log(res)
  172. this.article = res.data.data
  173. })
  174. }
  175. },
  176. mounted () {
  177. this.hGetChannels()
  178. this.hGetArticle()
  179. }
  180. }
  181. </script>
  182. <style lang="less" scoped>
  183. .cover-container{
  184. margin-left: 70px;
  185. margin-bottom: 20px;
  186. }
  187. </style>