笔记视频:https://www.bilibili.com/video/BV1EE411B7SU?p=1
项目分析
功能
技术栈
前端
**
- Vue
- Vue-router
- Element-UI
- Axios
- Echarts
后端
**
- Node.js
- Express
- Jwt(状态保持)
- Mysql
- Sequelize(操作数据库的框架)
项目初始化
安装 Vue 脚手架
npm install -g @vue/cli
使用 GUI 创建项目
vue ui
手动配置项目预设
在仪表盘插件选项中安装 vue-cli-plugin-element
配置插件(此时会安装 babel-plugin-component)
在仪表盘依赖选项中安装 axios(运行依赖)
在仪表盘依赖选项中安装 less-loader(开发依赖)
在仪表盘依赖选项中安装 less(开发依赖)
与远程仓库建立连接(git 三连)
git remote add github https://github.com/Tumi0321/mall-admin.git
划分目录
├─assets 资源
│ ├─css
│ ├─img
│ └─fonts
├─common 用于存放公共的js文件(变量、方法等)
├─components 用于存放公共的组件
│ ├─common 能复用的组件
│ └─content 能复用但只和内容相关的组件
├─network 网络请求
├─router 路由
└─views 主要的视图
├─category
└─home
配置别名
配置别后需要重启
// vue.config.js
module.exports = {
configureWebpack: {
resolve: {
// 配置别名
alias: {
'assets': '@/assets',
'common': '@/common',
'components': '@/components',
'network': '@/network',
'views': '@/views',
}
}
}
}
登录/退出
业务流程
1、在登录页面输入用户名和密码
2、调用后台接口进行验证
3、通过验证后根据后台响应状态跳转到项目主页
相关技术
- http 是无状态的
- 通过 cookie 在客户端记录状态(不存在跨域)
- 通过 session 在服务端记录状态(不存在跨域)
- 通过 token 方式维持状态(存在跨域)
—什么是跨域?
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域
当前页面url | 被请求页面url | 是否跨域 | 原因 |
---|---|---|---|
http://www.test.com/ | http://www.test.com/index.html | 否 | 同源(协议、域名、端口号相同) |
http://www.test.com/ | https://www.test.com/index.html | 跨域 | 协议不同(http/https) |
http://www.test.com/ | http://www.baidu.com/ | 跨域 | 主域名不同(test/baidu) |
http://www.test.com/ | http://blog.test.com/ | 跨域 | 子域名不同(www/blog) |
http://www.test.com:8080/ | http://www.test.com:7001/ | 跨域 | 端口号不同(8080/7001) |
—token 登录原理
初始化页面
—创建 git 分支
创建分支
git checkout -b login
查看所有分支
git branch
—创建组件
- 导入组件 ```javascript // plugins/element.js
import { Button, Form, FormItem, Input } from ‘element-ui’;
Vue.use(Button) Vue.use(Form) Vue.use(FormItem) Vue.use(Input)
- 设置样式
element-ui 的组件都有一个和组件名一样的类名
<a name="ExmZw"></a>
### 业务实现
**--数据绑定**
```html
/* 将数据对象绑定给表单 */
<el-form :model="loginForm">
<el-form-item>
/* v-model 双向绑定 */
<el-input v-model="loginForm.username"></el-input>
</el-form-item>
</el-form>
—表单数据验证
/* 将验证规则传入表单 */
<el-form :rules="loginFormRules">
// prop 传入需要校验的字段名
<el-form-item prop="username">
/* v-model 双向绑定 */
<el-input v-model="loginForm.username"></el-input>
</el-form-item>
</el-form>
export default {
data () {
// 表单验证规则
loginFormRules: {
// 验证用户名
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 10, message: '长度在 3 到 10 个字符', trigger: 'blur' }
],
}
}
},
}
—表单重置
/* 设置 ref 属性 */
<el-form :model="loginForm" ref="loginFormRef">
/* 监听事件 */
<el-button type="info" @click="resetLoginForm">重置</el-button>
</el-form>
resetLoginForm () {
// 使用 element 的 resetFields 事件重置表单
this.$refs.loginFormRef.resetFields();
},
—登录预验证
<el-form :model="loginForm" ref="loginFormRef">
/* 监听事件 */
<el-button type="primary" @click="login">登录</el-button>
</el-form>
login () {
// callback: boolean(是否校验成功)
// object: 校验失败的对象
this.$refs.loginFormRef.validate((callback, object) => {
})
}
—封装登录请求
**
// network/request.js
import axios from 'axios';
export function request (config) {
// 1.创建 axios 实例
const instance = axios.create({
baseURL: 'http://39.97.105.222:8888/api/private/v1/',
timeout: 50000
})
// 2.axios 拦截器
instance.interceptors.request.use(config => {
return config
}, err => {
console.log(err);
})
instance.interceptors.response.use(res => {
return res
}, err => {
console.log(err);
})
// 3.发送真正的网络请求
return instance(config)
}
—消息提示**
// element.js
import { Button, Form, FormItem, Input, Message } from 'element-ui';
// 全局挂载消息提示组件
Vue.prototype.$message = Message
login () {
this.$refs.loginFormRef.validate(async valid => {
if (!valid) return;
// 保存登录信息
const { data: res } = await getLogin(this.loginForm)
// 判断状态使用 message 组件
if (res.meta.status !== 200) return this.$message.error('登录失败!');
this.$message.success('登录成功')
})
}
—保存 token
**
sessionStorage 用于临时保存数据(关闭网页后失效)
login () {
this.$refs.loginFormRef.validate(async valid => {
if (!valid) return;
const { data: res } = await getLogin(this.loginForm)
if (res.meta.status !== 200) return this.$message.error("登录失败!");
this.$message.success("登录成功");
// 保存 token 到 sessionStorage 中
window.sessionStorage.setItem("token", res.data.token);
// 跳转到 home
this.$router.push("/home");
})
}
—退出
**
<!-- 监听点点击事件 -->
<el-button type="info" @click="logout">退出</el-button>
logout () {
// 销毁 sessionStorage
window.sessionStorage.clear();
// 跳转到 login
this.$router.push("/login");
}
推送
切换到 master 分支
git checkout master
合并 login 分支
git merge login
推送到仓库
git push -u origin
切换到 login 分支
git checkout login
将 login 分支推送到仓库
git push -u origin login
主页
配置拦截器
需要授权的 API ,必须在请求头中使用 Authorization 字段提供 token 令牌
export function request(config) {
// 1.创建 axios 实例
const instance = axios.create({
baseURL: 'http://39.97.105.222:8888/api/private/v1/',
timeout: 50000
})
// 2.axios 拦截器
instance.interceptors.request.use(config => {
config.headers.Authorization = window.sessionStorage.getItem('token');
return config
}, err => {
console.log(err);
})
// 3.发送真正的网络请求
return instance(config)
}
保持 menu 状态
saveNavState(activePath) {
// activePath:保存当前的路径
window.sessionStorage.setItem('activePath', activePath);
this.activePath = activePath;
},
created() {
// 刷新时重新激活
this.activePath = window.sessionStorage.getItem('activePath');
},
商品分类
使用了树形表格组件 vue-table-with-tree-grid
JS 工具库
深拷贝:.cloneDeep(value)
合并对象:.merge(object, [sources])
富文本编辑器
数据可视化
进度条
项目优化
移除 console
babel-plugin-transform-remove-console
安装
npm install babel-plugin-transform-remove-console —save-dev
使用
// babel.config.js
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
// 添加插件
"plugins": ["transform-remove-console"]
}
只在发布阶段移除 console
// 项目发布阶段需要用到的 babel 插件
const prodPlugins = []
// 获取当前的编译模式
if (process.env.NODE_ENV === 'production') {
prodPlugins.push('transform-remove-console')
}
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
"plugins": [...prodPlugins]
}
生成打包报告
通过命令行
vue-cli-service build —report
通过可视化的 UI 面板直接查看报告
指定不同打包入口
默认情况下,Vue项目的开发模式与发布模式,共用同一个打包的入口文件(即src/mainjs)。为了将项目
的开发过程与发布过程分离,我们可以为两种模式,各自指定打包的入口文件,即:
- 开发模式的入口文件为 src/main-dev.js
- 发布模式的入口文件为 src/main-prod.js
在 vue.config.js 导出的配置对象中,新增 configureWebpack 或 chainWebpack 节点,来自定义 webpack
的打包配置(文档)。
区别:
- chainWebpack 通过链式编程的形式,来修改默认的 webpack 配置
- configureWebpack 通过操作对象的形式,来修改默认的 webpack 配置
module.exports = {
chainWebpack: config => {
// 生产模式下的打包入口
config.when(process.env.NODE_ENV === 'production', config => {
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')
})
}
}
加载外部资源
默认情况下,通过 import 语法导入的第三方依赖包,最终会被打包合并到同一个文件中,从而导致打包成功后,单文件体积过大的问题。
为了解决上述问题,可以通过 webpack 的 externals 节点,来配置并加载外部的 CDN 资源。凡是声明在 externals 中的第三方依赖包,都不会被打包。
// vue.config.js
module.exports = {
chainWebpack: config => {
// 发布模式
config.when(process.env.NODE_ENV === 'production', config => {
config.entry('app').clear().add('./src/main-prod.js')
config.set('externals', {
vue: 'Vue',
'vue-router': 'VueRouter',
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')
})
}
}
同时需要在 public/index.html 文件的头部,添加如下的 CDN 资源引用:
<!-- nprogress 的样式表文件 -->
<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/quil1/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/vue- router/3.0.1/vue-router.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/1odash . 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/qui11/1.3.4/quill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-quill-editoL23.0.4/dist/vue-quill-editor.js"></script>
优化 ElementUI
虽然在开发阶段,我们启用了 element-ui 组件的按需加载,尽可能的减少了打包的体积,但是那些被按需加载的组件,还是占用了较大的文件体积。此时,我们可以将 element-ui 中的组件,也通过 CDN 的形式来加载,这样能够进一步减小打包后的文件体积。
具体操作流程如下:
- 在 main-prod.js 中,注释掉 element-ui 按需加载的代码
- 在 index.html 的头部区域中,通过 CDN 加载 element-ui 的 js 和 css 样式
<!-- element-ui 的样式表文件-->
<link rel="stylesheet" href="https://cdn.staticfile.org/element-ui/2.8.2/ theme-chalk/index.css" />
<!-- element-ui的js文件-->
<script src="https://cdn.staticfile.org/element-ui/2.8.2/index.js"></script>
首页内容定制
不同的打包环境下,首页内容可能会有所不同。我们可以通过插件的方式进行定制,插件配置如下:
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
})
})
}
在public/index.html首页中,可以根据isProd的值,来决定如何渲染页面结构:
<!-按需渲染页面的标题-->
<title><%= htmlWebpackPlugin.options.isProd ? '' : 'dev-' %>电商后台管理系统</title>
<!-按需加载外部的CDN资源-->
<% if(htmlWebpackPlugin.options.isProd) { %>
<!-通过externals加载的外部CDN资源-->
...
<% } %>
路由懒加载
const Welcome = () => import('components/Welcome')
const Users = () => import('views/users/Users')
const Rights = () => import('views/rights/Rights')
const Roles = () => import('views/roles/Roles')
Gzip 传输压缩
安装
npm install compression -D
配置
const express = require('express)
const compression = require('compression')
const app = express()
// 一定要把这一行代码写到静态资源托管之前
app.use(compression())
app.use(express.static('./dist'))
app.listen(80, () => {
console.log('server open 80')
})
配置 HTTPS
申请证书
- 进入 https://freessl.cn/ 官网,输入要申请的域名并选择品牌。
- 输入自己的邮箱并选择相关选项。
- 验证DNS (在域名管理后台添加TXT记录)。
- 验证通过之后,下载SSL 证书( full _chain.pem 公钥; private .key 私钥)。
配置
const https = require('https')
const fs = require('fs')
const options = {
cert: fs.readFileSync('./full_chain.pem'),
key: fs.readFileSync('./private.key')
}
https.createServer(options, app).listen(443)
pm2 管理应用
PM2 是node 进程管理工具,可以利用它来简化很多 node 应用管理的繁琐任务,如性能监控、自动重启、负载均衡等,而且使用非常简单。
安装
npm install pm2 -g
启动项目
pm2 start 脚本 —name 自定义名称
查看运行项目
pm2 ls
重启项目
pm2 restart 自定义名称
删除项目
pm2 delete 自定义名称