已转至个人博客-https://www.aerowang.cn/articles/qnw6i1s6
此项目来自B站 Vue实战项目:电商管理系统(Element-UI)。
此项目的功能与后端提供的接口并不完全相同,根据后端接口进行开发并添加了部分功能从而进行修改。
在完成此项目并测试上线后,在这里记录一下项目的开发思路,以及遇到的一些问题。
自我感觉这个项目是一个很不错的关于Vue+Element UI 的练手项目
先放一些效果图
项目上线地址:
https://www.aerowang.cn/vue_shop
电商后台管理系统用于管理用户账号、角色管理、权限分配、商品分类、商品信息、订单、数据统计等业务功能
该管理系统整体采用前后端分离的开发模式,其中前端项目是基于 Vue 技术栈的简单的单页应用(SPA)项目。
后端直接操作数据库,通过api接口将数据返回给前端项目。
前端负责构建用户界面并通过ajax等技术调用后端提供的接口获得数据。
前端项目技术栈
后端项目技术栈
Node.js
Express
Jwt
JSON Web Token(Jwt)定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
MySql
Sequelize
Sequelize.js 提供对 MySQL,MariaDB,SQLite 和 PostgreSQL 数据库的简单访问,通过映射数据库条目到对象,或者对象到数据库条目。简而言之,就是 ORM(Object-Relational-Mapper)。Sequelize.js 完全是使用 JavaScript 编写,适用于 Node.js 的环境
安装 Vue-cli
通过 Vue-cli 创建项目
配置 Vue 路由
配置 Element-UI 组件库(这里为了学习,使用的是按需导入的方式)
配置 axios 库
初始化 Git 远程仓库,将本地项目托管到 gitee 仓库中
这里通过vue ui
进行可视化创建项目,并默认使用 vue-router 默认的 hash 模式路由
安装Element UI插件(vue-cli-plugin-element)及安装运行依赖 axios 库
部署环境
在这里需要根据数据库实际用户名及密码修改后端接口中关于数据库的配置。
环境安装完之后,就是安装后端接口所需要的依赖包(npm install
),最后将其在本机运行起来。
接口API通过postman测是通过
不过我考虑到在后续的项目中,每次打开项目进行开发时都要重新运行起后端api服务器,略微麻烦,于是将该后端服务器部署到了云端服务器上,因此也遇到了一些问题(问题暂且不提)
这里每新建一个组件模块都要在路由配置文件router.js中进行配置,当然,嵌套路由肯定是要用到的。将组件 (components) 映射到路由 (routes),然后告诉 Vue Router 在哪里渲染它们
总的来说,首先是将基本页面布局通过Element-UI进行实现,之后向后端服务器发起请求,获取数据,将获取到的数据保存至每个组件中的data
中。methods
方法区中实现各模块所需要的方法。
这里需要注意的就是要注意后端返回的数据是什么样的格式,与前端需要的数据格式是否一致,不一致的话就要进行转换。
最后再将数据渲染到表单或者其他组件中。
以下模块就不在一一叙述这些。
这里的业务流程很简单
由于部署的后端服务器和前端项目端口不同或者IP不同,存在跨域问题,这时候就采取 token 方式维持登录状态。客户端登录发出请求,服务器端验证通过后生成该用户的 token 并返回给客户端,客户端存储该 token,后续请求都需要携带该 token 值发送请求(这里就需要在全局)。
// 配置请求的根路径
axios.defaults.baseURL = 'http://ip地址/api/private/v1/'
// 请求拦截器
axios.interceptors.request.use(config => {
// console.log(config)
config.headers.Authorization = window.sessionStorage.getItem('token')
return config
})
// 挂载Vue的原型对象上
Vue.prototype.$http = axios
注意: 这里需要配置导航守卫中的全局前置守卫router.beforeEach
来对未登录用户进行来拦截,并跳转至'/login'
页面,根据的是客户端是否有获取到服务器返回的 token 值进行判断拦截,以此决定是否重定向至'/login'
页面进行登录
如果前端和后端接口不存在跨域问题,使用cookie与session记录登录状态
该功能模块就更结合使用了Element-UI的 form 表单、Card卡片、button按钮、Dialog弹窗等组件。详情组件属性和方法当然是要查看Element-UI官网了
根据各组件提供的属性或者方法,包括用户列表数据的获取(利用async、await发起数据的请求)
分页显示实际上有三种思路,
一是后端把所有查询结果都发到前端,然后由前端进行分页显示处理;
二是后端查询后由后端出来分页,把其分好再发到前端
三是我需要时再查,每次点击上一页下一页时发送一个请求,请求包含分页的信息,由后端返回该分页的结果
这里根据后台提供接口便是第三种方法,前端接收的数据量小,反应快,用户体验好。每次点击分页发送请求传给后端查询并接受返回数据。
角色列表
这里的每一项权限可以通过表格的展开列进行展示及修改,也可以通过树形控件进行渲染
权限列表
这里的分级标签显示便是用 v-if
进行判断展示等级,其它展示数据根据后端获取并渲染到客户端就可以
这里使用了作用域插槽的形式获取 level 数据,这里我理解为 table 将获取到的数据渲染到每一行,因此每一行的数据可以通过 scope.row
的形式获取
这两个模块的开发其实和前两个没有多大的区别。
其中添加商品信息的模块中,新引进了 vue-quill-editor
富文本编辑器,可以对商品进行更详细的描述
// 导入富文本编辑器
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
这里有一个注意项,记录一下
添加商品时,商品分类级联选择器要求并记录goods_cat为数组,而发起请求的数据goods_cat为字符串
深拷贝addForm对象处理此问题,这里新引入了一个包 lodash
// lodash cloneDeep(obj)
const form = _.cloneDeep(this.addForm)
form.goods_cat = form.goods_cat.join(',')
数据统计这块,当然是引入 Echarts 了
根据 Echarts 提供的一个完整的实例,在 Vue 组件 中初始化 Echarts 实例,将准备好的 dom 区域渲染为图表。
这里注意的是:图表实在 DOM 渲染完毕后才会初始化,这就要在 mounted(){}
中初始化 echarts 实例
async mounted() {
const echarts = require('echarts')
// 基于准备好的dom,初始化echarts实例
const myChart = echarts.init(document.getElementById('main'))
const { data: res } = await this.$http.get('reports/type/1')
if (res.meta.status !== 200) {
return this.$message.error('获取折线图数据失败!')
}
// 指定图表的配置项和数据
const result = _.merge(res.data, this.options)
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(result)
}
此外,虽然已经获取到了数据,并渲染到页面之上,但是图表并不完整,还需要和以下 options 选项进行合并(利用lodash的merge函数合并对象)
需要合并的选项
options: {
title: {
text: '用户来源'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#E9EEF3'
}
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
boundaryGap: false
}
],
yAxis: [
{
type: 'value'
}
]
}
通过 vue ui
命令打开可视化面板,对项目进行打包(build) 生成项目打包报告。
优化完之后效果如下图,优化之前的资源项里有些感叹号表示文件过大,但是未截图,就这样展示吧
根据优化之前的项目打包报告可以清晰的看到哪些文件占用资源较大,哪些地方有警告或者错误
优化之前,可以看到有些第三方库是占用资源是比较大的,即使是已经通过按需导入的 Element-UI,一样占用比较大。
除此之外,还有其它包可以启用CDN优化,如加载页面的顶部进度条插件(Nprogress.js)、axios 等
在 vue.config.js 中进行配置发布阶段的配置,通过 externals
加载外部CDN资源
chainWebpack: config => {
// 发布阶段
config.when(process.env.NODE_ENV === 'production', config => {
config.entry('app').clear().add('./src/main-prod.js')
// key:vlaue 其中key便是 import 的包的名字,value 是包在项目代码中使用的别名
config.set('externals', {
vue: 'Vue',
'vue-router': 'VueRouter',
axios: 'axios',
lodash: '_',
echarts: 'echarts',
nprogress: 'NProgress',
'vue-quill-editor': 'VueQuillEditor'
})
})
}
Element-UI 的优化与此不同,需要在 ./src/main-prod.js
中注释掉Element-UI按需加载的代码,并在 index.html
的头部区域通过CDN引入 Element-UI的js和CSS样式
注意的是:外部引入CDN的版本是否与项目中依赖包的版本是否一致
此项目我是将其部署在 Nginx 之上,直接将前端页面打包完成后的文件放入网站目录中。
当然后端接口API也是部署在服务器之上。只不过这里遇到一些小小问题,前端项目中请求的API接口根路径需要配置代理.
原本是这样:但是会请求失败。因为我是通过https访问前端项目,而请求后台的根路径为 http ,请求被阻止
// 配置请求的根路径
axios.defaults.baseURL = 'http://ip地址:port/api/private/v1/'
于是就通过 Nginx 的配置文件,将请求后端API请求进行转发
Nginx
location ^~/api/ {
proxy_pass http://localhost:xxxx;
proxy_set_header Host $host:$server_port;
}
main-prod.js中
// 根据实际情况填写
axios.defaults.baseURL = 'https://xxx.cn/api/private/v1/'
这时,前端页面发送的请求根路径为 https://xxx.cn/api/private/v1/
,但是实际请求地址根路径依然为 https://xxx.cn:xxxx/api/private/v1/
https://www.aerowang.cn/vue_shop
https://gitee.com/aerowang/vue_shop