目录
项目gitee地址: https://gitee.com/LiDaJiang/git_vue2_koa2_mongoose_boke_alioss
效果图:
安装tinymce和@tinymce/tinymce-vue
cnpm i -S tinymce@5.7.1 @tinymce/tinymce-vue@^3
版本分别为:"@tinymce/tinymce-vue": "^3.2.8", "tinymce": "^5.7.1",
然后会发现是英文的,想换位中文。
在public中新建文件夹tinymce,在tinymce中导入zh_CN.js即可
为了让视频在上传后可以实时预览,需要修改tinymce/plugins/media/plugin.js,
大概在第619行的位置
return `<video width="${data.width}" height="${data.height}" controls="controls" ${data.poster ? poster = data.poster : ""} src="${data.source}"</video>`;
提前把nodemodule/tinymce/media/
客户端和服务端需要改动的结构目录:
客户端:
view/scwz/index.vue:
<!--scwz 上传文章-->
<template>
<div class="scwz">
<el-card v-loading="isLoading">
<div slot="header" class="clearfix">
<span style="font-size: 23px">上传文章</span>
<el-button type="primary" style="float: right" @click="addMd()">
上传
<i class="el-icon-upload el-icon--right"></i>
</el-button>
<el-button
type="warning"
style="float: right; margin: 0 10px"
@click="resetForm('ref_form_scwz')"
>
重置
<i class="el-icon-refresh-left el-icon--right"></i>
</el-button>
<el-button @click="$router.push({name:'wzlb'})" sizi="mini" style="float: right">跳转到"文章列表"页面</el-button>
</div>
<el-form ref="ref_form_scwz" :model="formData" label-width="100px">
<el-form-item label="标题:" prop="title">
<el-input v-model="formData.title" autocomplete="off" style="width: 200px" clearable></el-input>
</el-form-item>
<el-form-item label="内容:" prop="content">
<editor :init="editConfig" v-model="formData.content" />
</el-form-item>
</el-form>
</el-card>
<div
id="tinymce_vue_scwz"
v-html="formData.content"
style="position: fixed; visibility: hidden"
></div>
<el-dialog title="上传进度" :visible.sync="dialogTableVisible" :close-on-click-modal="false">
<el-progress :text-inside="true" :stroke-width="26" :percentage="percentage"></el-progress>
</el-dialog>
</div>
</template>
<script>
import { addMd } from "@/api/testmd/index";
import Editor from "@tinymce/tinymce-vue";
import tinymce from "tinymce/tinymce";
import client_alioss from "@/utils/alioss.js";
import { deleteUrls, get_urlImgVideo, arrayDifference } from "@/utils/index.js";
import "tinymce/plugins/image"; // 插入上传图片插件
import "tinymce/plugins/media"; // 插入视频插件
import "tinymce/plugins/table"; // 插入表格插件
import "tinymce/plugins/lists"; // 列表插件
import "tinymce/plugins/wordcount"; // 字数统计插件
import "tinymce/plugins/colorpicker";
import "tinymce/plugins/textcolor";
import "tinymce/plugins/link"; //超链接插件
import "tinymce/plugins/code"; //代码块插件
import "tinymce/plugins/contextmenu"; //右键菜单插件
import "tinymce/plugins/fullscreen"; //全屏
import "tinymce/plugins/help";
import "tinymce/plugins/charmap";
import "tinymce/plugins/paste";
import "tinymce/plugins/print";
import "tinymce/plugins/preview";
import "tinymce/plugins/hr";
import "tinymce/plugins/anchor";
import "tinymce/plugins/pagebreak";
import "tinymce/plugins/spellchecker";
import "tinymce/plugins/searchreplace";
import "tinymce/plugins/visualblocks";
import "tinymce/plugins/visualchars";
import "tinymce/plugins/insertdatetime";
import "tinymce/plugins/nonbreaking";
import "tinymce/plugins/autosave";
import "tinymce/plugins/fullpage";
import "tinymce/plugins/toc";
import "tinymce/themes/silver";
import "tinymce/icons/default";
export default {
name: "scwz",
components: {
editor: Editor
},
data() {
return {
tempCheckpoint: "",
percentage: 0,
dialogTableVisible: false, //上传进度窗口
urlImgVideo_start: [], //页面一加载完,就获取编辑器内容中的img和video地址
urlImgVideo_uploaded: [], //所有上传的img和video的地址。包括 原来已上传的+新上传的地址
urlImgVideo_end: [], //最后提交编辑内容时,当前的编辑器内容中的img和video地址
isLoading: false,
formData: {
title: "",
content: ``,
date: ""
},
editConfig: {
height: "800", //富文本高度
language_url: "/tinymce/zh_CN.js", //中文包
language: "zh_CN", //中文
skin_url: `/tinymce/skins/ui/oxide`,
content_css: `/tinymce/skins/content/default/content.css`,
// skin_url: `/tinymce/skins/ui/oxide-dark`, // 暗色系
// content_css: `/tinymce/skins/content/dark/content.css`, // 暗色系
browser_spellcheck: true, // 拼写检查
branding: false, // 去水印
elementpath: true, //禁用编辑器底部的状态栏
statusbar: true, // 隐藏编辑器底部的状态栏
paste_data_images: true, // 是否允许粘贴图像
menubar: true, // 最上方menu
fontsize_formats: "14px 16px 18px 20px 24px 26px 28px 30px 32px 36px", //字体大小
font_formats:
"微软雅黑=Microsoft YaHei,Helvetica Neue;PingFang SC;sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun;serifsans-serif;Terminal=terminal;monaco;Times New Roman=times new roman;times", //字体
file_picker_types: "image",
images_upload_credentials: true,
plugins: [
" lists link image charmap print preview anchor",
"searchreplace visualblocks code fullscreen ",
"insertdatetime media table paste code help wordcount textcolor wordcount"
],
toolbar:
"undo redo |formatselect |fontselect fontsizeselect link lineheight forecolor backcolor bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | image media quicklink outdent indent blockquote table numlist bullist preview fullscreen | removeformat",
// 图片上传三个参数,图片数据,成功时的回调函数,失败时的回调函数
images_upload_handler: async (blobInfo, success, failure) => {
//使用base64在本地加载显示。大图片转为base64后存在数据库会报错。
// success("data:image/png;base64," + blobInfo.base64());
//图片上传阿里云oss,并在本地加载显示
// let filename = blobInfo.filename();
let { name } = blobInfo.blob();
let { res } = await client_alioss.multipartUpload(
name,
blobInfo.blob()
);
if (res.requestUrls) {
// new URL()解决大图片上传不显示的问题。decodeURIComponent()解决中文乱码的问题
let { origin, pathname } = new URL(res.requestUrls[0]);
let decodeImgUrl = origin + decodeURIComponent(pathname);
success(decodeImgUrl);
this.urlImgVideo_uploaded.push(decodeImgUrl);
}
},
//上传视频
file_picker_types: "media", //添加后才会显示出本地上传图标
// 在tinymce5.0版本中这个钩子函数代替了原来的file_browser_callback,
// 有三个参数:callback 、value 、meta,其中callback的作用是将所选择的视频的url显示在输入框中。
file_picker_callback: async (callback, value, meta) => {
if (meta.filetype == "media") {
let input = document.createElement("input"); //创建一个隐藏的input
input.setAttribute("type", "file");
let that = this;
//下面这个onchange函数不要用箭头函数!!!,否则报错
input.onchange = async function() {
let file = this.files[0]; //选取第一个文件
let { name, size, type } = file;
let ext = name.split(".")[1];
if (ext !== "mp4") {
that.$message.error({
message: "上传资源只能是 mp4 格式的视频!",
showClose: true
});
return false;
}
that.dialogTableVisible = true;
let result = await client_alioss.multipartUpload(name, file, {
progress: function(p, checkpoint) {
that.percentage = parseFloat((p * 100).toFixed(2));
that.tempCheckpoint = checkpoint;
}
});
if (that.percentage == 100) {
that.dialogTableVisible = false;
that.$message({
showClose: true,
message: "上传成功!",
type: "success"
});
}
if (result) {
let { origin, pathname } = new URL(result.res.requestUrls[0]);
let decodeVideoUrl = origin + decodeURIComponent(pathname);
callback(decodeVideoUrl);
that.urlImgVideo_uploaded.push(decodeVideoUrl);
}
};
//触发点击
input.click();
}
}
}
};
},
computed: {},
mounted() {},
methods: {
//提交编辑内容
async addMd() {
this.isLoading = true;
let { title, content } = this.formData;
let {
data: { code }
} = await addMd({ title, content });
//删除已上传到服务器中的那些在编辑器内容中没有使用的img和video地址
//提交编辑器内容,给urlImgVideo_end属性赋值。获取当前富文本编辑器内容中的所有img ,video标签中的src,返回数组。
this.urlImgVideo_end = [...get_urlImgVideo("tinymce_vue_scwz")];
if (this.urlImgVideo_uploaded.length !== 0) {
deleteUrls(
arrayDifference(this.urlImgVideo_uploaded, this.urlImgVideo_end),
client_alioss
);
}
setTimeout(() => {
if (code == 200) {
this.isLoading = false;
this.$message.success("上传成功");
this.$refs["ref_form_scwz"].resetFields(); //注意:上传成功后,这里用这一行代码清空表单和富文本编辑框内容就行了,不要使用resetForm()方法
//注意:这里不要跳转到文章列表页面,否则上传文章页面会有缓存,清空不了
this.urlImgVideo_uploaded = [];
}
}, 1000);
},
//点击重置按钮
async resetForm(formName) {
this.$refs[formName].resetFields(); //清空表单
//上传了img、video,又点击重置按钮,就需要把已上传的img、video地址给删掉
deleteUrls(this.urlImgVideo_uploaded, client_alioss);
this.urlImgVideo_uploaded = [];
}
}
};
</script>
<style lang="scss" scoped>
.scwz {
padding: 10px;
color: black;
}
</style>
view/wzlb/index.vue:
<!--wzlb 文章列表 -->
<template>
<div class="wzlb">
<el-card v-show="!isShowEditPage">
<div slot="header" class="clearfix">
<span style="font-size: 23px">文章列表</span>
<el-button @click="getMdList()" sizi="mini" icon="el-icon-refresh" style="float: right">刷新</el-button>
<el-button @click="$router.push({name:'scwz'})" sizi="mini" style="float: right">跳转到"上传文章"页面</el-button>
</div>
<el-table :data="tableData" border>
<el-table-column label="标题" prop="title" align="center"></el-table-column>
<el-table-column label="更新日期" prop="date" align="center">
<template slot-scope="scope">
<i class="el-icon-time"></i>
<span style="margin-left: 10px">{{ scope.row.date | formatDate('YYYY-MM-DD HH:mm:ss') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="300px">
<template slot-scope="scope">
<el-button size="mini" @click="showPreviewDialog(scope.row)">
<i class="el-icon-view" /> 预览
</el-button>
<el-button size="mini" @click="showEditPage(scope.row)">
<i class="el-icon-edit" /> 编辑
</el-button>
<el-button size="mini" type="danger" @click="handleDelete(scope.row)">
<i class="el-icon-delete" /> 删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 预览文章 -->
<el-dialog :title="formData.title" width="70%" :visible.sync="isPreviewDialogVisible">
<div v-html="formData.content"></div>
</el-dialog>
<WzlbEdit v-if="isShowEditPage" :formData="formData" @cancelEdit="cancelEdit" @editMd="editMd"></WzlbEdit>
<div
id="tinymce_vue_wzlbDelete"
v-html="htmlContent"
style="position: fixed; visibility: hidden"
></div>
</div>
</template>
<script>
import { getMdList, deleteMd, editMd } from "@/api/testmd/index";
import WzlbEdit from "./wzlbEdit";
import client_alioss from "@/utils/alioss.js";
import { deleteUrls, get_urlImgVideo } from "@/utils/index.js";
export default {
name: "",
components: { WzlbEdit },
data() {
return {
isPreviewDialogVisible: false,
isShowEditPage: false,
htmlContent: ``,
formData: {
_id: "",
title: "",
content: "",
date: ""
},
tableData: [
{
title: "afd",
content: `asdf`,
date: ""
}
]
};
},
props: {},
created() {},
mounted() {
this.getMdList();
},
computed: {},
methods: {
//获取md列表
async getMdList() {
let { data } = await getMdList();
this.tableData = data.data; //注意:这里赋值时,有的时候是data,有的时候是data.data,具体的要自己打印出来再赋值
},
//打开预览窗口
showPreviewDialog(row) {
this.isPreviewDialogVisible = true;
this.formData = {
title: row.title,
content: row.content
};
},
//打开编辑窗口
showEditPage(row) {
this.isShowEditPage = true;
this.formData = {
_id: row._id,
title: row.title,
content: row.content,
date: row.date //不能少,因为编辑页面是主要监听的这个属性
};
},
//编辑Md
async editMd() {
let {
data: { code }
} = await editMd(this.formData, this.formData._id);
if (code == 200) {
this.isShowEditPage = false;
this.$notify({
title: "成功",
message: "编辑成功!",
type: "success"
});
this.getMdList();
}
},
//取消编辑
cancelEdit() {
this.isShowEditPage = false;
},
//删除Md
handleDelete(row) {
this.$confirm("此操作将永久删除该文章, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(async () => {
this.$message({
type: "success",
message: "删除成功!",
showClose: true
});
await deleteMd(row._id);
//注意:判断数组是否为空,要用Array.length是否为0来判断,不要直接用if([]){}判断!!!
//TODO :删除当前文章中所有保存在服务器中的图片和视频
this.htmlContent = row.content;
this.$nextTick(() =>
deleteUrls(
[...get_urlImgVideo("tinymce_vue_wzlbDelete")],
client_alioss
)
);
this.getMdList();
})
.catch(() => {
this.$message({
type: "info",
message: "已取消删除",
showClose: true
});
});
}
},
watch: {}
};
</script>
<style lang="scss" scoped>
.wzlb {
padding: 10px;
}
</style>
view/wzlbEdit.vue:
<!--wzlbEdit -->
<template>
<div class="wzlbEdit">
<el-card>
<div slot="header" class="clearfix">
<span style="font-size: 23px">编辑文章</span>
<el-button
@click="editMd('ref_form_wzlb')"
type="primary"
sizi="mini"
style="float: right"
icon="el-icon-edit"
>确 定</el-button>
<el-button
sizi="mini"
icon="el-icon-refresh-left"
style="float: right; margin: 0 10px"
@click="cancelEdit()"
>取消编辑</el-button>
</div>
<el-form ref="ref_form_wzlb" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="标题" prop="title">
<el-input v-model="formData.title" autocomplete="off" style="width: 90%"></el-input>
</el-form-item>
<el-form-item label="内容" prop="content">
<editor :init="editConfig" v-model="formData.content" />
</el-form-item>
</el-form>
</el-card>
<div
id="tinymce_vue_wzlbEdit"
v-html="formData.content"
style="position: fixed; visibility: hidden"
></div>
<el-dialog title="上传进度" :visible.sync="dialogTableVisible" :close-on-click-modal="false">
<el-progress :text-inside="true" :stroke-width="26" :percentage="percentage"></el-progress>
</el-dialog>
</div>
</template>
<script>
import Editor from "@tinymce/tinymce-vue";
import client_alioss from "@/utils/alioss.js";
import tinymce from "tinymce/tinymce";
import { deleteUrls, get_urlImgVideo, arrayDifference } from "@/utils/index.js";
import "tinymce/plugins/image"; // 插入上传图片插件
import "tinymce/plugins/media"; // 插入视频插件
import "tinymce/plugins/table"; // 插入表格插件
import "tinymce/plugins/lists"; // 列表插件
import "tinymce/plugins/wordcount"; // 字数统计插件
import "tinymce/plugins/colorpicker";
import "tinymce/plugins/textcolor";
import "tinymce/plugins/link"; //超链接插件
import "tinymce/plugins/code"; //代码块插件
import "tinymce/plugins/contextmenu"; //右键菜单插件
import "tinymce/plugins/fullscreen"; //全屏
import "tinymce/plugins/help";
import "tinymce/plugins/charmap";
import "tinymce/plugins/paste";
import "tinymce/plugins/print";
import "tinymce/plugins/preview";
import "tinymce/plugins/hr";
import "tinymce/plugins/anchor";
import "tinymce/plugins/pagebreak";
import "tinymce/plugins/spellchecker";
import "tinymce/plugins/searchreplace";
import "tinymce/plugins/visualblocks";
import "tinymce/plugins/visualchars";
import "tinymce/plugins/insertdatetime";
import "tinymce/plugins/nonbreaking";
import "tinymce/plugins/autosave";
import "tinymce/plugins/fullpage";
import "tinymce/plugins/toc";
import "tinymce/themes/silver";
import "tinymce/icons/default";
export default {
name: "",
components: { Editor },
props: ["formData"],
data() {
return {
tempCheckpoint: "",
percentage: 0,
dialogTableVisible: false, //上传进度窗口
urlImgVideo_start: [], //页面一加载完,就获取编辑器内容中的img和video地址
urlImgVideo_uploaded: [], //所有上传的img和video的地址。包括 原来已上传的+新上传的地址
urlImgVideo_end: [], //最后提交编辑内容时,当前的编辑器内容中的img和video地址
rules: {
title: [{ required: true, message: "请输入标题", trigger: "blur" }]
},
editConfig: {
height: "800", //富文本高度
language_url: "/tinymce/zh_CN.js", //中文包
language: "zh_CN", //中文
skin_url: `/tinymce/skins/ui/oxide`,
content_css: `/tinymce/skins/content/default/content.css`,
// skin_url: `/tinymce/skins/ui/oxide-dark`, // 暗色系
// content_css: `/tinymce/skins/content/dark/content.css`, // 暗色系
browser_spellcheck: true, // 拼写检查
branding: false, // 去水印
elementpath: true, //禁用编辑器底部的状态栏
statusbar: true, // 隐藏编辑器底部的状态栏
paste_data_images: true, // 是否允许粘贴图像
menubar: true, // 隐藏最上方menu
fontsize_formats: "14px 16px 18px 20px 24px 26px 28px 30px 32px 36px", //字体大小
font_formats:
"微软雅黑=Microsoft YaHei,Helvetica Neue;PingFang SC;sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun;serifsans-serif;Terminal=terminal;monaco;Times New Roman=times new roman;times", //字体
file_picker_types: "image",
images_upload_credentials: true,
plugins: [
" lists link image charmap print preview anchor",
"searchreplace visualblocks code fullscreen",
"insertdatetime media table paste code help wordcount"
],
toolbar:
"undo redo |formatselect |fontselect fontsizeselect link lineheight forecolor backcolor bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | image media quicklink outdent indent blockquote table numlist bullist preview fullscreen | removeformat",
// 图片上传三个参数,图片数据,成功时的回调函数,失败时的回调函数
images_upload_handler: async (blobInfo, success, failure) => {
//使用base64在本地加载显示
// success("data:image/jpeg;base64," + blobInfo.base64());
//上传阿里云oss,并在本地加载显示
let { name } = blobInfo.blob();
let {
res: { requestUrls }
} = await client_alioss.multipartUpload(name, blobInfo.blob());
if (requestUrls) {
//new URL()解决大图片上传不显示的问题。decodeURIComponent()解决中文乱码的问题
let { origin, pathname } = new URL(requestUrls[0]);
let decodeImgUrl = origin + decodeURIComponent(pathname);
success(decodeImgUrl);
this.urlImgVideo_uploaded.push(decodeImgUrl);
}
},
//上传视频
file_picker_types: "media", //添加后才会显示出本地上传图标
// 在tinymce5.0版本中这个钩子函数代替了原来的file_browser_callback,
// 有三个参数:callback 、value 、meta,其中callback的作用是将所选择的视频的url显示在输入框中。
file_picker_callback: async (callback, value, meta) => {
if (meta.filetype == "media") {
let input = document.createElement("input"); //创建一个隐藏的input
input.setAttribute("type", "file");
input.setAttribute("accept", "video/mp4");
let that = this;
//下面这个onchange函数不要用箭头函数!!!,否则报错
input.onchange = async function() {
let file = this.files[0]; //选取第一个文件
let { name, size, type } = file;
let ext = name.split(".")[1];
if (ext !== "mp4") {
that.$message.error({
message: "上传资源只能是 mp4 格式的视频!",
showClose: true
});
return false;
}
that.dialogTableVisible = true;
let result = await client_alioss.multipartUpload(name, file, {
progress: function(p, checkpoint) {
that.percentage = parseFloat((p * 100).toFixed(2));
that.tempCheckpoint = checkpoint;
}
});
if (that.percentage == 100) {
that.dialogTableVisible = false;
that.$message({
showClose: true,
message: "上传成功!",
type: "success"
});
}
if (result) {
let { origin, pathname } = new URL(result.res.requestUrls[0]);
let decodeVideoUrl = origin + decodeURIComponent(pathname);
callback(decodeVideoUrl);
that.urlImgVideo_uploaded.push(decodeVideoUrl);
}
};
//触发点击
input.click();
}
}
}
};
},
created() {},
mounted() {},
computed: {},
methods: {
//提交编辑
editMd(formName) {
this.$refs[formName].validate(async valid => {
if (valid) {
//删除已上传到服务器中的那些在编辑器内容中没有使用的img和video地址
// this.get_urlImgVideo_end();
this.urlImgVideo_end = [...get_urlImgVideo("tinymce_vue_wzlbEdit")];
if (this.urlImgVideo_uploaded.length !== 0) {
deleteUrls(
arrayDifference(this.urlImgVideo_uploaded, this.urlImgVideo_end),
client_alioss
);
}
//更新数据库
this.$emit("editMd");
} else {
console.log("error submit!!");
return false;
}
});
},
//点击取消编辑
async cancelEdit() {
//删除已上传到服务器中的那些在编辑器内容中没有使用的img和video地址
if (this.urlImgVideo_uploaded.length !== 0) {
deleteUrls(
arrayDifference(this.urlImgVideo_uploaded, this.urlImgVideo_start),
client_alioss
);
}
this.$emit("cancelEdit");
}
},
watch: {
//有类似触发子组件钩子函数的作用
"formData.date": {
handler: function(newVal) {
//由于formData中有date属性,而每次点击时时间都不同,所以每次点击编辑按钮都会触发监听
this.$nextTick(() => {
//初始化,给urlImgVideo_start属性赋值。页面一加载就获取当前富文本编辑器内容中的所有img ,video标签中的src,返回数组。
this.urlImgVideo_start = [...get_urlImgVideo("tinymce_vue_wzlbEdit")];
this.urlImgVideo_uploaded = [...this.urlImgVideo_start];
});
},
immediate: true
}
}
};
</script>
<style lang="scss" scoped>
.wzlbEdit {
/*去除upload组件过渡效果*/
::v-deep .el-upload-list__item {
transition: none !important;
}
}
</style>
util/index.js
//通过img和video的url组成的数组来删除服务器中的img,video
export async function deleteUrls(arr_deleteUrl = [], client_alioss = {}) {
if (arr_deleteUrl.length == 0) {
return
}
let arr_pathname = [];
for (const item of arr_deleteUrl) {
let { pathname } = new URL(item);//new URL()解决大图片加载失败问题。因为大图片的url中含有uploadid,导致url不能直接预览
//decodeURIComponent函数解决中文乱码
arr_pathname.push(decodeURIComponent(pathname));
}
if (arr_pathname.length !== 0) {
await client_alioss.deleteMulti(arr_pathname);
}
}
//获取img、video地址数组
export function get_urlImgVideo(elementId) {
let arrImgVideo = document.getElementById(elementId).querySelectorAll("img,video");
let arr = [];
if (arrImgVideo.length !== 0) {
arrImgVideo.forEach(item => {
arr.push(decodeURIComponent(item.src));
});
}
return arr;
}
// 两个数组取差集,返回arrA中有,而arrB中没有的。
// 如arrA = ["a", "b", "c", "d"];arrB = ["a", "b", "e"];则返回["c","d"]
export function arrayDifference(arrA, arrB) {
return arrA.filter(item => !arrB.includes(item))
}
router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [{
path: '/',
name: 'home',
component: () => import("@/views/home.vue"),
redirect: "/scwz",
children: [{
path: '/scwz',
name: 'scwz',
component: () => import("@/views/scwz/index")
}, {
path: '/wzlb',
name: 'wzlb',
component: () => import("@/views/wzlb/index")
}]
}]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
util/alioss.js
const OSS = require("ali-oss");
let client = new OSS({
region: "oss-cn-chengdu",//这里改成自己的
accessKeyId: "xxx",//这里改成自己的
accessKeySecret: "xxx",//这里改成自己的
bucket: "bucket-lijiang-test",//这里改成自己的
});
export default client;
request.js
import axios from 'axios'
const service = axios.create({
timeout: 5000
})
service.interceptors.request.use(
config => {
return config
},
error => {
return Promise.reject(error)
}
)
service.interceptors.response.use(
response => {
return response
},
error => {
return Promise.reject(error)
}
)
export default service
api/testmd/index.js
import request from '@/utils/request'
//添加md
export function addMd(data) {
return request({
url: '/api/testmd/add',
method: 'post',
data
})
}
//获取md列表
export function getMdList() {
return request({
url: '/api/testmd/getList',
method: 'get',
})
}
// 编辑md
export function editMd(data, _id) {
return request({
url: `/api/testmd/edit/${_id}`,
method: 'post',
data
})
}
// 删除md
export function deleteMd(_id) {
return request({
url: `/api/testmd/delete/${_id}`,
method: 'get'
})
}
main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)
import moment from 'moment'
//全局过滤器
Vue.filter('formatDate', function (value) {
return moment(value).format('YYYY-MM-DD HH:mm')
})
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
vue.config.js
module.exports = {
lintOnSave: false, //取消eslint
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000', //设置开发环境服务域名和端口号 别忘了加http
changeOrigin: true, //這裡true表示实现跨域
pathRewrite: {
'^/api': ''//使用时,如:axios.get('/api/login')
}
}
}
}
}
服务端
models/Testmd.js
const mongoose = require("../db");
const Schema = mongoose.Schema;
const testmdSchema = new Schema({
title: {
type: String
},
content: {
type: String
},
date: {
type: Date,
default: Date.now
}
})
module.exports = Testmd = mongoose.model("Testmd", testmdSchema,
"testmd")
routes/testmd.js
const router = require('koa-router')()
const Testmd = require("../models/Testmd"); //引入模块模型
router.prefix('/testmd')
//新增md
router.post('/add', async (ctx, next) => {
let { title, content } = ctx.request.body;
await Testmd.create({ title, content })
ctx.body = { code: 200, message: "新增成功" }
})
//获取md列表
router.get('/getList', async (ctx, next) => {
let data = await Testmd.find({})
ctx.body = { code: 200, message: "新增成功", data }
})
//编辑md
router.post('/edit/:_id', async (ctx, next) => {
let { title, content, date } = ctx.request.body;
let { _id } = ctx.params
await Testmd.findByIdAndUpdate(_id, { title, content, date })
ctx.body = { code: 200, message: "编辑成功" }
})
//删除md
router.get('/delete/:_id', async (ctx, next) => {
let { _id } = ctx.params;
await Testmd.findByIdAndDelete(_id)
ctx.body = { code: 200, message: "删除成功" }
})
module.exports = router
app.js
const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const logger = require('koa-logger')
const index = require('./routes/index')
const users = require('./routes/users')
const testmd = require('./routes/testmd')
onerror(app)
// middlewares
app.use(bodyparser({
enableTypes: ['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))
app.use(views(__dirname + '/views', {
extension: 'ejs'
}))
// logger
app.use(async (ctx, next) => {
const start = new Date()
await next()
const ms = new Date() - start
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})
// routes
app.use(index.routes(), index.allowedMethods())
app.use(users.routes(), users.allowedMethods())
app.use(testmd.routes(), testmd.allowedMethods())
// error-handling
app.on('error', (err, ctx) => {
console.error('server error', err, ctx)
});
module.exports = app
db.js
//db.js
let mongoose = require('mongoose')
mongoose.set('useCreateIndex', true)
mongoose.set('useNewUrlParser', true)
mongoose.set('useUnifiedTopology', true)
const DB_NAME = 'test_boke' //数据库名,自己更换
const DB_URL = 'localhost:27017'
mongoose.connect(`mongodb://${DB_URL}/${DB_NAME}`)
mongoose.connection.on('open', err => {
if (err) {
console.log(err)
return
}
console.log("数据库连接成功!")
})
module.exports = mongoose;