900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > VBlog项目代码理解之前端

VBlog项目代码理解之前端

时间:2023-10-21 20:51:46

相关推荐

VBlog项目代码理解之前端

文章目录

VBlog项目代码理解之前端资源配置问题config/index:通过代理解决跨域问题router:页面跳转控制utils/api.js:调用后端方法并接受返回值filter_utils:过滤器main:导入依赖组件Login:登录与权限验证Home:主页面框架ArticleList:显示文章列表BlogTable:具体文章列表BlogDetail:文章显示PostArticle:编辑与发表BlogCfg:博客配置CateMana:栏目管理UserMana:员工显示与管理

VBlog项目代码理解之前端

资源

项目地址

前后端交互理解

后端代码理解

推荐:整个项目几乎是只用到了SpringBoot、Vue、Mybatis、ElementUI,没有用到Redis、RabbitMQ等内容,很适合刚学完SpringBoot和Vue的同学练手,感谢作者!帮作者打个广告吧~

PS:这是本人第一个学习的项目,难免会有错误的地方,哪里有问题烦请指正,感谢!

配置问题

解决前后端交互、跨域、页面跳转等问题

config/index:通过代理解决跨域问题

核心内容就是proxyTable,这块根据SpringBoot的配置来配置,具体内容都在注释里了。config/index.js参数详解

'use strict'// Template version: 1.2.7// see http://vuejs-templates.github.io/webpack for documentation.// 这个不知道有啥用const path = require('path')// 话说这边并没有用env: require()来指定环境啊module.exports = {// 管开发的时候,既有前后端交互的跨域配置,也有在前端玩的配置dev: {// Paths// 静态资源子目录和公开地址assetsSubDirectory: 'static',assetsPublicPath: '/',// 这边是设置代理,解决跨域问题,开发的时候才用proxyTable: {// 路径啥也不加'/': {// 改写,相当于把http://localhost:8080改成下面的内容+/target: 'http://localhost:8081',changeOrigin: true, // 指示是否跨域pathRewrite: {'^/': '' // 啥都没有,就等价于http://localhost:8081/}}},// 这边是前端自己玩// Various Dev Server settingshost: 'localhost', // can be overwritten by process.env.HOST// dev-server的端口号port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determinedautoOpenBrowser: false,errorOverlay: true,notifyOnErrors: true,poll: false, // /configuration/dev-server/#devserver-watchoptions-/*** Source Maps*/// /configuration/devtool/#developmentdevtool: 'eval-source-map',// debug工具出问题的时候可以试试改为false,可能会有用cacheBusting: true,// 是否生成css、map文件,说可能存在问题,但是没啥必要用这个,出问题了可以控制台// 所以这里于默认不同,设为falsecssSourceMap: false,},// 配置build、打包问题// 这一套很固定,根本不用动,创建完了就是这样的build: {// 这块好像都是说运行npm run build之后,生成的文件应在的位置// build之后生成的index位置?index: path.resolve(__dirname, '../dist/index.html'),// Paths// 静态资源的根目录assetsRoot: path.resolve(__dirname, '../dist'),// 静态资源子目录assetsSubDirectory: 'static',// 静态资源的公开路径,也就是真正的引用路径(引用路径指使用时?)assetsPublicPath: '/',/*** Source Maps*/// 是否生成生产环境的sourcemap,sourcemap用来对编译后的文件进行debug,方法是映射回编译前的文件// 编译后的代码人是看不懂的productionSourceMap: true,// 这应当是一种映射工具devtool: '#source-map',// 是否在生产环境中压缩代码,如果要压缩必须安装compression-webpack-pluginproductionGzip: false,// 指定要压缩的文件类型productionGzipExtensions: ['js', 'css'],// 开启编译完成后的报告,只有运行了npm run build --report才有吧bundleAnalyzerReport: process.env.npm_config_report}}

router:页面跳转控制

所有的跳转都通过路由控制,在router/index.js文件中配置,path可以通过children设置子路径,如果有多个子模块,就会变成可选模式。同时每个路径都绑定了component,子路径的component会在父路径的组件的<router-view>位置显示,所以路径的跳转本质上就是组件的不同组合。还可以配置一些信息,如组件名字、是否隐藏、保持激活等内容。

import Vue from 'vue'import Router from 'vue-router'import Login from '@/components/Login'import Home from '@/components/Home'import ArticleList from '@/components/ArticleList'import CateMana from '@/components/CateMana'import DataCharts from '@/components/DataCharts'import PostArticle from '@/components/PostArticle'import UserMana from '@/components/UserMana'import BlogDetail from '@/components/BlogDetail'import Doex from '@/components/Doex'Vue.use(Router)export default new Router({routes: [{path: '/',name: '登录',hidden: true,component: Login}, {path: '/home',name: '',component: Home,hidden: true}, {path: '/home',component: Home,name: '文章管理',iconCls: 'fa fa-file-text-o',children: [{path: '/articleList',name: '文章列表',component: ArticleList,meta: {keepAlive: true}}, {path: '/postArticle',name: '发表文章',component: PostArticle,meta: {keepAlive: false}}, {path: '/blogDetail',name: '博客详情',component: BlogDetail,hidden: true,meta: {keepAlive: false}}, {path: '/editBlog',name: '编辑博客',component: PostArticle,hidden: true,meta: {keepAlive: false}}]}, {path: '/home',component: Home,name: '用户管理',children: [{path: '/user',iconCls: 'fa fa-user-o',name: '用户管理',component: UserMana}]}, {path: '/home',component: Home,name: '栏目管理',children: [{path: '/cateMana',iconCls: 'fa fa-reorder',name: '栏目管理',component: CateMana}]}, {path: '/home',component: Home,name: '数据统计',iconCls: 'fa fa-bar-chart',children: [{path: '/charts',iconCls: 'fa fa-bar-chart',name: '数据统计',component: DataCharts}]}, {path: '/home',component: Home,name: '身体记录',iconCls: 'el-icon-date',children: [{path: '/doex',iconCls: 'el-icon-date',name: '身体记录',component: Doex}]}]})

utils/api.js:调用后端方法并接受返回值

关于两种不同的Content-Type:

application/x-www-form-urlencoded会对参数进行编码,键值对参数用&连接,空格转换为+,有特殊符号就转换为ASCII HEX值。然后这个类型就是编码格式,也是浏览器默认的编码格式。如果是Get请求,就将参数转化成?key=value&key=value的格式接在url后面。multipart/form-data不会进行编码,使用分割线来相当于&。常用于文件等二进制,也可以用于键值对参数。application/json也经常使用。两种post接口的解读

四种常用请求含义

Get(SELECT):从服务器查询,可以在服务器通过请求的参数区分查询的方式。POST(CREATE):在服务器新建一个资源,调用insert操作。PUT(UPDATE):在服务器更新资源,调用update操作。DELETE(DELETE):从服务器删除资源,调用delete语句。

import axios from 'axios'// base意义是啥let base = '';// 这些要在component用到的时候importexport const postRequest = (url, params) => {return axios({method: 'post',url: `${base}${url}`,data: params,transformRequest: [function (data) {// 这边就是参数传递的标准表达式了let ret = ''for (let it in data) {ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'}return ret}],headers: {'Content-Type': 'application/x-www-form-urlencoded'}});}export const uploadFileRequest = (url, params) => {return axios({method: 'post',url: `${base}${url}`,data: params,headers: {'Content-Type': 'multipart/form-data'}});}export const putRequest = (url, params) => {return axios({method: 'put',url: `${base}${url}`,data: params,transformRequest: [function (data) {let ret = ''for (let it in data) {ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'}return ret}],headers: {'Content-Type': 'application/x-www-form-urlencoded'}});}export const deleteRequest = (url) => {return axios({method: 'delete',url: `${base}${url}`});}export const getRequest = (url,params) => {return axios({method: 'get',data:params,transformRequest: [function (data) {let ret = ''for (let it in data) {ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'}return ret}],headers: {'Content-Type': 'application/x-www-form-urlencoded'},url: `${base}${url}`});}

