前言

关于接口调用和页面的各种调优,都不必按部就班的按照这个样子来写。本来就是个小项目,作为一个前端菜鸟,我也在前进的路上,望知悉。

编辑器

用来写文章的编辑器,写博客,我们都是使用的markdown,由于对前端的不熟悉,只能胡乱选一个。最终使用的是tinymce,文档在这,这不是官方的。

⚠️!这是富文本编辑器,下面还有markdown编辑器, 两个都放出来了,可以按照个人喜好选择。

  1. 下载包
    网址,从这里把Download TinyMCE Community下载下来,然后再下载一个语言包,下载Chinese,不用说了

  2. 把tinymcew下的langs删了,替换成下载好的语言包,在couponents下新建一个文件夹editor,把这个文件夹放进去。

  3. 在editor下新建组件index.vue

    <template>
      <div>
        <Editor :init="init" v-model="content"></Editor>
      </div>
    </template>
    
    <script>
    import Editor from '@tinymce/tinymce-vue'
    import './tinymce.min.js'
    import './icons/default/icons.min.js'
    import './themes/silver/theme.min.js'
    import './langs/zh_CN'
    // 注册插件
    import './plugins/preview/plugin.min.js'
    import './plugins/paste/plugin.min.js'
    import './plugins/wordcount/plugin.min.js'
    import './plugins/code/plugin.min.js'
    import './plugins/image/plugin.min.js'
    import './plugins/imagetools/plugin.min.js'
    import './plugins/media/plugin.min.js'
    import './plugins/codesample/plugin.min.js'
    import './plugins/lists/plugin.min.js'
    import './plugins/table/plugin.min.js'
    export default {
      components: {Editor},
      props: {
        value: {
          type: String,
          default: '',
        },
      },
      data() {
        return {
          init: {
            language: 'zh_CN',
            height: '600px',
            plugins: 'preview paste wordcount code imagetools image media codesample lists table',
            branding: false,
            paste_data_images: true,
            toolbar: [
              'undo redo | styleselect |fontsizeselect| bold italic underline strikethrough |alignleft aligncenter alignright alignjustify |blockquote removeformat |numlist bullist table',
    'preview paste code codesample |image media',
            ],
            //上传图片
            images_upload_handler: async (blobInfo, succFun, failFun) => {
              let formdata = new FormData()
              formdata.append('file', blobInfo.blob(), blobInfo.name())
              const { data: res } = await this.$http.post('api/admin/upload/', formdata)
              succFun('http://localhost:8081/'+res.data)
              failFun(this.$message.error('上传图片失败'))
            },
            imagetools_cors_hosts: ['*'],
            imagetools_proxy: '',
          },
          content: this.value,
        }
      },
      watch: {
        value(newV) {
          this.content = newV
        },
        content(newV) {
          this.$emit('input', newV)
        },
      },
    }
    </script>
    
    <style scoped>
    @import url('./skins/ui/oxide/skin.min.css');
    @import url('./skins/content/default/content.min.css');
    </style>

在写完了富文本之后,我发现我搞错了一点。博客当然是要用markdown啊😭,我本以为没有markdown编辑器的,哎,怪我认知不好。于是在一顿搜罗之后,我找到了一个巨好用的markdown编辑器。网址

npm install mavon-editor --save

直接安装,安装完成之后,直接全局引入。在main.js中。

import mavonEditor from "mavon-editor";
import "mavon-editor/dist/css/index.css";
Vue.use(mavonEditor);

然后直接使用,github上提供了他很多方法,这里我们直接引用。再根据需要,修改一下上传文件的方法。就大功告成。

<template>
	<div>
		<a-card>
			<h3>{{id?'编辑文章':'新增文章'}}</h3>
		<a-form-model :model="artInfo" ref="artInfoRef" :rules="artInfoRules" :hideRequiredMark="true">
			<a-form-model-item label="文章标题" prop="title">
				<a-input style="width:300px" v-model="artInfo.title"></a-input>
			</a-form-model-item>
			<a-form-model-item 
			style="width:200px"
			prop="cid"
			label="文章分类">
				<a-select v-model="artInfo.cid" placeholder="请选择分类" @change="cateChange">
					<a-select-option v-for="item in Catelist" :key="item.id" :value="item.id">{{item.name}}</a-select-option>
				</a-select>
			</a-form-model-item>
			<a-form-model-item label="文章描述" prop="desc">
				<a-input type="textarea" v-model="artInfo.desc"></a-input>
			</a-form-model-item>
			<a-form-model-item label="缩略图" prop="img"> 
				<a-upload
					name="file"
					:multiple="false"
					:action="uploadUrl"
					:headers="headers"
					@change="upChange"
					listType="picture"
				>
					<a-button> <a-icon type="upload" /> 上传图片 </a-button>
					<br/>
					<template v-if="id">
						<img :src="artInfo.img" style="width:120px;height:100px">
					</template>
				</a-upload>
			</a-form-model-item>
			<a-form-model-item label="内容" prop="content">
				<!-- <Editor v-model="artInfo.content"></Editor> -->
				<mavon-editor
            ref="md"
            placeholder="请输入文档内容..."
            :boxShadow="false"
            style="z-index:1;border: 1px solid #d9d9d9;height:50vh"
            v-model="artInfo.content"
            :toolbars="toolbars"
						@imgAdd="$imgAdd"
          />
			</a-form-model-item>
			<a-form-model-item>
				<a-button type="primary" 
				style="margin-right:15px" 
				@click="artOk(artInfo.id)"
				>
				{{artInfo.id?"更新":'提交'}}
				</a-button>
				<a-button type="danger" @click="addCancel">取消</a-button>
			</a-form-model-item>
		</a-form-model>
		</a-card>
	</div>
