点击写文章,会跳转到该页面
点击右上角的发布,会弹出发布的对话框因此,我们要调用文章分类列表和文章标签列表的接口
以及如果是点击文字的编辑按钮进入该页面的话,还需要通过调用文章id获取文章详情的接口
还需要调用发布文章的接口
/**
* 获取分类列表
*/
export function getAllCategorys()
{
return request({
url:"/categorys",
method:'GET',
})
}
/**
* 查询标签列表
*/
export function getAllTags()
{
return request({
url:"/tags",
method:"GET",
})
}
/**
* 通过文章id获取文章详情
* @param PageParams
*/
export function getArticleById(id)
{
return request({
url: `/articles/${id}`,
method: 'POST'
})
}
/**
* 通过token和文章参数发布文章
* @param PageParams
*/
export function publishArticle(article,token)
{
return request({
headers: {'Authorization': token},
url: '/articles/publish',
method: 'post',
data: article
})
}
这里的header是复用了baseHeader组件。
将simple传入到baseHeader中,baseHeader通过props获取,将之前首页展示的首页、文章分类、标签、文章归档全部屏蔽掉
然后用了slot,将传入的写文章、发布、取消全部插入到baseHeader中
<template>
<div id="write" v-title :data-title="title">
<el-container>
<!--标题、发布、取消按钮-->
<base-header :simple=true>
<el-col :span="4" :offset="2">
<div class="me-write-info">写文章</div>
</el-col>
<el-col :span="4" :offset="6">
<div class="me-write-btn">
<el-button round @click="publishShow">发布</el-button>
<el-button round @click="cancel">取消</el-button>
</div>
</el-col>
</base-header>
<el-container class="me-area me-write-box">
<el-main class="me-write-main">
<div class="me-write-title">
<el-input resize="none"
type="textarea"
autosize
v-model="articleForm.title"
placeholder="请输入标题"
class="me-write-input">
</el-input>
</div>
<div id="placeholder" style="visibility: hidden;height: 89px;display: none;"></div>
<markdown-editor :editor="articleForm.editor" class="me-write-editor"></markdown-editor>
</el-main>
</el-container>
<!--点击发布之后弹出的对话框-->
<el-dialog title="摘要 分类 标签"
:visible.sync="publishVisible"
:close-on-click-modal=false
custom-class="me-dialog">
<el-form :model="articleForm" ref="articleForm" :rules="rules">
<el-form-item prop="summary">
<el-input type="textarea"
v-model="articleForm.summary"
:rows="6"
placeholder="请输入摘要">
</el-input>
</el-form-item>
<el-form-item label="文章分类" prop="category">
<el-select v-model="articleForm.category" value-key="id" placeholder="请选择文章分类">
<el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c"></el-option>
</el-select>
</el-form-item>
<el-form-item label="文章标签" prop="tags">
<el-checkbox-group v-model="articleForm.tags">
<el-checkbox v-for="t in tags" :key="t.id" :label="t.id" name="tags">{{t.tagName}}</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="publishVisible = false">取 消</el-button>
<el-button type="primary" @click="publish('articleForm')">发布</el-button>
</div>
</el-dialog>
</el-container>
</div>
</template>
<script>
import BaseHeader from '@/views/BaseHeader'
import MarkdownEditor from '@/components/markdown/MarkdownEditor'
import {publishArticle, getArticleById} from '@/api/article'
import {getAllCategorys} from '@/api/category'
import {getAllTags} from '@/api/tag'
export default
{
name: 'publishArticle',
mounted()
{
if(this.$route.params.id)
{
this.getArticleById(this.$route.params.id)
}
/*获取是所有标签和分类*/
this.getCategorysAndTags();
this.editorToolBarToFixedWrapper = this.$_.throttle(this.editorToolBarToFixed, 200)
window.addEventListener('scroll', this.editorToolBarToFixedWrapper, false);
},
beforeDestroy()
{
window.removeEventListener('scroll', this.editorToolBarToFixedWrapper, false)
},
data()
{
return {
publishVisible: false,
categorys: [],
tags: [],
articleForm: {
id: '',
title: '',
summary: '',
category: '',
tags: [],
editor: {
value: '',
ref: '',//保存mavonEditor实例 实际不该这样
default_open: 'edit',
toolbars: {
bold: true, // 粗体
italic: true, // 斜体
header: true, // 标题
underline: true, // 下划线
strikethrough: true, // 中划线
mark: true, // 标记
superscript: true, // 上角标
subscript: true, // 下角标
quote: true, // 引用
ol: true, // 有序列表
ul: true, // 无序列表
imagelink: true, // 图片链接
code: true, // code
fullscreen: true, // 全屏编辑
readmodel: true, // 沉浸式阅读
help: true, // 帮助
undo: true, // 上一步
redo: true, // 下一步
trash: true, // 清空
navigation: true, // 导航目录
//subfield: true, // 单双栏模式
preview: true, // 预览
}
}
},
rules: {
summary: [
{required: true, message: '请输入摘要', trigger: 'blur'},
{max: 80, message: '不能大于80个字符', trigger: 'blur'}
],
category: [
{required: true, message: '请选择文章分类', trigger: 'change'}
],
tags: [
{type: 'array', required: true, message: '请选择标签', trigger: 'change'}
]
}
}
},
computed: {
title (){
return '写文章'
}
},
methods: {
/*点击文章编辑时,获取文章详情*/
getArticleById(id)
{
let that = this;
getArticleById(id).then(data =>
{
Object.assign(that.articleForm, data.data);
that.articleForm.editor.value = data.data.body.content;
let tags = this.articleForm.tags.map(function (item)
{
return item.id;
});
this.articleForm.tags = tags
}).catch(error =>
{
if (error !== 'error')
{
that.$message({type: 'error', message: '文章加载失败', showClose: true})
}
})
},
publishShow()
{
if (!this.articleForm.title)
{
this.$message({message: '标题不能为空哦', type: 'warning', showClose: true})
return
}
if (this.articleForm.title.length > 30)
{
this.$message({message: '标题不能大于30个字符', type: 'warning', showClose: true})
return
}
/*如果markdown编辑器内容为空*/
if (!this.articleForm.editor.ref.d_render)
{
this.$message({message: '内容不能为空哦', type: 'warning', showClose: true})
return
}
/*点击发布,之后的对话框显示*/
this.publishVisible = true;
},
/*文章发布*/
publish(articleForm)
{
let that = this;
this.$refs[articleForm].validate((valid) =>
{
if (valid)
{
let tags = this.articleForm.tags.map(function (item)
{
return {id: item};
});
let article =
{
id: this.articleForm.id,
title: this.articleForm.title,
summary: this.articleForm.summary,
category: this.articleForm.category,
tags: tags,
body: {
content: this.articleForm.editor.value,
contentHtml: this.articleForm.editor.ref.d_render
}
};
this.publishVisible = false;
let loading = this.$loading({
lock: true,
text: '发布中,请稍后...'
});
publishArticle(article,this.$store.state.token).then((data) =>
{
if(data.success){
loading.close();
that.$message({message: '发布成功啦', type: 'success', showClose: true})
/*跳转到文章详情*/
that.$router.push({path: `/view/${data.data.id}`})
}else{
that.$message({message: error, type: '发布文章失败:'+data.msg, showClose: true});
}
}).catch((error) =>
{
loading.close();
if (error !== 'error')
{
that.$message({message: error, type: 'error', showClose: true});
}
});
} else
{
return false;
}
});
},
cancel() {
this.$confirm('文章将不会保存, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$router.push('/')
})
},
/*获取所有标签以及分类*/
getCategorysAndTags() {
let that = this
getAllCategorys().then(data =>
{
if(data.success)
{
that.categorys = data.data
}else{
that.$message({type: 'error', message: '文章分类加载失败', showClose: true})
}
}).catch(error =>
{
if (error !== 'error')
{
that.$message({type: 'error', message: '文章分类加载失败', showClose: true})
}
})
getAllTags().then(data =>
{
if(data.success)
{
that.tags = data.data
}else
{
that.$message({type: 'error', message: '标签加载失败', showClose: true})
}
}).catch(error =>
{
if (error !== 'error')
{
that.$message({type: 'error', message: '标签加载失败', showClose: true})
}
})
},
editorToolBarToFixed()
{
let toolbar = document.querySelector('.v-note-op');
let curHeight = document.documentElement.scrollTop || document.body.scrollTop;
if (curHeight >= 160)
{
document.getElementById("placeholder").style.display = "block"; //bad 用计算属性较好
toolbar.classList.add("me-write-toolbar-fixed");
} else
{
document.getElementById("placeholder").style.display = "none";
toolbar.classList.remove("me-write-toolbar-fixed");
}
}
},
components:
{
'base-header': BaseHeader,
'markdown-editor': MarkdownEditor
},
//组件内的守卫 调整body的背景色
beforeRouteEnter(to, from, next)
{
window.document.body.style.backgroundColor = '#fff';
next();
},
beforeRouteLeave(to, from, next)
{
window.document.body.style.backgroundColor = '#f5f5f5';
next();
}
}
</script>
<style>
.el-header
{
position: fixed;
z-index: 1024;
min-width: 100%;
box-shadow: 0 2px 3px hsla(0, 0%, 7%, .1), 0 0 0 1px hsla(0, 0%, 7%, .1);
}
.me-write-info
{
line-height: 60px;
font-size: 18px;
font-weight: 600;
}
.me-write-btn
{
margin-top: 10px;
}
.me-write-box
{
max-width: 700px;
margin: 80px auto 0;
}
.me-write-main
{
padding: 0;
}
.me-write-title
{
}
.me-write-input textarea
{
font-size: 32px;
font-weight: 600;
height: 20px;
border: none;
}
.me-write-editor
{
min-height: 650px !important;
}
.me-header-left
{
margin-top: 10px;
}
.me-title img
{
max-height: 2.4rem;
max-width: 100%;
}
.me-write-toolbar-fixed
{
position: fixed;
width: 700px !important;
top: 60px;
}
.v-note-op
{
box-shadow: none !important;
}
.auto-textarea-input, .auto-textarea-block
{
font-size: 18px !important;
}
</style>
这里需要安装lodash插件:
npm i --save lodash
main.js引用:
import lodash from 'lodash'
Object.defineProperty(Vue.prototype, '$_', { value: lodash })
baseHeader:
<template>
<el-header class="my-area">
<el-row class="my-header">
<!--头像-->
<el-col :span="4" class="my-header-left">
<router-link to="/" class="my-title">
<img src="../../assets/img/logo.png"/>
</router-link>
</el-col>
<el-col v-if="!simple" :span="16">
<!--
:router=true
是否使用vue-router的模式,
启用该模式会在激活导航时以 index 作为 path 进行路由跳转-->
<el-menu :router=true
menu-trigger="click"
active-text-color="#00aaff"
:default-active="activeIndex"
mode="horizontal">
<el-menu-item index="/">
<i class="el-icon-tickets"></i>首页
</el-menu-item>
<el-menu-item index="/category/all">
<i class="el-icon-s-order"></i>文章分类
</el-menu-item>
<el-menu-item index="/tag/all">
<i class="el-icon-price-tag"></i>标签
</el-menu-item>
<el-menu-item index="/archives">
<i class="el-icon-chat-line-square"></i>文章归档
</el-menu-item>
<!--offset可以指定偏移数-->
<el-col :span="4" :offset="4">
<el-menu-item index="/write">
<i class="el-icon-edit"></i>写文章
</el-menu-item>
</el-col>
</el-menu>
</el-col>
<template v-else>
<slot></slot>
</template>
<el-col :span="4">
<el-menu :router=true
menu-trigger="click"
mode="horizontal"
active-text-color="#00aaff">
<template v-if="!user.login">
<el-menu-item index="/login">
<el-button type="text">登录</el-button>
</el-menu-item>
<el-menu-item index="/register">
<el-button type="text">注册</el-button>
</el-menu-item>
</template>
<template v-else>
<el-submenu index>
<template slot="title">
<img class="my-header-picture" :src="user.avatar"/>
</template>
<el-menu-item index @click="logout"><i class="el-icon-back"></i>退出</el-menu-item>
</el-submenu>
</template>
</el-menu>
</el-col>
</el-row>
</el-header>
</template>
<script>
export default {
name: 'BaseHeader',
props: {
activeIndex: String,//选中哪个menu进行下方条形的一个显示
simple: {
type: Boolean,
default: false
}
},
computed: {
user()
{
let login = this.$store.state.account.length !== 0;
let avatar = this.$store.state.avatar;
return{
login,avatar
}
}
},
methods: {
logout()
{
this.$store.dispatch("logout").then((res)=>
{
this.$router.push({path:"/login"});
this.$message({message:"已退出账户,请重新登录",type:"success",showClose:true});
}).catch((error)=>
{
this.$message({message:error,type:"error",showClose:true});
})
},
}
}
</script>
<style>
/*登录、注册*/
.el-button
{
color: #00aaff;
}
/*body*/
.el-header
{
background-color: white;
position: fixed;
z-index: 1024;
min-width: 100%;
box-shadow: 0 1px 2px hsla(0, 0%, 7%, .1), 0 0 0 0px hsla(0, 0%, 7%, .1);
}
/*头像*/
.my-title
{
margin-top: 10px;
font-size: 24px;
}
.my-header-left
{
margin-top: 10px;
}
/*左上角头像*/
.my-title img
{
max-height: 2.5rem;
max-width: 100%;
}
.my-header-picture
{
width: 36px;
height: 36px;
border: 1px solid #ddd;
border-radius: 50%;
vertical-align: middle;
background-color: #00aaff;
}
</style>
markdown需要调用upload接口,将图片上传到七牛云
import request from '@/request'
/*发布文章时,有可能在markdown会上传图片*/
export function upload(formdata) {
return request({
headers: {'Content-Type': 'multipart/form-data'},
url: '/upload',
method: 'post',
data: formdata
})
}
markdown组件:
<template>
<mavon-editor
class="my-editor"
ref="md"
v-model="editor.value"
@imgAdd="imgAdd"
v-bind="editor">
</mavon-editor>
</template>
<script>
import {mavonEditor} from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
import {upload} from '@/api/upload'
export default {
name: 'MarkdownEditor',
props: {
editor: Object
},
data() {
return {}
},
mounted() {
this.$set(this.editor, 'ref', this.$refs.md)
},
methods: {
/*图片上传*/
imgAdd(pos, $file)
{
let that = this
let formData = new FormData();
formData.append('image', $file);
upload(formData).then(res =>
{
// 第二步.将返回的url替换到文本原位置![...](./0) -> ![...](url)
if (res.data.success)
{
that.$refs.md.$img2Url(pos, res.data.data);
} else {
that.$message({message: res.data.msg, type: 'error', showClose: true})
}
}).catch(err => {
that.$message({message: err, type: 'error', showClose: true});
})
}
},
components: {
mavonEditor
}
}
</script>
<style scoped>
.my-editor
{
z-index: 6 !important;
}
.v-note-wrapper.fullscreen
{
top: 60px !important
}
</style>