filter_utils:过滤器

在一个单独的文件中定义过滤器,使用方法是{{ 值 | 过滤器函数名}}

过滤器的定义

import Vue from 'vue'// 定义全局过滤器,在main.js中导入Vue.filter("formatDate", function formatDate(value) {var date = new Date(value);var year = date.getFullYear();var month = date.getMonth() + 1;var day = date.getDate();if (month < 10) {month = "0" + month;}if (day < 10) {day = "0" + day;}return year + "-" + month + "-" + day;});Vue.filter("formatDateTime", function formatDateTime(value) {var date = new Date(value);var year = date.getFullYear();var month = date.getMonth() + 1;var day = date.getDate();var hours = date.getHours();var minutes = date.getMinutes();if (month < 10) {month = "0" + month;}if (day < 10) {day = "0" + day;}return year + "-" + month + "-" + day + " " + hours + ":" + minutes;});

main:导入依赖

导入使用的外部组件,如ElementtUIVCharts,还有过滤器也是在这里导入的。

import Vue from 'vue'import App from './App'import router from './router'import ElementUI from 'element-ui'import 'element-ui/lib/theme-chalk/index.css'// import './styles/element-variables.scss'import 'font-awesome/css/font-awesome.min.css'// 再次导入了过滤器import './utils/filter_utils.js'import VCharts from 'v-charts'Vue.use(ElementUI)Vue.use(VCharts)Vue.config.productionTip = false;window.bus = new Vue();new Vue({el: '#app',router,template: '<App/>',components: {App}})

组件

最核心的部分,通过各种组件的组合、跳转来实现页面的展示。

Login:登录与权限验证

登录页面是Login,两个输入框绑定rules校验规则,绑定方法是el-form:rules,然后通过prop关联rules中的性质。然后登录按钮绑定了方法,传递地址为后端的spring security的/login/{username, password},执行后判断

v:onclick.nativa.prevent:“方法名”:

给vue组件绑定事件时候,必须加上native ,否则会认为监听的是来自Item组件自定义的事件但父组件想在子组件上监听自己的click的话,需要加上native修饰符,故写法就像上面这样。prevent 是用来阻止默认的 ,相当于原生的event.preventDefault()

auto-complete=“off”:

autocomplete 属性规定输入字段是否应该启用自动完成功能。自动完成允许浏览器预测对字段的输入。当用户在字段开始键入时,浏览器基于之前键入过的值,应该显示出在字段中填写的选项。

注意:autocomplete 属性适用于<form>,以及下面的<input>类型:text, search, url, telephone, email, password, datepickers, range 以及 color。

完整代码