</template>

<script>
import {Url} from "../../plugins/http"
import Editor from "../editor/index"
export default {
	components:{Editor},
	props:['id'],
	data(){
		return{
			artInfo:{
				id:0,
				title:'',
				cid:undefined,
				desc:'',
				content:'',
				img:''
			},
			Catelist:[],
			uploadUrl:Url+'/api/admin/upload/',
			headers:{},
			artInfoRules: {
        title: [{ required: true, message: '请输入文章标题', trigger: 'change' }],
        cid: [{ required: true, message: '请选择文章分类', trigger: 'change' }],
        desc: [
          { required: true, message: '请输入文章描述', trigger: 'change' },
          { max: 120, message: '描述最多可写120个字符', trigger: 'change' },
        ],
        content: [{ required: true, message: '请输入文章内容', trigger: 'change' }],
      },
			// markdown对象
			toolbars: {
        bold: true, // 粗体
        italic: true, // 斜体
        header: true, // 标题
        underline: true, // 下划线
        strikethrough: true, // 中划线
        mark: true, // 标记
        superscript: true, // 上角标
        subscript: true, // 下角标
        quote: true, // 引用
        ol: true, // 有序列表
        ul: true, // 无序列表
        link: true, // 链接
        imagelink: true, // 图片链接
        code: true, // code
        table: true, // 表格
        fullscreen: true, // 全屏编辑
        readmodel: true, // 沉浸式阅读
        htmlcode: true, // 展示html源码
        help: true, // 帮助
        /* 1.3.5 */
        undo: true, // 上一步
        redo: true, // 下一步
        trash: true, // 清空
        save: false, // 保存(触发events中的save事件)
        /* 1.4.2 */
        navigation: true, // 导航目录
        /* 2.1.8 */
        alignleft: true, // 左对齐
        aligncenter: true, // 居中
        alignright: true, // 右对齐
        /* 2.2.1 */
        subfield: true, // 单双栏模式
        preview: true // 预览
      }
		}
	},
	created(){
		this.getCateList()
		this.headers = {Authorization:`Bearer ${window.sessionStorage.getItem('token')}`}
		if(this.id){
			this.getArtInfo(this.id)
		}
	},
	methods:{
		// 查询文章信息
		async getArtInfo(id){
			const {data: res} = await this.$http.get(`api/v1/article/${id}/`)
			if(res.code != 200) return this.$$message.error(res.msg)
			this.artInfo = res.data
			this.artInfo.id = res.data.ID
		},
		// 获取分类
    async getCateList() {
      const { data: res } = await this.$http.get('api/v1/category/',{params: {
          size: 100,
          page: 1,
        },})
      if (res.code !== 200) return this.$message.error(res.msg)
      this.Catelist = res.data
    },
		cateChange(val){
			this.artInfo.cid = val
		},
		upChange(info){
			if (info.file.status !== 'uploading') {
        
      }
      if (info.file.status === 'done') {
        this.$message.success('上传成功');
				const imgUrl = info.file.response.data
				this.artInfo.img = imgUrl
      } else if (info.file.status === 'error') {
        this.$message.error(`上传失败`);
      }
		},
		// 提交
		artOk(id){
			this.$refs.artInfoRef.validate(async (valid) => {
				if (!valid) return this.$message.error('参数验证未通过,请按要求录入文章内容')
				if (id === 0) {
          const { data: res } = await this.$http.post('/api/admin/article/', this.artInfo)
          if (res.code !== 200) return this.$message.error(res.msg)
          this.$router.push('/admin/artlist')
          this.$message.success('添加文章成功')
        } else {
          const { data: res } = await this.$http.put(`/api/admin/article/${id}/`, this.artInfo)
          if (res.code !== 200) return this.$message.error(res.msg)
          this.$router.push('/admin/artlist')
          this.$message.success('更新文章成功')
        }
			})
		},
		// 取消
		addCancel(){
			this.$refs.artInfoRef.resetFields()
		},
		// 上传图片方法
    async $imgAdd(pos, $file) {
			var formdata = new FormData();
			formdata.append('file', $file);
			const {data: res} = await this.$http.post("/api/admin/upload/",formdata,{ 'Content-Type': 'multipart/form-data' })
			this.$refs.md.$imglst2Url([[pos, "http://localhost:8081/"+res.data]])
    },
	}

}
</script>

<style>

</style>

这样的话,我们以后修改起来也是很方便的,只是,在后台我们需要一个渲染库。

渲染库

渲染库使用的是这个

安装

go get -u github.com/gomarkdown/markdown

可以在utils下新建一个Markdown.go,写成工具函数。

package utils

import (
	"github.com/gomarkdown/markdown"
	"github.com/gomarkdown/markdown/html"
)


// 渲染html
func Render(data string) string {
	htmlFlags := html.CommonFlags | html.HrefTargetBlank
	opts := html.RendererOptions{Flags: htmlFlags}
	renderer := html.NewRenderer(opts)
	md := []byte(data)
	content := markdown.ToHTML(md, nil, renderer)
	return string(content)
}

然后只需要在必要的地方来使用这个工具即可。