Vue后台管理系统
文章目录
- Vue后台管理系统
- 前言
- 一、项目概述
- 二、项目初始化
- 三、实现功能业务的一般流程
- 四、登录/退出功能
- 四、主页功能
- 五、其他业务功能的布局和功能实现
- 六、引入的功能组件
- 七、项目优化
- 总结
前言
一、项目概述
1.1 电商项目基本业务概述
1.2 电商后台管理系统的功能
1.3 电商后台管理系统的开发模式(前后端分离)
二、项目初始化
2.1 通过 Vue 脚手架创建项目
2.1.1 配置 Vue 路由 插件要选择Router
2.1.2 配置 Element-UI 组件库 import方式需要选择import on demand
2.1.3 配置 axios 库 添加axios依赖
2.1.4 初始化 git 远程仓库需要在github或gitee创建远程仓库
2.1.5 将本地项目托管到 Github 或 码云 中
1、在项目的根目录打开PowerShell
2、git add .
3、git commit -m "说明"
4、按照新建仓库给的连接远程仓库和提交命令继续操作,与远程仓库建立联系
2.1.6 梳理项目结构
格式化App.vue
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'app'
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin-top: 60px;
}
</style>
格式化 router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
]
const router = new VueRouter({
routes
})
/*------------实现导航守卫控制页面访问权限----------------*/
/**
param: to 将要访问到的路径
param: from 代表从哪个路径跳转而来
param:next 代表一个函数,表示放行
*/
router.beforeEach((to, from, next) => {
if(to.path === '/login') return next();
const tokenStr = sessionStorage.getItem("token");
// 判断token是否存在,如果不存在则代表未登录,则强制路由到登录页面
if(!tokenStr) return next('./login');
next();
})
格式化 main.js
/*------------------引入element-ui和其他样式--------------------*/
import './plugins/element.js'
import './assets/css/global.css'
import './assets/fonts/iconfont.css'
/*------------------封装axios和加载进度条--------------------*/
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import axios from 'axios'
Vue.prototype.$http = axios
axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
/*------------------通过axios拦截器添加token验证--------------------*/
// 文档通常会说明后台会对操作数据的api进行根据token令牌授权的操作
// 根据实际的文档常常需要在请求头添加内容是token令牌的字段 Authorization
axios.interceptors.request.use(config => {
NProgress.start();
// 添加请求头
config.headers.Authorization = window.sessionStorage.getItem('token');
return config
})
axios.interceptors.response.use(config => {
NProgress.done();
return config
})
/*------------------定义过滤器--------------------*/
Vue.filter('dateFormat', function(date) {
const dateObj = new Date(date);
const y = dateObj.getFullYear();
const m = (dateObj.getMonth() + 1 + '').padStart(2, '0');
const d = (dateObj.getDay() + '').padStart(2, '0');
const hh = (dateObj.getHours() + '').padStart(2, '0');
const MM = (dateObj.getMinutes()+ '').padStart(2, '0');
const ss = (dateObj.getSeconds() + '').padStart(2, '0');
return `${y}-${m}-${d} ${hh}:${MM}:${ss}`;
})
格式化 element.js
import Vue from 'vue'
import {
Button,
Message,
MessageBox
} from 'element-ui'
Vue.prototype.$message = Message
// 实现基于element-ui的message全局弹框组的配置
Vue.prototype.$confirm = MessageBox.confirm
2.2 后台项目的环境安装配置
2.2.1 导入数据库
1、停止本地mysql80服务
2、启动phpstudy,开启mysql的服务
3、先创建数据库,再导入sql脚本
2.2.2 修改项目的config文件修改用户和密码为自己刚刚创建的数据库的用户和密码
2.2.3 在项目根目录打开powershell运行node ./app.js
2.2.4 使用postman看看接口是否跑起来了,如果有数据返回则服务器运行成功
三、实现功能业务的一般流程
3.1 开始新业务开发,首先创建并切换到新分支 git checkout -b 新分支名
3.2 新建组件并初始化结构 template、script、style
3.3 在router/index.js创建并引入路由 {path: '路径' ,component: 组件名}
3.4 实现页面ui结构 通过原生标签和element-ui配合组成页面结构
3.5 定义数据和自封装函数来实现业务功能
3.5.1 加载页面的时候就触发获取列表数据的事件,所以应该把该事件的触发放到created周期
3.5.2 当在html页面上需要用到某些数据的时候先到data里面找,如果没有则用作用域插槽的scope.row来获取当前表格行的数据
3.6 将本地代码提交到码云上
3.6.1 把文件提交到暂存区 git add .
提交到仓库 git commit -m "说明"
提交到远程仓库的功能分支 git push -u origin 分支名
切换为主分支并合并分支 git checkout master、git merge 分支名
提交到远程仓库 git push
四、登录/退出功能
3.1 登录业务流程
3.2 技术选择
3.2.1 方案1:cookie和session记录登录状态
3.2.2 方案2:token 维持登录状态
方案 | 使用场景 |
---|---|
cookie、session | 服务器和客户端不跨域 |
token | 服务器和客户端跨域,常用 |
3.3 登录页面的实现过程
3.3.1 表单的数据绑定
3.3.2 表单的数据校验
3.3.3 表单重置
methods: {
resetLoginForm: function() {
// this指向当前组件实例,refs是存有含有ref属性的组件的对象,resetFields()方法是重置所有的表单项
this.$refs.loginFormRef.resetFields()
}
},
3.3.4 表单提交前先通过validate函数预校验再提交,登录后需要有路由指向
login: function() {
this.$refs.loginFormRef.validate(async valid => {
// 1、预校验
if(!valid) return;
const { data: res } = await this.$http.post("login",this.loginForm);
if(res.meta.status != 200) return this.$message.error('登陆失败!')
this.$message.success('登陆成功!')
// 2、将token保存到客户端的sessionStorage中
// 2.1 项目中除了登录之后的其他API接口,必须在登录之后才能访问
// 2.2 token只应在当前网站打开期间生效,所以将token保存在sessionStorage中
window.sessionStorage.setItem("token", res.data.token);
// 3、通过编程式导航到后台主页,路由地址是 /home
this.$router.push("/home");
})
}
四、主页功能
4.1 主页布局
4.2 登录后退出功能
// 在已登录放行的页面定义
methods: {
exit: function() {
// 移除token并跳转到登陆页面
window.sessionStorage.removeItem("token");
this.$router.push('/login');
}
}
4.3 v-for循环渲染菜单列表
4.4 给其他功能组件提供占位
五、其他业务功能的布局和功能实现
5.1 基于element-ui绘制用户列表的组件
5.3.1 页内导航 BreadCrumb
5.3.2 主要内容面板 Card
5.3.3 栅格系统 col、row
5.3.4 数据表格 el-table
5.3.5 分页导航 el-pagination
5.2 基于el-table渲染数据列表
5.2.1 获取用户信息列表
5.2.2 渲染数据列表
5.2.3 实现分页展示用户数据
5.3 实现增删改查功能
5.3.1 添加功能常需要弹出对话框 el-dialog
1、对话框的显隐标志visible需要用v-bind绑定而不是v-model v-bind:visible="..."
2、对话框要添加关闭事件@close="关闭事件名"
,不然点击关闭按钮会无反应
3、关闭对话框的同时需要清空表单,该操作最好添加在关闭事件上 this.$refs.表单实例对象.resetFields();
4、对话框内容常常是表单,需要遵守表单的规则
5.3.2 删除功能常需要弹出消息框 基于messageBox的confirm
方法名() {
this.$confirm('此操作将永久删除..., 是否继续?', '消息框标题', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 在这里发送删除请求
this.$message({
type: 'success',
message: '删除成功!'
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
5.3.3 修改功能也常需要弹出对话框 el-dialog
5.3.4 搜索功能 设计接口的时候把搜索所需要的信息设计为可空
六、引入的功能组件
6.1 树形表格 vue-table-with-tree-grid
6.1.1 安装 npm i vue-table-with-tree-grid -S
6.1.2 引入
import ZkTable from 'vue-table-with-tree-grid'
Vue.use(ZkTable)
Vue.component('tree-table',ZkTable)
6.1.3 使用
https://github.com/connie1992/vue-table-with-tree-grid-icon
<!-- 数据列表 -->
<tree-table :data="cateList" :columns="columns"
:selection-type="false" :expand-type="false"
:show-index="true" index-text="#" :border="true"
:stripe="true" :show-row-hover="false" class="treetable">
<!-- 是否有效列 -->
<template slot="isok" slot-scope="scope">
<i class="el-icon-success iconSuccess" v-if="scope.row.cat_deleted == false"></i>
<i class="el-icon-error iconError" v-else></i>
</template>
<!-- 排序列 -->
<template slot="sort" slot-scope="scope">
<el-tag v-if="scope.row.cat_level == 0" size="mini">一级</el-tag>
<el-tag type="success" v-else-if="scope.row.cat_level == 1" size="mini">二级</el-tag>
<el-tag type="warning" v-else size="mini">三级</el-tag>
</template>
<!-- 操作列 -->
<template slot="action" >
<el-button type="primary" icon="el-icon-edit" size="mini">编辑</el-button>
<el-button type="danger" icon="el-icon-delete" size="mini">删除</el-button>
</template>
</tree-table>
columns: [
{
label: '分类名称',
prop: 'cat_name'
},
{
label: "是否有效",
// 将当前列作为模板列
type: 'template',
template: 'isok'
},
{
label: '排序',
type: 'template',
template: 'sort'
},
{
label: '操作',
type: 'template',
template: 'action'
}
],
6.2 富文本编辑器 vue-quill-editor
6.2.1 下载 npm install vue-quill-editor -S
6.2.2 引入
import VueQuillEditor from 'vue-quill-editor'
import 'quill/dist/quill.core.css' // import styles
import 'quill/dist/quill.snow.css' // for snow theme
import 'quill/dist/quill.bubble.css' // for bubble theme
Vue.use(VueQuillEditor)
6.2.3 使用
<quill-editor v-model="addForm.goods_introduce"/>
6.3 第三方可视化库 Echarts
https://echarts.apache.org/zh/index.html
6.2.1 下载 npm install echarts -S
6.2.2 引入
import * as echarts from 'echarts';
6.2.3 使用
<div id="main" style="width: 750px;height:400px;"></div>
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('main'));
// 绘制图表
myChart.setOption({
title: {
text: 'ECharts 入门示例'
},
tooltip: {},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: {},
series: [{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}]
});
6.4 第三方工具库,主要降低 array、number、objects、string 等等的使用难度 Lodash
6.2.1 下载 npm i --save lodash
6.2.2 引入
import _ from 'lodash'
6.2.3 使用
<div id="main" style="width: 750px;height:400px;"></div>
// 深拷贝
const form = _.cloneDeep(this.addForm);
// 合并对象
const result = _.merge(res.data, this.options)
七、项目优化
7.1 项目优化策略
7.1.1 生成打包报告
1、在可视化的UI面板中,通过控制台和分析面板,可以方便地看到项目中所存在的问题
2、清除所有的console.log语句
下载包 npm install babel-plugin-transform-remove-console --save-dev或安装开发依赖
/*----------------------------------babel.comfig.js---------------------------------------*/
// 这是项目发布阶段才用的babel插件
const prodPlugins = []
if(process.env.NODE_ENV === 'production') {
// 只在发布阶段清除console语句
prodPlugins.push('transform-remove-console')
}
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
plugins: [
// 发布产品时候的插件数组
...prodPlugins,
]
}
3、为开发模式与发布模式指定不同的打包入口
① 开发模式的入口文件为 src/main-dev.js
② 发布模式的入口文件为 src/main-prod.js
vue.config.js 导出的配置对象中,新增 configureWebpack 或 chainWebpack 节点,来自定义 webpack 的打包配置
① chainWebpack 通过链式编程的形式,来修改默认的 webpack 配置
② configureWebpack 通过操作对象的形式,来修改默认的 webpack 配置
module.exports = {
// 使用chainWebpack节点
chainWebpack: config => {
config.when(process.env.NODE_ENV === 'production',config => {
// 获取app的入口文件对象,先清空再添加路径
config.entry('app').clear().add('./src/main-prod.js')
})
config.when(process.env.NODE_ENV === 'development',config => {
config.entry('app').clear().add('./src/main-dev.js')
})
}
}
7.1.2 第三方库启用 CDN
通过 import 语法导入的第三方依赖包,最终会被打包合并到同一个文件中,从而导致打包成功后,单文件体积过大的问题
module.exports = {
// 使用chainWebpack节点
chainWebpack: config => {
config.when(process.env.NODE_ENV === 'production',config => {
// 获取app的入口文件对象,先清空再添加路径
config.entry('app').clear().add('./src/main-prod.js')
// 通过 webpack 的 externals 节点,来配置并加载外部的 CDN 资源。凡是声明在externals 中的第三方依赖包,都不会被打包
config.set('externals', {
vue: 'Vue',
axios: 'axios',
lodash: '_',
echarts: 'echarts',
nprogress: 'NProgress', 'vue-quill-editor': 'VueQuillEditor'
})
})
config.when(process.env.NODE_ENV === 'development',config => {
config.entry('app').clear().add('./src/main-dev.js')
})
}
}
在main-prod.js中删除上述externals中的第三方包的css文件
删除红框部分
在public/index.html中引入样式和js文件
<link rel="stylesheet" href="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.css" />
<!-- 富文本编辑器 的样式表文件 -->
<link rel="stylesheet" href="https://cdn.staticfile.org/quill/1.3.4/quill.core.min.css" />
<link rel="stylesheet" href="https://cdn.staticfile.org/quill/1.3.4/quill.snow.min.css" />
<link rel="stylesheet" href="https://cdn.staticfile.org/quill/1.3.4/quill.bubble.min.css" />
<script src="https://cdn.staticfile.org/vue/2.5.22/vue.min.js"></script>
<script src="https://cdn.staticfile.org/axios/0.18.0/axios.min.js"></script>
<script src="https://cdn.staticfile.org/lodash.js/4.17.11/lodash.min.js"></script>
<script src="https://cdn.staticfile.org/echarts/4.1.0/echarts.min.js"></script>
<script src="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.js"></script>
<!-- 富文本编辑器的 js 文件 -->
<script src="https://cdn.staticfile.org/quill/1.3.4/quill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-quill-editor@3.0.4/dist/vue-quill-editor.js"></script>
7.1.3 Element-UI 组件按需加载
注释掉引入elementui的语句
在public/index.html中引入样式和js文件
<!-- element-ui 的样式表文件 -->
<link rel="stylesheet" href="https://cdn.staticfile.org/element-ui/2.15.1/theme-chalk/index.css" />
<!-- element-ui 的 js 文件 -->
<script src="https://cdn.staticfile.org/element-ui/2.15.1/index.js"></script>
7.1.4 首页内容定制
不同的打包环境下,首页内容可能会有所不同。我们可以通过插件的方式在进行定制
chainWebpack: config => {
config.when(process.env.NODE_ENV === 'production', config => {
config.plugin('html').tap(args => {
args[0].isProd = true
return args
})
})
config.when(process.env.NODE_ENV === 'development', config => {
config.plugin('html').tap(args => {
args[0].isProd = false
return args
})
})
}
根据 isProd 的值,来决定如何渲染页面结构
<!– 按需渲染页面的标题 -->
<title><%= htmlWebpackPlugin.options.isProd ? '' : 'dev - ' %>电商后台管理系统</title>
<!– 按需加载外部的 CDN 资源 -->
<% if(htmlWebpackPlugin.options.isProd) { %>
<!—通过 externals 加载的外部 CDN 资源-->
<% } %>
7.1.5 路由懒加载
安装开发依赖 @babel/plugin-syntax-dynamic-import
在babel.config.js 配置文件中声明该插件
将路由改为按需加载的形式
const Login = () => import(/* webpackChunkName: "login_home_welcome" */ '../components/Login.vue')
const Home = () => import(/* webpackChunkName: "login_home_welcome" */ '../components/Home.vue')
const Welcome = () => import(/* webpackChunkName: "login_home_welcome" */ '../components/Welcome.vue')
// import Login from '../components/Login.vue'
// import Home from '../components/Home.vue'
// import Welcome from '../components/Welcome.vue'
const Users = () => import(/* webpackChunkName: "Users_Rights_Roles" */ '../components/Users.vue')
const Rights = () => import(/* webpackChunkName: "Users_Rights_Roles" */ '../components/power/Rights.vue')
const Roles = () => import(/* webpackChunkName: "Users_Rights_Roles" */ '../components/power/Roles.vue')
// import Users from '../components/Users.vue'
// import Rights from '../components/power/Rights.vue'
// import Roles from '../components/power/Roles.vue'
const Cate = () => import(/* webpackChunkName: "Cates_Params_List_Add" */ '../components/goods/Cate.vue')
const Params = () => import(/* webpackChunkName: "Cates_Params_List_Add" */ '../components/goods/Params.vue')
const List = () => import(/* webpackChunkName: "Cates_Params_List_Add" */ '../components/goods/List.vue')
const Add = () => import(/* webpackChunkName: "Cates_Params_List_Add" */ '../components/goods/Add.vue')
// import Cate from '../components/goods/Cate.vue'
// import Params from '../components/goods/Params.vue'
// import List from '../components/goods/List.vue'
// import Add from '../components/goods/Add.vue'
const Order = () => import(/* webpackChunkName: "Order_Report" */ '../components/order/order.vue')
const Report = () => import(/* webpackChunkName: "Order_Report" */ '../components/report/Report.vue')
// import Order from '../components/order/order.vue'
// import Report from '../components/report/Report.vue'