<template><!--v-bind:rules,是绑定一些数据?答:是绑定校验规则,通过prop的方法使用rules的校验规则--><!--所有的class都可以在下面设置style--><el-form :rules="rules" class="login-container" label-position="left"label-width="0px" v-loading="loading"><h3 class="login_title">系统登录</h3><el-form-item prop="account"><!--输入用户名,关闭了自动提示。--><el-input type="text" v-model="loginForm.username" auto-complete="off" placeholder="账号"></el-input></el-form-item><!--注意这个prop,这是用来设置校验规则的,--><el-form-item prop="checkPass"><el-input type="password" v-model="loginForm.password" auto-complete="off" placeholder="密码"></el-input></el-form-item><!--这个左右对齐好像没差别--><el-checkbox class="login_remember" v-model="checked" label-position="left">记住密码</el-checkbox><el-form-item style="width: 100%"><!--v:on绑定自定义方法--><el-button type="primary" @click.native.prevent="submitClick" style="width: 100%">登录</el-button></el-form-item></el-form></template><!--调用utils/api里面的HTTP传递方法--><script>import {postRequest} from '../utils/api'import {putRequest} from '../utils/api'export default{data(){// 下面这些值会传递给上边// 这个return是要传给后端码?return {// rules:规则,校验规则rules: {// 跟上面的prop绑定的,触发器为失去焦点account: [{required: true, message: '请输入用户名', trigger: 'blur'}],checkPass: [{required: true, message: '请输入密码', trigger: 'blur'}]},// 默认被选择checked: true,// 默认文本内容,因为由v-model,所以会显示loginForm: {username: 'sang',password: '123'},loading: false}},methods: {submitClick: function () {var _this = this;// 改变参数this.loading = true;// 方法里用的axios,第一个参数是url,第二个是参数// 方法通往Spring security的权限管理postRequest('/login', {username: this.loginForm.username,password: this.loginForm.password}).then(resp=> {_this.loading = false;if (resp.status == 200) {//成功var json = resp.data;if (json.status == 'success') {_this.$router.replace({path: '/home'});} else {// 第一个参数是内容,第二个参数是标题_this.$alert('登录失败!', '失败!');}} else {//失败_this.$alert('登录失败!', '失败!');}}, resp=> {_this.loading = false;_this.$alert('找不到服务器⊙﹏⊙∥!', '失败!');});}}}</script><!--给绑定的class设置style--><style>.login-container {border-radius: 15px;background-clip: padding-box;margin: 180px auto;width: 350px;padding: 35px 35px 15px 35px;background: #fff;border: 1px solid #eaeaea;box-shadow: 0 0 25px #cac6c6;}.login_title {margin: 0px auto 40px auto;text-align: center;color: #505458;}.login_remember {margin: 0px 0px 35px 0px;text-align: left;}</style>

Home:主页面框架

登录成功就来到Home。

v-if="!item.hidden"

通过hidden来控制是否显示

slot=“title”:是系统自带的吗,找不到东西

Breadcrumb 面包屑

el-breadcrumb作用显示当前页面的路径,快速返回之前的任意页面,之前的页面用el-breadcrumb-item表示

可以控制最上面的导航,加了点东西就变成下一行了

激活判断

代码中可以根据keep-alive参数来决定子组件显示的位置

<!--根据激活状态判断在哪里显示--><keep-alive><div>{{'激活'}}</div><router-view v-if="this.$route.meta.keepAlive"></router-view></keep-alive><div>{{'未激活'}}</div><router-view v-if="!this.$route.meta.keepAlive"></router-view>

通过:index来绑定路径path

<!--但是这个怎么跳转呢?--><!--index这里绑定了path, 为啥index绑定了就能跳?是因为el-menu-item的特性?--><el-menu-item :index="item.children[0].path"><i :class="item.children[0].iconCls"></i><span slot="title">{{item.children[0].name}}</span></el-menu-item>

登录声明

通过一个钩子函数触发,当转到该页面的时候,会调用

// 钩子函数,转到的时候触发mounted: function () {this.$alert('为了确保所有的小伙伴都能看到完整的数据演示,数据库只开放了查询权限和部分字段的更新权限,其他权限都不具备,完整权限的演示需要大家在自己本地部署后,换一个正常的数据库用户后即可查看,这点请大家悉知!', '友情提示', {confirmButtonText: '确定',callback: action => {}});var _this = this;getRequest("/currentUserName").then(function (msg) {_this.currentUserName = msg.data;}, function (msg) {_this.currentUserName = '游客';});},

下拉框

通过el-dropdown来实现下拉功能,其中分割符号在el-dropdown-item中添加divided实现。

// template部分<!--下拉框,里面的都是固定操作--><el-dropdown @command="handleCommand"><span class="el-dropdown-link home_userinfo"><!--left,right还是没区别--><!--这里有的参数-->{{currentUserName}}<i class="el-icon-arrow-down el-icon--right home_userinfo"></i></span><el-dropdown-menu slot="dropdown"><!--command可以在后面做判断的时候使用,算是一个注释--><!--除了退出登录,其他功能都没有实现--><el-dropdown-item command="sysMsg">系统消息</el-dropdown-item><el-dropdown-item command="MyArticle">我的文章</el-dropdown-item><el-dropdown-item command="MyHome">个人主页</el-dropdown-item><el-dropdown-item command="logout" divided>退出登录</el-dropdown-item></el-dropdown-menu></el-dropdown>// script部分handleCommand(command){var _this = this;if (command == 'logout') {this.$confirm('注销登录吗?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(function () {getRequest("/logout")_this.currentUserName = '游客';_this.$router.replace({path: '/'});}, function () {//取消})}}},

this.$router.replace

跳转到指定URL,替换history栈中最后一个记录,点击后退会返回至上上一个页面

参考

左侧菜单中最后一个router参数的作用

是否使用vue-router的模式,启用该模式会在激活导航时以index作为path进行路由跳转。

<el-menudefault-active="0"class="el-menu-vertical-demo" style="background-color: #ECECEC" router>

完整代码

<template><el-container class="home_container"><!--头不--><el-header><div class="home_title">V部落博客管理平台</div><div class="home_userinfoContainer"><!--下拉框,里面的都是固定操作--><el-dropdown @command="handleCommand"><span class="el-dropdown-link home_userinfo"><!--left,right还是没区别--><!--这里有的参数-->{{currentUserName}}<i class="el-icon-arrow-down el-icon--right home_userinfo"></i></span><el-dropdown-menu slot="dropdown"><!--command可以在后面做判断的时候使用,算是一个注释--><!--除了退出登录,其他功能都没有实现--><el-dropdown-item command="sysMsg">系统消息</el-dropdown-item><el-dropdown-item command="MyArticle">我的文章</el-dropdown-item><el-dropdown-item command="MyHome">个人主页</el-dropdown-item><el-dropdown-item command="logout" divided>退出登录</el-dropdown-item></el-dropdown-menu></el-dropdown></div></el-header><!--主要内容--><el-container><el-aside width="200px"><!--左侧菜单--><el-menudefault-active="0"class="el-menu-vertical-demo" style="background-color: #ECECEC" router><!--上面最后的router是干嘛的?答:控制使用index来跳转路由path的--><!--对routes里面的内容遍历,但是不显示隐藏的--><!--this.$router.options.routes是如何绑定的?--><template v-for="(item,index) in this.$router.options.routes" v-if="!item.hidden"><!--如果孩子大于1,就显示子菜单--><!--对应这个if--><el-submenu :index="index+''" v-if="item.children.length>1" :key="index"><template slot="title"><!--根据自带的属性设置格式--><i :class="item.iconCls"></i><span>{{item.name}}</span></template><!--给子标签也显示出来--><!--那为啥不是对应这个if呢?因为这个是在组件的里面?--><el-menu-item v-for="child in item.children" v-if="!child.hidden" :index="child.path" :key="child.path">{{child.name}}</el-menu-item></el-submenu><!--这个else--><template v-else><!--但是这个怎么跳转呢?--><!--index这里绑定了path, 为啥index绑定了就能跳?是因为el-menu-item的特性?--><el-menu-item :index="item.children[0].path"><i :class="item.children[0].iconCls"></i><span slot="title">{{item.children[0].name}}</span></el-menu-item></template></template></el-menu></el-aside><!--内容页面--><el-container><el-main><!--面包屑,就最上面那层--><!--这个class是管图标分隔符的--><el-breadcrumb separator-class="el-icon-arrow-right"><!--path指的是路由的路径,这个跟面包屑没啥关系吧,就是基础语法--><el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item><!--如果text中的内容存在,就也会显示,否则不显示;这个也没有点击功能--><el-breadcrumb-item v-text="this.$router.currentRoute.name"></el-breadcrumb-item></el-breadcrumb><!--根据激活状态判断在哪里显示--><keep-alive><div>{{'激活'}}</div><router-view v-if="this.$route.meta.keepAlive"></router-view></keep-alive><div>{{'未激活'}}</div><router-view v-if="!this.$route.meta.keepAlive"></router-view></el-main></el-container></el-container></el-container></template><script>import {getRequest} from '../utils/api'export default{methods: {handleCommand(command){var _this = this;if (command == 'logout') {this.$confirm('注销登录吗?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(function () {getRequest("/logout")_this.currentUserName = '游客';_this.$router.replace({path: '/'});}, function () {//取消})}}},// 钩子函数,转到的时候触发mounted: function () {this.$alert('为了确保所有的小伙伴都能看到完整的数据演示,数据库只开放了查询权限和部分字段的更新权限,其他权限都不具备,完整权限的演示需要大家在自己本地部署后,换一个正常的数据库用户后即可查看,这点请大家悉知!', '友情提示', {confirmButtonText: '确定',callback: action => {}});var _this = this;getRequest("/currentUserName").then(function (msg) {_this.currentUserName = msg.data;}, function (msg) {_this.currentUserName = '游客';});},// 最后就返回了给名字data(){return {currentUserName: ''}}}</script><style>.home_container {height: 100%;position: absolute;top: 0px;left: 0px;width: 100%;}.el-header {background-color: #20a0ff;color: #333;text-align: center;display: flex;align-items: center;justify-content: space-between;}.el-aside {background-color: #ECECEC;}.el-main {background-color: #fff;color: #000;text-align: center;}.home_title {color: #fff;font-size: 22px;display: inline;}.home_userinfo {color: #fff;cursor: pointer;}.home_userinfoContainer {display: inline;margin-right: 20px;}</style>

ArticleList:显示文章列表

el-main

el-main感觉就是好看的,来将内容收到一起

建多Tage

建多个按钮用el-tabs,可以在“全部文章”、“已发表”、“草稿箱”等tag中切换。el-tabs可以给下面不同的el-tab-pane设置不同的lable(作用是显示的名字不同),然后调用组件blog_table,传递参数过去,用props接收,这个组件的作用就是根据参数来控制该显示哪些内容,本质上所有的tag的内容都在一个组件中,但是不会全部都显示,根据每个tag设置的参数来控制显示内容,就达到了不同的效果

关于el-tabs的点击响应,这里应是一种自动配置的方法,框架已经帮做好了:

// template中<el-tabs v-model="activeName" @tab-click="handleClick" type="card">// script中methods: {// 这个啥也不做吗// 可能是自动做了handleClick(tab, event) {// console.log(tab, event);}

完整代码:

<template><el-container class="article_list"><!--感觉main的作用就是分的更开了,格式好看了--><el-main class="main"><!--点击了总的有个响应的地方吧,响应呢?--><!--答:使用了组件,blog_table和blog_cfg--><el-tabs v-model="activeName" @tab-click="handleClick" type="card"><!--根据绑定的方法,点击后自动把label传给activateName--><el-tab-pane label="全部文章" name="all"><!--使用组件的同时传递参数--><blog_table state="-1" :showEdit="false" :showDelete="false" :showRestore="false" :activeName="activeName"></blog_table></el-tab-pane><el-tab-pane label="已发表" name="post"><blog_table state="1" :showEdit="true" :showDelete="true" :showRestore="false" :activeName="activeName"></blog_table></el-tab-pane><el-tab-pane label="草稿箱" name="draft"><blog_table state="0" :showEdit="true" :showDelete="true" :showRestore="false" :activeName="activeName"></blog_table></el-tab-pane><el-tab-pane label="回收站" name="dustbin"><blog_table state="2" :showEdit="false" :showDelete="true" :showRestore="true" :activeName="activeName"></blog_table></el-tab-pane><el-tab-pane label="博客管理" name="blogmana" v-if="isAdmin"><blog_table state="-2" :showEdit="false" :showDelete="true" :showRestore="false" :activeName="activeName"></blog_table></el-tab-pane><el-tab-pane label="博客配置" name="blogcfg"><blog_cfg></blog_cfg></el-tab-pane></el-tabs></el-main></el-container></template><script>import BlogTable from '@/components/BlogTable'import BlogCfg from '@/components/BlogCfg'import {postRequest} from '../utils/api'import {putRequest} from '../utils/api'import {deleteRequest} from '../utils/api'import {getRequest} from '../utils/api'export default {mounted: function () {var _this = this;getRequest("/isAdmin").then(resp=> {if (resp.status == 200) {_this.isAdmin = resp.data;}})},data() {return {// 初始化标签activeName: 'post',isAdmin: false};},methods: {// 这个啥也不做吗// 可能是自动做了handleClick(tab, event) {// console.log(tab, event);}},components: {'blog_table': BlogTable,'blog_cfg': BlogCfg}};</script><style>.article_list > .header {background-color: #ececec;margin-top: 10px;padding-left: 5px;display: flex;justify-content: flex-start;}.article_list > .main {/*justify-content: flex-start;*/display: flex;flex-direction: column;padding-left: 0px;background-color: #fff;padding-top: 0px;margin-top: 8px;}</style>

BlogTable:具体文章列表

模糊搜索

比较常规的操作

// template部分<div style="display: flex;justify-content: flex-start"><!--搜索输入框,还设置了图标,这里应该是要用到数据库了吧--><!--确实用到了,主要是keywords生效--><el-inputplaceholder="通过标题搜索该分类下的博客..."prefix-icon="el-icon-search"v-model="keywords" style="width: 400px" size="mini"></el-input><!--设置图标挺秀的--><el-button type="primary" icon="el-icon-search" size="mini" style="margin-left: 3px" @click="searchClick">搜索</el-button></div>// script部分searchClick(){this.loadBlogs(1, this.pageSize);}loadBlogs(page, count){var _this = this;var url = '';if (this.state == -2) {url = "/admin/article/all" + "?page=" + page + "&count=" + count + "&keywords=" + this.keywords;} else {url = "/article/all?state=" + this.state + "&page=" + page + "&count=" + count + "&keywords=" + this.keywords;}getRequest(url).then(resp=> {_this.loading = false;if (resp.status == 200) {_this.articles = resp.data.articles;_this.totalCount = resp.data.totalCount;} else {_this.$message({type: 'error', message: '数据加载失败!'});}}, resp=> {_this.loading = false;if (resp.response.status == 403) {_this.$message({type: 'error', message: resp.response.data});} else {_this.$message({type: 'error', message: '数据加载失败!'});}}).catch(resp=> {//try catch?//压根没见到服务器_this.loading = false;_this.$message({type: 'error', message: '数据加载失败!'});})}

建表

<el-tableref="multipleTable":data="articles"tooltip-effect="dark"style="width: 100%;overflow-x: hidden; overflow-y: hidden;"max-height="390"@selection-change="handleSelectionChange" v-loading="loading">

多table的操作是建一个<el-table>, 然后指定ref="multipleTable",在夹层中el-table中添加一些el-table-column,这些就是表的列,即每个元素的通用属性。要注意有一个el-table-column是用来作为选择框的,不需要有什么内容但要设置type="selection",如下图所示:

<el-table-columntype="selection"width="35" align="left" v-if="showEdit || showDelete"></el-table-column>

然后其他的列可以用lable来设置列名,还可以绑定点击触发事件。

<el-table-columnlabel="标题"width="400" align="left"><!--这里scope.row,应该就是数据库数据了,来自:data="article"--><template slot-scope="scope"><span style="color: #409eff;cursor: pointer" @click="itemClick(scope.row)">{{scope.row.title}}</span></template></el-table-column>

多选框

在建表的el-table中添加@selection-change="handleSelectionChange"属性,这个可以获取到选择的id的变化,并生成一个数组送到方法handleSelectionChange

// template部分<el-tableref="multipleTable":data="articles"tooltip-effect="dark"style="width: 100%;overflow-x: hidden; overflow-y: hidden;"max-height="390"@selection-change="handleSelectionChange" v-loading="loading">// script部分handleSelectionChange(val) {// 可获取多选的内容this.selItems = val;// console.log(val);},

时间与过滤器

编辑时间什么的都用过滤器过一下,vue的过滤格式就是{{ 值 | 过滤器函数名}}

<el-table-columnlabel="最近编辑时间" width="140" align="left"><template slot-scope="scope">{{scope.row.editTime | formatDateTime}}</template></el-table-column>

点击查看详细内容

会跳到blogDetail页面

// template部分<el-table-columnlabel="标题"width="400" align="left"><!--这里scope.row,应该就是数据库数据了,来自:data="article"--><template slot-scope="scope"><span style="color: #409eff;cursor: pointer" @click="itemClick(scope.row)">{{scope.row.title}}</span></template></el-table-column>// script部分itemClick(row){// row.id为什么是数据库里对应的id?// 答:因为row这东西就是数据库来的this.$router.push({path: '/blogDetail', query: {aid: row.id}})},

这个slot-scope="scope"我感觉也是自带的,然后点击触发事件。其中scope.row就是表单这一行的属性,在这里就是一条数据库的完整对象(应是来自:data="articles"),所以可以使用数据库有的属性,如idtitle

scope.$index→拿到每一行的indexscope.$row→拿到每一行的数据。

批量删除

之前把选中的id都存放到了selItems对象中,可以根据能不能显示、选没选中来控制按钮的展示。

这里是把id都放入了dustbinData数组中,删除的时候就把这个数组传到MySQL中做foreach删除,删除完了再把dustbinData数组清空。

// template部分<!--批量删除,文章数不为0可显示,选中的元素为0时不可选,点击后调用deleteMany方法--><el-button type="danger" size="mini" style="margin: 0px;" v-show="this.articles.length>0 && showDelete":disabled="this.selItems.length==0" @click="deleteMany">批量删除</el-button>// script部分deleteMany(){var selItems = this.selItems;for (var i = 0; i < selItems.length; i++) {this.dustbinData.push(selItems[i].id)}// 随便取一个state指定就行,因为是在一个页面选择的,所以state都相同this.deleteToDustBin(selItems[0].state)},

分页功能

翻完了要重新加载一次

// tmeplate部分<!--分页功能--><!--layout中的内容用逗号隔开,内容会依次显示--><!--background可以显示背景颜色--><!--v-show指定展示条件--><!--current-change:页面改动时会触发,这里是重新加载了数据--><el-paginationbackground:page-size="pageSize"layout="prev, pager, next":total="totalCount" @current-change="currentChange" v-show="this.articles.length>0"></el-pagination>// script部分//翻页currentChange(currentPage){this.currentPage = currentPage;this.loading = true;this.loadBlogs(currentPage, this.pageSize);},loadBlogs(page, count){var _this = this;var url = '';if (this.state == -2) {url = "/admin/article/all" + "?page=" + page + "&count=" + count + "&keywords=" + this.keywords;} else {url = "/article/all?state=" + this.state + "&page=" + page + "&count=" + count + "&keywords=" + this.keywords;}getRequest(url).then(resp=> {_this.loading = false;if (resp.status == 200) {_this.articles = resp.data.articles;_this.totalCount = resp.data.totalCount;} else {_this.$message({type: 'error', message: '数据加载失败!'});}}, resp=> {_this.loading = false;if (resp.response.status == 403) {_this.$message({type: 'error', message: resp.response.data});} else {_this.$message({type: 'error', message: '数据加载失败!'});}}).catch(resp=> {//try catch?//压根没见到服务器_this.loading = false;_this.$message({type: 'error', message: '数据加载失败!'});})},

编辑

按钮与方法,参数还传入了scope.$index,主要还是跳转同时传递参数,参数是通过钩子函数获取的(this.$route.query.变量)。

// template部分<el-buttonsize="mini"@click="handleEdit(scope.$index, scope.row)" v-if="showEdit">编辑</el-button>// scripte部分handleEdit(index, row) {this.$router.push({path: '/editBlog', query: {from: this.activeName,id:row.id}});},

删除

按钮与方法,删除的时候会使用再次确定功能this.$confirm根据state判断是送到回收站还是彻底删除,会走不同的url地址,删除完了dustbinData会清零。

// template部分<el-buttonsize="mini"type="danger"@click="handleDelete(scope.$index, scope.row)" v-if="showDelete">删除</el-button>// script部分deleteToDustBin(state){var _this = this;// state=2是管理页面,删了后进入回收站;否则的话就是回收站了,回收站的删除是永久删除this.$confirm(state != 2 ? '将该文件放入回收站,是否继续?' : '永久删除该文件, 是否继续?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => {_this.loading = true;var url = '';if (_this.state == -2) {url = "/admin/article/dustbin";} else {url = "/article/dustbin";}// dustbinData是这次要删除的,删完了就清零putRequest(url, {aids: _this.dustbinData, state: state}).then(resp=> {if (resp.status == 200) {var data = resp.data;_this.$message({type: data.status, message: data.msg});if (data.status == 'success') {window.bus.$emit('blogTableReload')//通过选项卡都重新加载数据}} else {_this.$message({type: 'error', message: '删除失败!'});}_this.loading = false;_this.dustbinData = []}, resp=> {_this.loading = false;_this.$message({type: 'error', message: '删除失败!'});_this.dustbinData = []});}).catch(() => {_this.$message({type: 'info',message: '已取消删除'});_this.dustbinData = []});}

还原

从回收站还原,然后也有一个再次确定的验证,和删除差不多,都是state的改变,完了后重新加载一下。

// template部分<el-buttonsize="mini"@click="handleRestore(scope.$index, scope.row)" v-if="showRestore">还原</el-button>// script部分handleRestore(index, row) {let _this = this;this.$confirm('将该文件还原到原处,是否继续?','提示',{confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'} ).then(() => {_this.loading = true;// id参数传给后端putRequest('/article/restore', {articleId: row.id}).then(resp=> {if (resp.status == 200) {// 获取后端信息,实际上是获取了后端返回的RespBean类var data = resp.data;// 出现信息框,表示状态_this.$message({type: data.status, message: data.msg});if (data.status == 'success') {// 出发事件,方法在钩子中定义了window.bus.$emit('blogTableReload')//通过选项卡都重新加载数据}} else {_this.$message({type: 'error', message: '还原失败!'});}_this.loading = false;});}).catch(() => {_this.$message({type: 'info',message: '已取消还原'});});},

完整代码

<style type="text/css">.blog_table_footer {display: flex;box-sizing: content-box;padding-top: 10px;padding-bottom: 0px;margin-bottom: 0px;justify-content: space-between;}</style><template><div><div style="display: flex;justify-content: flex-start"><!--搜索输入框,还设置了图标,这里应该是要用到数据库了吧--><!--确实用到了,主要是keywords生效--><el-inputplaceholder="通过标题搜索该分类下的博客..."prefix-icon="el-icon-search"v-model="keywords" style="width: 400px" size="mini"></el-input><!--设置图标挺秀的--><el-button type="primary" icon="el-icon-search" size="mini" style="margin-left: 3px" @click="searchClick">搜索</el-button></div><!--<div style="width: 100%;height: 1px;background-color: #20a0ff;margin-top: 8px;margin-bottom: 0px"></div>--><!--这个表还蛮复杂的--><el-tableref="multipleTable":data="articles"tooltip-effect="dark"style="width: 100%;overflow-x: hidden; overflow-y: hidden;"max-height="390"@selection-change="handleSelectionChange" v-loading="loading"><el-table-columntype="selection"width="35" align="left" v-if="showEdit || showDelete"></el-table-column><el-table-columnlabel="标题"width="400" align="left"><!--这里scope.row,应该就是数据库数据了,来自:data="article"--><template slot-scope="scope"><span style="color: #409eff;cursor: pointer" @click="itemClick(scope.row)">{{scope.row.title}}</span></template></el-table-column><el-table-columnlabel="最近编辑时间" width="140" align="left"><template slot-scope="scope">{{scope.row.editTime | formatDateTime}}</template></el-table-column><el-table-columnprop="nickname"label="作者"width="120" align="left"></el-table-column><el-table-columnprop="cateName"label="所属分类"width="120" align="left"></el-table-column><el-table-column label="操作" align="left" v-if="showEdit || showDelete"><template slot-scope="scope"><!--调用了下面的方法, 参数是用来指定自身的吧--><el-buttonsize="mini"@click="handleEdit(scope.$index, scope.row)" v-if="showEdit">编辑</el-button><el-buttonsize="mini"@click="handleRestore(scope.$index, scope.row)" v-if="showRestore">还原</el-button><el-buttonsize="mini"type="danger"@click="handleDelete(scope.$index, scope.row)" v-if="showDelete">删除</el-button></template></el-table-column></el-table><!--这个class指定了左对齐--><div class="blog_table_footer"><!--批量删除,文章数不为0可显示,选中的元素为0时不可选,点击后调用deleteMany方法--><el-button type="danger" size="mini" style="margin: 0px;" v-show="this.articles.length>0 && showDelete":disabled="this.selItems.length==0" @click="deleteMany">批量删除</el-button><span>{{'底部,啥也没写'}}</span><!--分页功能--><!--layout中的内容用逗号隔开,内容会依次显示--><!--background可以显示背景颜色--><!--v-show指定展示条件--><!--current-change:页面改动时会触发,这里是重新加载了数据--><el-paginationbackground:page-size="pageSize"layout="prev, pager, next":total="totalCount" @current-change="currentChange" v-show="this.articles.length>0"></el-pagination></div></div></template><script>import {putRequest} from '../utils/api'import {getRequest} from '../utils/api'// import Vue from 'vue'// var bus = new Vue()export default{data() {return {articles: [],selItems: [],loading: false,currentPage: 1,totalCount: -1,pageSize: 6,keywords: '',dustbinData: []}},mounted: function () {var _this = this;this.loading = true;this.loadBlogs(1, this.pageSize);var _this = this;window.bus.$on('blogTableReload', function () {_this.loading = true;_this.loadBlogs(_this.currentPage, _this.pageSize);})},methods: {searchClick(){this.loadBlogs(1, this.pageSize);},itemClick(row){// row.id为什么是数据库里对应的id?// 答:因为row这东西就是数据库来的this.$router.push({path: '/blogDetail', query: {aid: row.id}})},deleteMany(){var selItems = this.selItems;for (var i = 0; i < selItems.length; i++) {this.dustbinData.push(selItems[i].id)}// 随便取一个state指定就行,因为是在一个页面选择的,所以state都相同this.deleteToDustBin(selItems[0].state)},//翻页currentChange(currentPage){this.currentPage = currentPage;this.loading = true;this.loadBlogs(currentPage, this.pageSize);},loadBlogs(page, count){var _this = this;var url = '';if (this.state == -2) {url = "/admin/article/all" + "?page=" + page + "&count=" + count + "&keywords=" + this.keywords;} else {url = "/article/all?state=" + this.state + "&page=" + page + "&count=" + count + "&keywords=" + this.keywords;}getRequest(url).then(resp=> {_this.loading = false;if (resp.status == 200) {_this.articles = resp.data.articles;_this.totalCount = resp.data.totalCount;} else {_this.$message({type: 'error', message: '数据加载失败!'});}}, resp=> {_this.loading = false;if (resp.response.status == 403) {_this.$message({type: 'error', message: resp.response.data});} else {_this.$message({type: 'error', message: '数据加载失败!'});}}).catch(resp=> {//try catch?//压根没见到服务器_this.loading = false;_this.$message({type: 'error', message: '数据加载失败!'});})},handleSelectionChange(val) {// 可获取多选的内容this.selItems = val;// console.log(val);},handleEdit(index, row) {this.$router.push({path: '/editBlog', query: {from: this.activeName,id:row.id}});},handleDelete(index, row) {// 回收站+idthis.dustbinData.push(row.id);this.deleteToDustBin(row.state);},handleRestore(index, row) {let _this = this;this.$confirm('将该文件还原到原处,是否继续?','提示',{confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'} ).then(() => {_this.loading = true;// id参数传给后端putRequest('/article/restore', {articleId: row.id}).then(resp=> {if (resp.status == 200) {// 获取后端信息,实际上是获取了后端返回的RespBean类var data = resp.data;// 出现信息框,表示状态_this.$message({type: data.status, message: data.msg});if (data.status == 'success') {// 出发事件,方法在钩子中定义了window.bus.$emit('blogTableReload')//通过选项卡都重新加载数据}} else {_this.$message({type: 'error', message: '还原失败!'});}_this.loading = false;});}).catch(() => {_this.$message({type: 'info',message: '已取消还原'});});},deleteToDustBin(state){var _this = this;// state=2是管理页面,删了后进入回收站;否则的话就是回收站了,回收站的删除是永久删除this.$confirm(state != 2 ? '将该文件放入回收站,是否继续?' : '永久删除该文件, 是否继续?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => {_this.loading = true;var url = '';if (_this.state == -2) {url = "/admin/article/dustbin";} else {url = "/article/dustbin";}// dustbinData是这次要删除的,删完了就清零putRequest(url, {aids: _this.dustbinData, state: state}).then(resp=> {if (resp.status == 200) {var data = resp.data;_this.$message({type: data.status, message: data.msg});if (data.status == 'success') {window.bus.$emit('blogTableReload')//通过选项卡都重新加载数据}} else {_this.$message({type: 'error', message: '删除失败!'});}_this.loading = false;_this.dustbinData = []}, resp=> {_this.loading = false;_this.$message({type: 'error', message: '删除失败!'});_this.dustbinData = []});}).catch(() => {_this.$message({type: 'info',message: '已取消删除'});_this.dustbinData = []});}},props: ['state', 'showEdit', 'showDelete', 'activeName', 'showRestore']}</script>

BlogDetail:文章显示

返回

向后跳转一个页面,通过this.$router.go(参数)方法实现的,参数为正则向前,为负则向后,数值决定跳几步

// template部分<!--向后跳转一个页面--><el-button type="text" icon="el-icon-back" @click="goBack" style="padding-bottom: 0px;">返回</el-button>// script部分goBack(){// 正为向后跳转,负为向后跳转this.$router.go(-1);}

浏览量

这个功能是在后端完成的,当点击该文章的时候,钩子函数会把该文章的id传送给后端,显示文章的同时浏览量+1。同时也注意这里的钩子函数是如何获取前面的query参数的。

// template部分<span style="color: #20a0ff;margin-right:20px;font-size: 12px;">浏览 {{article.pageView==null?0:article.pageView}}</span>// script部分mounted: function () {var aid = this.$route.query.aid;// 这个an变量没见到呀?this.activeName = this.$route.query.an;console.log(aid);// 确实不存在console.log(this.activeName);var _this = this;this.loading = true;getRequest("/article/" + aid).then(resp=> {if (resp.status == 200) {_this.article = resp.data;}_this.loading = false;}, resp=> {_this.loading = false;_this.$message({type: 'error', message: '页面加载失败!'});});},

Tag

使用el-tag来完成,对所有的tags做遍历展示

<!--添加了tag,还挺好看--><el-tag type="success" v-for="(item,index) in article.tags" :key="index" size="small"style="margin-left: 8px">{{item.tagName}}</el-tag>

内容展示

直接调用数据库显示即可,注意html格式的内容用v-html获取

<el-col><!--内容, 应也是来自数据库属性, 名字就是htmlContent--><div style="text-align: left" v-html="article.htmlContent"></div></el-col>

完整代码

<template><!--整个一大行?--><el-row v-loading="loading"><el-col :span="24"><div style="text-align: left;"><!--向后跳转一个页面--><el-button type="text" icon="el-icon-back" @click="goBack" style="padding-bottom: 0px;">返回</el-button></div></el-col><el-col :span="24"><div><div><h3 style="margin-top: 0px;margin-bottom: 0px">{{article.title}}</h3></div><div style="width: 100%;margin-top: 5px;display: flex;justify-content: flex-end;align-items: center"><div style="display: inline; color: #20a0ff;margin-left: 50px;margin-right:20px;font-size: 12px;">{{article.nickname}}</div><!--或许pageView是在后端++的--><!--答:没错,就是在后端加的,直接在数据库里改的--><span style="color: #20a0ff;margin-right:20px;font-size: 12px;">浏览 {{article.pageView==null?0:article.pageView}}</span><span style="color: #20a0ff;margin-right:20px;font-size: 12px;"> {{article.editTime | formatDateTime}}</span><!--添加了tag,还挺好看--><el-tag type="success" v-for="(item,index) in article.tags" :key="index" size="small"style="margin-left: 8px">{{item.tagName}}</el-tag><span style="margin:0px 50px 0px 0px"></span></div></div></el-col><el-col><!--内容, 应也是来自数据库属性, 名字就是htmlContent--><div style="text-align: left" v-html="article.htmlContent"></div></el-col></el-row></template><script>import {getRequest} from '../utils/api'export default{methods: {goBack(){// 正为向后跳转,负为向后跳转this.$router.go(-1);}},mounted: function () {var aid = this.$route.query.aid;// 这个an变量没见到呀?this.activeName = this.$route.query.an;console.log(aid);// 确实不存在console.log(this.activeName);var _this = this;this.loading = true;getRequest("/article/" + aid).then(resp=> {if (resp.status == 200) {_this.article = resp.data;}_this.loading = false;}, resp=> {_this.loading = false;_this.$message({type: 'error', message: '页面加载失败!'});});},data(){return {article: {},loading: false,activeName: ''}}}</script>

PostArticle:编辑与发表

el-header:

使用el-header将栏目选择、标题、tag三个包裹到一起,不然的话就会变得松散,而且和el-main混到了一行,如下图:

选择栏目

使用el-select完成,在el-option中做遍历,最终影响的是article.cid的值。

<!--从数据库获取栏目对应id信息,显示还得看label--><el-select v-model="article.cid" placeholder="请选择文章栏目" style="width: 150px;"><el-optionv-for="item in categories":key="item.id":label="item.cateName":value="item.id"></el-option></el-select>

标题输入

基本操作。

<el-input v-model="article.title" placeholder="请输入标题..." style="width: 400px;margin-left: 10px"></el-input>

显示tag

虽然这块显示只有标签本身和添加标签按钮,但是实现由三部分组成,依次是显示、输入、按钮,其中输入与按钮是不能同时存在的,因为点击了按钮就会变成输入,输入完了就会变成按钮,所以此处用来v-ifv-else实现。el-tag的显示是个简单的v-for方法,同时具有closable属性,就是点击叉号可删除该标签,还可以用:disable-transitions来设置渐变动画

// template部分<!--已经添加的先在前面显示--><!--closable是可移除标签--><!--:disable-transitions="false" 开启渐变动画(是一个翻转的效果)--><!--调用handleClose移除标签--><el-tag:key="tag"v-for="tag in article.dynamicTags"closable:disable-transitions="false"@close="handleClose(tag)" style="margin-left: 10px">{{tag}}</el-tag>// script部分handleClose(tag) {this.article.dynamicTags.splice(this.article.dynamicTags.indexOf(tag), 1);},

输入与按钮

@keyup.enter.native@blur就是回车和失去焦点都会触发。

// template部分<!--这个也在前面,是点击了button之后才展示--><!--可以通过回车键确定,也可以失去焦点确定--><el-inputclass="input-new-tag"v-if="tagInputVisible"v-model="tagValue"ref="saveTagInput"size="small"@keyup.enter.native="handleInputConfirm"@blur="handleInputConfirm"></el-input><!--这一套都是固定的--><el-button v-else class="button-new-tag" type="primary" size="small" @click="showInput">+Tag</el-button>// script部分showInput() {this.tagInputVisible = true;this.$nextTick(_ => {this.$refs.saveTagInput.$refs.input.focus();});},handleInputConfirm() {let tagValue = this.tagValue;if (tagValue) {this.article.dynamicTags.push(tagValue);}this.tagInputVisible = false;this.tagValue = '';}

markdown输入

这里是调用的库mavon-editor,但是图像上传和删除是自己写的方法。

// template部分<div id="editor"><!--用的库,但是图像的两个方法是自己定义的--><mavon-editor style="height: 100%;width: 100%;" ref=md @imgAdd="imgAdd"@imgDel="imgDel" v-model="article.mdContent"></mavon-editor></div>// script部分imgAdd(pos, $file){var _this = this;// 第一步.将图片上传到服务器.var formdata = new FormData();formdata.append('image', $file);uploadFileRequest("/article/uploadimg", formdata).then(resp=> {var json = resp.data;if (json.status == 'success') {// _this.$refs.md.$imgUpdateByUrl(pos, json.msg)_this.$refs.md.$imglst2Url([[pos, json.msg]])} else {_this.$message({type: json.status, message: json.msg});}});},// 删除就是啥也不做?imgDel(pos){},

撤销修改与保存发布

撤销操作还是用的起那面的this.$router.go(-1)功能,保存就是把内容插入or更新到数据库中,调用后端方法。

// template部分<!--这是修改才有的button--><el-button @click="cancelEdit" v-if="from!=undefined">放弃修改</el-button><!--如果是新的或者是草稿--><template v-if="from==undefined || from=='draft'"><el-button @click="saveBlog(0)">保存到草稿箱</el-button><el-button type="primary" @click="saveBlog(1)">发表文章</el-button></template><template v-else="from==post"><el-button type="primary" @click="saveBlog(1)">保存修改</el-button></template>// script部分cancelEdit(){this.$router.go(-1)},saveBlog(state){if (!(isNotNullORBlank(this.article.title, this.article.mdContent, this.article.cid))) {this.$message({type: 'error', message: '数据不能为空!'});return;}// 将所有的信息都重新整一遍,估计后端那边接收的是一个Article类var _this = this;_this.loading = true;postRequest("/article/", {id: _this.article.id,title: _this.article.title,mdContent: _this.article.mdContent,htmlContent: _this.$refs.md.d_render,cid: _this.article.cid,state: state,dynamicTags: _this.article.dynamicTags}).then(resp=> {_this.loading = false;if (resp.status == 200 && resp.data.status == 'success') {_this.article.id = resp.data.msg;_this.$message({type: 'success', message: state == 0 ? '保存成功!' : '发布成功!'});// if (_this.from != undefined) {window.bus.$emit('blogTableReload')// }if (state == 1) {_this.$router.replace({path: '/articleList'});}}}, resp=> {_this.loading = false;_this.$message({type: 'error', message: state == 0 ? '保存草稿失败!' : '博客发布失败!'});})},

BlogCfg:博客配置

邮箱校验

主要就是以邮箱校验功能,通过ref="emailValidateForm"实现(还是说type="email"?),其他的都是基本操作

<!--这应该是个邮箱校验,像是自己定义的?--><!--也不是,就是撞名了,这么常用的功能被定义很正常啊--><!--条件是有@有.--><el-form :model="emailValidateForm" label-position="top" ref="emailValidateForm"style="color:#20a0ff;font-size: 14px;"><el-form-itemprop="email"label="开启博客评论通知":rules="[{type: 'email', message: '邮箱格式不对哦!'}]"><el-input type="email" v-model.email="emailValidateForm.email" auto-complete="off" style="width: 300px"placeholder="请输入邮箱地址..." size="mini"></el-input><el-button type="primary" @click="submitForm('emailValidateForm')" size="mini">确定</el-button></el-form-item></el-form>

完整代码

<template><el-card style="width: 500px" v-loading="loading"><div><div style="text-align: left"><!--这应该是个邮箱校验,像是自己定义的?--><!--也不是,就是撞名了,这么常用的功能被定义很正常啊--><!--条件是有@有.--><el-form :model="emailValidateForm" label-position="top" ref="emailValidateForm"style="color:#20a0ff;font-size: 14px;"><el-form-itemprop="email"label="开启博客评论通知":rules="[{type: 'email', message: '邮箱格式不对哦!'}]"><el-input type="email" v-model.email="emailValidateForm.email" auto-complete="off" style="width: 300px"placeholder="请输入邮箱地址..." size="mini"></el-input><el-button type="primary" @click="submitForm('emailValidateForm')" size="mini">确定</el-button></el-form-item></el-form></div></div></el-card></template><script>import {getRequest} from '../utils/api'import {putRequest} from '../utils/api'export default{data(){return {emailValidateForm: {email: ''},loading: false}},mounted: function () {var _this = this;getRequest("/currentUserEmail").then(resp=> {if (resp.status == 200) {_this.emailValidateForm.email = resp.data;}});},methods: {submitForm(formName) {var _this = this;this.$refs[formName].validate((valid) => {if (valid) {_this.loading = true;putRequest("/updateUserEmail", {email: _this.emailValidateForm.email}).then(resp=> {_this.loading = false;if (resp.status == 200) {_this.$message({type: resp.data.status, message: resp.data.msg});} else {_this.$message({type: 'error', message: '开启失败!'});}}, resp=> {_this.loading = false;_this.$message({type: 'error', message: '开启失败!'});});} else {_this.$message({type: 'error', message: '邮箱格式不对哦!'})return false;}});}}}</script>

CateMana:栏目管理

到了这里大部分功能上面已经讲过了,也就删除这里有点新东西

403 (禁止) 服务器拒绝请求。503 (服务不可用) 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。

由数据库的关系图可知,栏目与文章有绑定,所以不能乱删。

deleteCate(ids){var _this = this;this.loading = true;//删除deleteRequest("/admin/category/" + ids).then(resp=> {var json = resp.data;_this.$message({type: json.status,message: json.msg});_this.refresh();}, resp=> {_this.loading = false;if (resp.response.status == 403) {_this.$message({type: 'error',message: resp.response.data});} else if (resp.response.status == 500) {_this.$message({type: 'error',message: '该栏目下尚有文章,删除失败!'});}})},

完整代码

package org.sang.controller;import org.sang.bean.Category;import org.sang.bean.RespBean;import org.sang.service.CategoryService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RestController;import java.util.List;/*** 超级管理员专属Controller*/@RestController@RequestMapping("/admin/category")public class CategoryController {@AutowiredCategoryService categoryService;// 输出到外面,所以外面get?@RequestMapping(value = "/all", method = RequestMethod.GET)public List<Category> getAllCategories() {return categoryService.getAllCategories();}@RequestMapping(value = "/{ids}", method = RequestMethod.DELETE)public RespBean deleteById(@PathVariable String ids) {boolean result = categoryService.deleteCategoryByIds(ids);if (result) {return new RespBean("success", "删除成功!");}return new RespBean("error", "删除失败!");}// 根据method匹配上了,话说为啥是Post?从外面Post进来的?@RequestMapping(value = "/", method = RequestMethod.POST)public RespBean addNewCate(Category category) {if ("".equals(category.getCateName()) || category.getCateName() == null) {return new RespBean("error", "请输入栏目名称!");}int result = categoryService.addCategory(category);if (result == 1) {return new RespBean("success", "添加成功!");}return new RespBean("error", "添加失败!");}@RequestMapping(value = "/", method = RequestMethod.PUT)public RespBean updateCate(Category category) {int i = categoryService.updateCategoryById(category);if (i == 1) {return new RespBean("success", "修改成功!");}return new RespBean("error", "修改失败!");}}

UserMana:员工显示与管理

卡片

使用el-car实现,因为有很多卡片,所以还是要用到v-for,具体要展示的内容在夹层中显示

禁用按钮

通过el-switch实现,可以通过active-textinactive-text来设置激活与未激活时显示的文字,调用了个方法,看不懂有啥用

// template部分<!--开关,这样activate-text的值是什么样的?--><!--应该就是true or false,因为user.enabled就是true or false--><el-switchv-model="user.enabled"active-text="启用"active-color="#13ce66"@change="enabledChange(user.enabled,user.id,index)"inactive-text="禁用" style="font-size: 12px"></el-switch>// script部分enabledChange(enabled, id, index){var _this = this;_this.cardloading.splice(index, 1, true)putRequest("/admin/user/enabled", {enabled: enabled, uid: id}).then(resp=> {if (resp.status != 200) {_this.$message({type: 'error', message: '更新失败!'})_this.loadOneUserById(id, index);return;}_this.cardloading.splice(index, 1, false)_this.$message({type: 'success', message: '更新成功!'})}, resp=> {_this.$message({type: 'error', message: '更新失败!'})_this.loadOneUserById(id, index);});},loadOneUserById(id, index){var _this = this;getRequest("/admin/user/" + id).then(resp=> {_this.cardloading.splice(index, 1, false)if (resp.status == 200) {_this.users.splice(index, 1, resp.data);} else {_this.$message({type: 'error', message: '数据加载失败!'});}}, resp=> {_this.cardloading.splice(index, 1, false)if (resp.response.status == 403) {var data = resp.response.data;_this.$message({type: 'error', message: data});}});},

角色管理

el-popover为弹出框,即左边的箭头点击触发,其中的placement="right"控制弹出的方向,trigger属性用于设置何时触发 Popover,支持四种触发方式:hoverclickfocusmanual;使用具名插槽slot="reference"来激活popover,即代码中的el-button部分,对应上图左边按钮。可在el-popover中嵌套内容,如展示列表、选择,这里嵌入了el-select,可修改roles的值,所以会影响前面tag的显示,按右边箭头可出现选择,这个选择也自带删除功能。使用v-loading在接口为请求到数据之前,显示加载中,直到请求到数据后消失,这里eploading[index]里面都是trueorfalse。代码中有两个函数,saveRolesshowRoleshowRole函数的作用是从数据库读取userroles,然后赋值给vue类的变量this.roles,用这个变量来展示并获取多选的变化,然后saveRolesthis.roles替换数据库中userroles。之所以不像el-tag一样直接使用user.roles,是因为这一步是要变化的,前端不能直接修改数据库内容,要依靠后端完成。

// template部分<el-tagv-for="role in user.roles":key="role.id"size="mini"style="margin-right: 8px"type="success">{{role.name}}</el-tag><!--placement确定展示方向--><el-popoverplacement="right"title="角色列表"width="200":key="index+''+user.id"@hide="saveRoles(user.id,index)"trigger="click" v-loading="eploading[index]"><!--trigger是触发器设置--><!--这里嵌套了一个选择器,是在<></>中夹着的--><!--roles是一个数组,multiple决定了多选,多选的结果保存到roles--><!--但是显示和删除是怎么做到的?答:这个选择自带的--><el-select v-model="roles" :key="user.id" multiple placeholder="请选择" size="mini"><el-optionv-for="(item,index) in allRoles":key="user.id+'-'+item.id":label="item.name":value="item.id"></el-option></el-select><!--点击这个button会出现框--><el-button type="text" icon="el-icon-more" style="padding-top: 0px" slot="reference"@click="showRole(user.roles,user.id,index)"></el-button></el-popover>// script部分saveRoles(id, index){// 获取变化的roles来改变数据库中user的rolesvar selRoles = this.roles;if (this.cpRoles.length == selRoles.length) {for (var i = 0; i < this.cpRoles.length; i++) {for (var j = 0; j < selRoles.length; j++) {if (this.cpRoles[i].id == selRoles[j]) {selRoles.splice(j, 1);break;}}}if (selRoles.length == 0) {return;}}var _this = this;_this.cardloading.splice(index, 1, true)putRequest("/admin/user/role", {rids: this.roles, id: id}).then(resp=> {if (resp.status == 200 && resp.data.status == 'success') {_this.$message({type: resp.data.status, message: resp.data.msg});_this.loadOneUserById(id, index);} else {_this.cardloading.splice(index, 1, false)_this.$message({type: 'error', message: '更新失败!'});}}, resp=> {_this.cardloading.splice(index, 1, false)if (resp.response.status == 403) {var data = resp.response.data;_this.$message({type: 'error', message: data});}});},showRole(aRoles, id, index){this.cpRoles = aRoles;this.roles = [];// 获取数据库中rolesthis.loadRoles(index);for (var i = 0; i < aRoles.length; i++) {this.roles.push(aRoles[i].id);}},

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。