# 电商后台 **Repository Path**: owahahah/e-commerce-background ## Basic Information - **Project Name**: 电商后台 - **Description**: PC后台管理端的功能:管理用户账号(登录,退出,用户管理,权限管理),商品管理(商品分类,分类参数,商品信息,订单),数据统计 技术栈:Vue, Vue-Router, Element-UI, Axios, Echarts - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2021-01-19 - **Last Updated**: 2021-06-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # shop_management ## Project setup ``` npm install ``` ### Compiles and hot-reloads for development ``` npm run serve ``` ### Compiles and minifies for production ``` npm run build ``` ### Lints and fixes files ``` npm run lint ``` ### Customize configuration See [Configuration Reference](https://cli.vuejs.org/config/). **login页**:admin 123456 ![1611303330620](img/1611303330620.png) **首页** ![1611303310948](img/1611303310948.png) **用户管理** ![1611303354887](img/1611303354887.png) 添加用户![1611303964295](img/1611303964295.png) 权限管理 角色列表 ![1611303368921](img/1611303368921.png)权限列表![1611303384345](img/1611303384345.png) **商品管理** 商品列表 ![1611303447528](img/1611303447528.png)添加商品 ![1611303603333](img/1611303603333.png) 分类参数 ![1611303460196](img/1611303460196.png) 商品分类 ![1611303512219](img/1611303512219.png) 订单管理 ![1611303523202](img/1611303523202.png)![1611303575078](img/1611303575078.png) 数据统计 ![1611303535079](img/1611303535079.png) # 1.电商业务概述 客户使用的业务服务:PC端,小程序,移动web,移动app 管理员使用的业务服务:PC后台管理端。 PC后台管理端的功能:管理用户账号(登录,退出,用户管理,权限管理),商品管理(商品分类,分类参数,商品信息,订单),数据统计 电商后台管理系统采用前后端分离的开发模式 前端项目是基于Vue的SPA(单页应用程序)项目 **前端技术栈:**Vue, Vue-Router, Element-UI, Axios, Echarts **后端技术栈:**Node.js, Express, Jwt(模拟session), Mysql, Sequelize(操作数据库的框架) # ==2.项目步骤:== ## 1.项目初始化 A.安装Vue脚手架 B.通过脚手架创建项目 C.配置路由 D.配置Element-UI:在插件中安装,搜索vue-cli-plugin-element E.配置Axios:在依赖中安装,搜索axios(运行依赖) F.初始化git仓库 G.将本地项目托管到github或者码云中 ## 2.码云相关操作 A.注册登录码云账号 B.安装git 在Windows上使用Git,可以从Git官网直接下载安装程序进行安装。 测试命令:git --version C.点击网站右上角“登录”,登录码云,并进行账号设置 ![](https://gitee.com/owahahah/note/raw/master/img/%E7%A0%81%E4%BA%91%E7%82%B9%E5%87%BB%E8%AE%BE%E7%BD%AE.jpg) D.在本地创建公钥:在终端运行:ssh-keygen -t rsa -C "xxx@xxx.com" ![](https://gitee.com/owahahah/note/raw/master/img/%E5%88%9B%E5%BB%BA%E5%85%AC%E9%92%A5.jpg) E.找到公钥地址: Your identification has been saved in /c/Users/My/.ssh/id_rsa. Your public key has been saved in /c/Users/My/.ssh/id_rsa.pub. 当我们创建公钥完毕之后,请注意打印出来的信息“Your public key has been saved in” /c/Users/My/.ssh/id_rsa.pub : c盘下面的Users下面的My下面的.ssh下面的id_rsa.pub就是我们创建好的公钥了 E.打开id_rsa.pub文件,复制文件中的所有代码,点击码云中的SSH公钥,将生成的公钥复制到公钥中 ![](https://gitee.com/owahahah/note/raw/master/img/pub%E6%96%87%E4%BB%B6.jpg) ![](https://gitee.com/owahahah/note/raw/master/img/ssh%E5%85%AC%E9%92%A5.jpg) G.测试公钥:打开终端,输入命令 ssh -T git@gitee.com ![](https://gitee.com/owahahah/note/raw/master/img/success.jpg) H.将本地代码托管到码云中 点击码云右上角的+号->新建仓库 ![](https://gitee.com/owahahah/note/raw/master/img/%E6%96%B0%E5%BB%BA%E4%BB%93%E5%BA%93.jpg) ![](https://gitee.com/owahahah/note/raw/master/img/%E5%88%9B%E5%BB%BA%E4%BB%93%E5%BA%932.jpg) I.进行git配置: ![](https://gitee.com/owahahah/note/raw/master/img/config.jpg) 打开项目所在位置的终端,进行git仓库关联 ![](https://gitee.com/owahahah/note/raw/master/img/%E9%A1%B9%E7%9B%AE%E7%BB%88%E7%AB%AF%E6%89%A7%E8%A1%8C%E5%85%B3%E8%81%94.jpg) ## 3.配置服务器 ### A.安装phpStudy并导入mysql数据库数据 ![](https://gitee.com/owahahah/note/raw/master/img/phpStudy.jpg) ![](https://gitee.com/owahahah/note/raw/master/img/mysql.jpg) ![1610854034472](https://gitee.com/owahahah/note/raw/master/img/1610854034472.png) 此时会弹出一个小黑窗,不需要管它,打开服务器即可node app.js ### B.初始化npm,并启动服务器 安装nodeJS,配置后台项目,从终端打开后台项目vue_api_server,文件在`F:\阶段五:Vue.js项目实战资料\阶段五:Vue.js项目实战资料\Vue-2019版\vue电商\3.vue-项目实战day1(4章1-2小节)\素材\vue_api_server` 然后在终端中输入命令安装项目依赖包:npm install ```js npm install //安装项目依赖包 node app.js //打开项目 ``` ### C.使用postman测试api接口 ![1611025827692](img/1611025827692.png) ### D.注意启动事项 下次启动 直接管理员运行phpStudy,然后启动服务器即可 ## 4.登录页面的功能 ### A.登录状态保持 如果服务器和客户端**同源**,建议可以使用cookie或者session来保持登录状态 如果客户端和服务器**跨域**了,建议使用token进行维持登录状态。 ![1611299067869](img/1611299067869.png) ### B.登录逻辑流程: > 1. 在登录页面输入账号和密码进行登录,将数据发送给服务器。 > 2. 服务器返回登录的结果,登录成功则返回数据中带有token。 > 3. 客户端得到token并进行保存,后续的请求都需要将此token发送给服务器,服务器会验证token以保证用户身份。 > 4. 登录成功之后,根据后台的响应状态跳转到项目主页。 ### C.添加新分支login,在login分支中开发当前项目vue_shop: 打开vue_shop终端,使用git status确定当前项目状态。 ````js git status ```` 确定当前工作目录是干净的之后,**创建一个分支**进行开发,开发完毕之后将其合并到master ```js git checkout -b login ``` 然后查看新创建的分支: ```js git branch ``` 确定我们正在使用login分支进行开发 ![](https://gitee.com/owahahah/note/raw/master/img/branch.jpg) 然后执行vue ui命令打开ui界面,然后运行serve,运行app查看当前项目效果 ![](https://gitee.com/owahahah/note/raw/master/img/ui%E7%95%8C%E9%9D%A2%E5%90%AF%E5%8A%A8%E9%A1%B9%E7%9B%AE.jpg) ### D.项目文件更改 #### 1.main.js文件(入口文件) 发现现在是一个默认页面,我们需要进行更改,打开项目的src目录,点击main.js文件(入口文件) ```js import Vue from 'vue' import App from './App.vue' import router from './router' import './plugins/element.js' Vue.config.productionTip = false new Vue({ router, render: h => h(App) }).$mount('# app') ``` #### 2.App.vue(根组件) 再打开App.vue(根组件),将根组件的内容进行操作梳理(template中留下根节点,script中留下默认导出,去掉组件,style中去掉所有样式) 在App.vue中添加路由占位符 ```vue ``` #### 3.router.js(路由) 再打开router.js(路由),将routes数组中的路由规则清除,然后将views删除,将components中的helloworld.vue删除 ```js import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) export default new Router({ routes: [ ] }) ``` #### 4.新建Login.vue组件 在components文件夹中新建Login.vue组件,添加template,script,style标签,style标签中的scoped可以防止组件之间的样式冲突,没有scoped则样式是全局的 ```vue ``` #### 5.在router.js中导入组件并设置规则 ```js import login from './component/login.vue' const router = new Router({ routes: [ //路由重定向 { path: '/', redirect: '/login' }, { path: '/login', component: Login } ] }) ``` 注意:当我们给Login.vue中的内容添加样式的时候,会报错“缺少less-loader”,需要配置less加载器(开发依赖),安装less(开发依赖) ![](https://gitee.com/owahahah/note/raw/master/img/less.jpg) ### F.添加公共样式 在assets文件夹下面添加css文件夹,创建global.css文件,添加全局样式 ```css /* 全局样式表 */ html,body,# app{ width: 100%; height: 100%; margin: 0; padding: 0; } ``` 在main.js中导入global.css,使得全局样式生效 import "./assets/css/global.css" 然后Login.vue中的根元素也需要设置撑满全屏(height:100%) ### G.Login.vue代码说明 ```vue ``` 其中我们有用到一下内容,需要进行进一步处理: ## 5.辅助的饿了么框架和字体图标引入 #### A.添加element-ui的表单组件 在plugins文件夹中打开element.js文件,进行elementui的按需导入 ```js import Vue from 'vue' import { Button } from 'element-ui' import { Form, FormItem } from 'element-ui' import { Input } from 'element-ui' //注册为全局组件 Vue.use(Button) Vue.use(Form) Vue.use(FormItem) Vue.use(Input) ``` #### B.添加第三方字体 复制素材中的fonts文件夹到assets中,在入口文件main.js中导入import './assets/fonts/iconfont.css' 然后直接在 ``` ``` ## 6.添加表单验证的步骤 1).给添加属性:rules="rules",rules是一堆验证规则,定义在script中 2).在script中添加rules: ``` export default{ data(){return{......, rules: { name: [ { required: true, message: '请输入活动名称', trigger: 'blur' }, { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' } ], region: [ { required: true, message: '请选择活动区域', trigger: 'change' } ] }...... ``` 3).通过的prop属性设置验证规则 ## 7.导入axios以发送ajax请求 ```js //安装 npm install axios -S //打开main.js, import axios from 'axios'; //设置请求的根路径: axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'; //挂载axios: Vue.prototype.$http = axios; ``` 5.配置弹窗提示: 在plugins文件夹中打开element.js文件,进行elementui的按需导入 ``` import {Message} from 'element-ui' ``` 进行全局挂载: ```js Vue.prototype.$message = Message; //在login.vue组件中编写弹窗代码: this.$message.error('登录失败') ``` ## 8.登录成功-状态保持 #### A.保存token 登录成功之后,需要将后台返回的token保存到sessionStorage中 操作完毕之后,需要跳转到/home ```js login() { //点击登录的时候先调用validate方法验证表单内容是否有误 this.$refs.LoginFormRef.validate(async valid => { console.log(this.loginFormRules) //如果valid参数为true则验证通过 if (!valid) { return } //发送请求进行登录 const { data: res } = await this.$http.post('login', this.loginForm) // console.log(res); if (res.meta.status !== 200) { return this.$message.error('登录失败:' + res.meta.msg) //console.log("登录失败:"+res.meta.msg) } this.$message.success('登录成功') console.log(res) //保存token window.sessionStorage.setItem('token', res.data.token) // 导航至/home this.$router.push('/home') }) } ``` #### B.新建组件Home.vue ```js ``` #### C.添加路由规则 ```js const router = new Router({ routes: [ { path: '/', redirect: '/login' }, { path: '/login', component: Login }, { path: '/home', component: Home } ] }) ``` ## 9.添加路由守卫 如果用户没有登录,那么就不能访问/home,而如果用户通过url地址直接访问,则会强制跳转到登录页面,这个时候需要添加路由守卫控制访问权限 ```js //router.js import Vue from 'vue' import Router from 'vue-router' import Login from './components/Login.vue' import Home from './components/Home.vue' Vue.use(Router) const router = new Router({ routes: [ { path:'/', redirect:'/login'}, { path:'/login' , component:Login }, { path:'/home' , component:Home} ] }) //挂载路由导航守卫, //to表示将要访问的路径, //from表示从哪里来, //next是下一个要做的操作 router.beforeEach((to,from,next)=>{ if(to.path === '/login') return next(); //获取token const tokenStr = window.sessionStorage.getItem('token'); if(!tokenStr) return next('/login'); next(); }) export default router ``` ## 10.实现退出功能 在Home组件中添加一个退出功能按钮,给退出按钮添加点击事件,添加事件处理代码如下: ```js export default { methods:{ logout(){ window.sessionStorage.clear(); this.$router.push('/login'); } } } ``` ### 补充 #### A.处理ESLint警告 打开脚手架面板,查看警告信息 ![1609241438225](https://gitee.com/owahahah/note/raw/master/img/1609241438225.png) 默认情况下,ESLint和vscode格式化工具有冲突,需要添加配置文件解决冲突。 在项目根目录添加 .prettierrc 文件 ```js { "semi":false, //格式化不加分号 "singleQuote":true //不加单引号; } ``` 打开.eslintrc.js文件,禁用对 space-before-function-paren 的检查: ```js rules: { 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'space-before-function-paren' : 0 //函数的()前面要有一个空格的报错 }, ``` #### B.合并按需导入的element-ui ```js import Vue from 'vue' import { Button, Form, FormItem, Input, Message } from 'element-ui' Vue.use(Button) Vue.use(Form) Vue.use(FormItem) Vue.use(Input) // 进行全局挂载: Vue.prototype.$message = Message ``` #### C.将代码提交到码云 新建一个项目终端,输入命令‘git status’查看修改过的与新增的文件内容 将所有文件添加到暂存区: ```js git add . ``` 将所有代码提交到本地仓库: ```js git commit -m "添加登录功能以及/home的基本结构" ``` 查看分支: ```js git branch ``` 发现所有代码都被提交到了login分支 , ```js ///推送到码云: git push -u origin 远端分支名 ``` 去master将login分支代码合并到master主分支, ```js //先切换到master: git checkout master //在master分支进行代码合并: git merge login //将本地的master推送到远端的码云: git push ``` # 今日目标 1.实现后台首页的基本布局 2.实现左侧菜单栏 3.实现用户列表展示 4.实现添加用户 # ==3.后台首页的基本布局== ## 1.后台首页基本布局 打开Home.vue组件,进行布局: ```jsp Header 退出 Aside Main ``` 默认情况下,跟element-ui组件同名的类名可以帮助我们快速的给对应的组件添加样式,如: ```js .home-container { height: 100%; } .el-header{ background-color:# 373D41; } .el-aside{ background-color:# 333744; } .el-main{ background-color:# eaedf1; } ``` ## 2.顶部布局,侧边栏布局 ```jsp ``` ## 3.axios请求拦截器 后台除了登录接口之外,都需要token权限验证,我们可以通过添加axios请求拦截器来添加token,以保证拥有获取数据的权限 在main.js中添加代码,在将axios挂载到vue原型之前添加下面的代码 ```js //请求在到达服务器之前,先会调用use中的这个回调函数来添加请求头信息 axios.interceptors.request.use(config=>{ //为请求头对象,添加token验证的Authorization字段 config.headers.Authorization = window.sessionStorage.getItem("token") return config }) ``` ## 4.请求侧边栏数据 ```js ``` 通过v-for双重循环渲染左侧菜单 ```jsp ``` ## 5.设置激活子菜单样式 通过更改el-menu的active-text-color属性可以设置侧边栏菜单中点击的激活项的文字颜色 通过更改菜单项模板(template)中的i标签的类名,可以将左侧菜单栏的图标进行设置,我们需要在项目中使用第三方字体图标 在数据中添加一个iconsObj: ```js iconsObj: { '125':'iconfont icon-user', '103':'iconfont icon-tijikongjian', '101':'iconfont icon-shangpin', '102':'iconfont icon-danju', '145':'iconfont icon-baobiao' } ``` 然后将图标类名进行数据绑定,绑定iconsObj中的数据: 为了保持左侧菜单每次只能打开一个,显示其中的子菜单,我们可以在el-menu中添加一个属性unique-opened 或者也可以数据绑定进行设置(此时true认为是一个bool值,而不是字符串) :unique-opened="true" ## 6.制作侧边菜单栏的伸缩功能 在菜单栏上方添加一个div ```jsp
|||
||| ``` ## 7.在后台首页添加子级路由 新增子级路由组件Welcome.vue 在router.js中导入子级路由组件,并设置路由规则以及子级路由的默认重定向 打开Home.vue,在main的主体结构中添加一个路由占位符 制作好了Welcome子级路由之后,我们需要将所有的侧边栏二级菜单都改造成子级路由链接 我们只需要将el-menu的router属性设置为true就可以了,此时当我们点击二级菜单的时候,就会根据菜单的index 属性进行路由跳转,如: /110, 使用index id来作为跳转的路径不合适,我们可以重新绑定index的值为 :index="'/'+subItem.path" ## 8.完成用户列表主体区域 新建用户列表组件 user/Users.vue 在router.js中导入子级路由组件Users.vue,并设置路由规则 当点击二级菜单的时候,被点击的二级子菜单并没有高亮,我们需要正在被使用的功能高亮显示 我们可以通过设置el-menu的default-active属性来设置当前激活菜单的index 但是default-active属性也不能写死,固定为某个菜单值 所以我们可以先给所有的二级菜单添加点击事件,并将path值作为方法的参数 @click="saveNavState('/'+subItem.path)" 在saveNavState方法中将path保存到sessionStorage中 saveNavState( path ){ //点击二级菜单的时候保存被点击的二级菜单信息 window.sessionStorage.setItem("activePath",path); this.activePath = path; } 然后在数据中添加一个activePath绑定数据,并将el-menu的default-active属性设置为activePath 最后在created中将sessionStorage中的数据赋值给activePath this.activePath = window.sessionStorage.getItem("activePath") ## 9.绘制用户列表基本结构 A.使用element-ui面包屑组件完成顶部导航路径(复制面包屑代码,在element.js中导入组件Breadcrumb,BreadcrumbItem) B.使用element-ui卡片组件完成主体表格(复制卡片组件代码,在element.js中导入组件Card),再使用element-ui输入框完成搜索框及搜索按钮, 此时我们需要使用栅格布局来划分结构(复制卡片组件代码,在element.js中导入组件Row,Col),然后再使用el-button制作添加用户按钮 ```

用户列表组件

首页 用户管理 用户列表 添加用户
``` ## 10.请求用户列表数据 ``` ``` ## 11.将用户列表数据展示 使用表格来展示用户列表数据,使用element-ui表格组件完成列表展示数据(复制表格代码,在element.js中导入组件Table,TableColumn) 在渲染展示状态时,会使用作用域插槽获取每一行的数据 再使用switch开关组件展示状态信息(复制开关组件代码,在element.js中导入组件Switch) 而渲染操作列时,也是使用作用域插槽来进行渲染的, 在操作列中包含了修改,删除,分配角色按钮,当我们把鼠标放到分配角色按钮上时 希望能有一些文字提示,此时我们需要使用文字提示组件(复制文字提示组件代码,在element.js中导入组件Tooltip),将分配角色按钮包含 代码结构如下: ``` ``` ## 12.实现用户列表分页 A.使用表格来展示用户列表数据,可以使用分页组件完成列表分页展示数据(复制分页组件代码,在element.js中导入组件Pagination) B.更改组件中的绑定数据 ``` ``` C.添加两个事件的事件处理函数@size-change,@current-change ``` handleSizeChange(newSize) { //pagesize改变时触发,当pagesize发生改变的时候,我们应该 //以最新的pagesize来请求数据并展示数据 // console.log(newSize) this.queryInfo.pagesize = newSize; //重新按照pagesize发送请求,请求最新的数据 this.getUserList(); }, handleCurrentChange( current ) { //页码发生改变时触发当current发生改变的时候,我们应该 //以最新的current页码来请求数据并展示数据 // console.log(current) this.queryInfo.pagenum = current; //重新按照pagenum发送请求,请求最新的数据 this.getUserList(); } ``` ## 13.实现更新用户状态 当用户点击列表中的switch组件时,用户的状态应该跟随发生改变。 A.首先监听用户点击switch组件的事件,并将作用域插槽的数据当做事件参数进行传递 ``` ``` B.在事件中发送请求完成状态的更改 ``` async userStateChanged(row) { //发送请求进行状态修改 const { data: res } = await this.$http.put( `users/${row.id}/state/${row.mg_state}` ) //如果返回状态为异常状态则报错并返回 if (res.meta.status !== 200) { row.mg_state = !row.mg_state return this.$message.error('修改状态失败') } this.$message.success('更新状态成功') }, ``` ## 14.实现搜索功能 添加数据绑定,添加搜索按钮的点击事件(当用户点击搜索按钮的时候,调用getUserList方法根据文本框内容重新请求用户列表数据) 当我们在输入框中输入内容并点击搜索之后,会按照搜索关键字搜索,我们希望能够提供一个X删除搜索关键字并重新获取所有的用户列表数据,只需要给文本框添加clearable属性并添加clear事件,在clear事件中重新请求数据即可 ``` ``` ## 15.实现添加用户 A.当我们点击添加用户按钮的时候,弹出一个对话框来实现添加用户的功能,首先我们需要复制对话框组件的代码并在element.js文件中引入Dialog组件 B.接下来我们要为“添加用户”按钮添加点击事件,在事件中将addDialogVisible设置为true,即显示对话框 C.更改Dialog组件中的内容 ``` 取 消 确 定 ``` D.添加数据绑定和校验规则: ``` data() { //验证邮箱的规则 var checkEmail = (rule, value, cb) => { const regEmail = /^\w+@\w+(\.\w+)+$/ if (regEmail.test(value)) { return cb() } //返回一个错误提示 cb(new Error('请输入合法的邮箱')) } //验证手机号码的规则 var checkMobile = (rule, value, cb) => { const regMobile = /^1[34578]\d{9}$/ if (regMobile.test(value)) { return cb() } //返回一个错误提示 cb(new Error('请输入合法的手机号码')) } return { //获取查询用户信息的参数 queryInfo: { // 查询的条件 query: '', // 当前的页数,即页码 pagenum: 1, // 每页显示的数据条数 pagesize: 2 }, //保存请求回来的用户列表数据 userList: [], total: 0, //是否显示添加用户弹出窗 addDialogVisible: false, // 添加用户的表单数据 addForm: { username: '', password: '', email: '', mobile: '' }, // 添加表单的验证规则对象 addFormRules: { username: [ { required: true, message: '请输入用户名称', trigger: 'blur' }, { min: 3, max: 10, message: '用户名在3~10个字符之间', trigger: 'blur' } ], password: [ { required: true, message: '请输入密码', trigger: 'blur' }, { min: 6, max: 15, message: '用户名在6~15个字符之间', trigger: 'blur' } ], email: [ { required: true, message: '请输入邮箱', trigger: 'blur' }, { validator:checkEmail, message: '邮箱格式不正确,请重新输入', trigger: 'blur'} ], mobile: [ { required: true, message: '请输入手机号码', trigger: 'blur' }, { validator:checkMobile, message: '手机号码不正确,请重新输入', trigger: 'blur'} ] } } } ``` E.当关闭对话框时,重置表单 给el-dialog添加@close事件,在事件中添加重置表单的代码 ``` methods:{ .... addDialogClosed(){ //对话框关闭之后,重置表达 this.$refs.addFormRef.resetFields(); } } ``` F.点击对话框中的确定按钮,发送请求完成添加用户的操作 首先给确定按钮添加点击事件,在点击事件中完成业务逻辑代码 ``` methods:{ .... addUser(){ //点击确定按钮,添加新用户 //调用validate进行表单验证 this.$refs.addFormRef.validate( async valid => { if(!valid) return this.$message.error("请填写完整用户信息"); //发送请求完成添加用户的操作 const {data:res} = await this.$http.post("users",this.addForm) //判断如果添加失败,就做提示 if (res.meta.status !== 200) return this.$message.error('添加用户失败') //添加成功的提示 this.$message.success("添加用户成功") //关闭对话框 this.addDialogVisible = false //重新请求最新的数据 this.getUserList() }) } } ``` # 今日目标 1.修改用户,删除用户 2.推送代码到码云 3.权限列表 4.角色列表 5.分配角色 # ==4.用户管理== ## 1.修改用户信息 A.为用户列表中的修改按钮绑定点击事件 B.在页面中添加修改用户对话框,并修改对话框的属性 C.根据id查询需要修改的用户数据 ``` //展示编辑用户的对话框 async showEditDialog(id) { //发送请求根据id获取用户信息 const { data: res } = await this.$http.get('users/' + id) //判断如果添加失败,就做提示 if (res.meta.status !== 200) return this.$message.error('获取用户信息失败') //将获取到的数据保存到数据editForm中 this.editForm = res.data //显示弹出窗 this.editDialogVisible = true } ``` D.在弹出窗中添加修改用户信息的表单并做响应的数据绑定以及数据验证 ``` ``` 数据绑定以及验证: ``` //控制修改用户对话框的显示与否 editDialogVisible: false, //修改用户的表单数据 editForm: { username: '', email: '', mobile: '' }, //修改表单的验证规则对象 editFormRules: { email: [ { required: true, message: '请输入邮箱', trigger: 'blur' }, { validator: checkEmail, message: '邮箱格式不正确,请重新输入', trigger: 'blur' } ], mobile: [ { required: true, message: '请输入手机号码', trigger: 'blur' }, { validator: checkMobile, message: '手机号码不正确,请重新输入', trigger: 'blur' } ] } ``` E.监听对话框关闭事件,在对话框关闭之后,重置表单 ``` editDialogClosed(){ //对话框关闭之后,重置表达 this.$refs.editFormRef.resetFields() } ``` F.在用户点击确定按钮的时候,验证数据成功之后发送请求完成修改 ``` editUser() { //用户点击修改表单中的确定按钮之后,验证表单数据 this.$refs.editFormRef.validate(async valid => { if (!valid) return this.$message.error('请填写完整用户信息') //发送请求完成修改用户的操作 const { data: res } = await this.$http.put( 'users/' + this.editForm.id, this.editForm ) //判断如果修改失败,就做提示 if (res.meta.status !== 200) return this.$message.error('修改用户失败') //修改成功的提示 this.$message.success('修改用户成功') //关闭对话框 this.editDialogVisible = false //重新请求最新的数据 this.getUserList() }) } ``` ### 2.删除用户 在点击删除按钮的时候,我们应该跳出提示信息框,让用户确认要进行删除操作。 如果想要使用确认取消提示框,我们需要先将提示信息框挂载到vue中。 A.导入MessageBox组件,并将MessageBox组件挂载到实例。 Vue.prototype.$confirm = MessageBox.confirm B.给用户列表中的删除按钮添加事件,并在事件处理函数中弹出确定取消窗,最后再根据id发送删除用户的请求 ``` async removeUserById(id){ //弹出确定取消框,是否删除用户 const confirmResult = await this.$confirm('请问是否要永久删除该用户','删除提示',{ confirmButtonText:'确认删除', cancelButtonText:'取消', type:'warning' }).catch(err=>err) //如果用户点击确认,则confirmResult 为'confirm' //如果用户点击取消, 则confirmResult获取的就是catch的错误消息'cancel' if(confirmResult != "confirm"){ return this.$message.info("已经取消删除") } //发送请求根据id完成删除操作 const {data:res} = await this.$http.delete('users/'+id) //判断如果删除失败,就做提示 if (res.meta.status !== 200) return this.$message.error('删除用户失败') //修改成功的提示 this.$message.success('删除用户成功') //重新请求最新的数据 this.getUserList() } ``` ### 3.推送代码 创建user子分支,并将代码推送到码云 A.创建user子分支 git checkout -b user B.将代码添加到暂存区 git add . C.将代码提交并注释 git commit -m '添加完成用户列表功能' D.将本地的user分支推送到码云 git push -u origin user E.将user分支代码合并到master: 切换到master git checkout master 合并user git merge user F.将本地master分支的代码推送到码云 git push 创建rights子分支 A.创建rights子分支 git checkout -b rights B.将本地的rights分支推送到码云 git push -u origin rights ### 4.权限列表 #### A.添加权限列表路由 创建权限管理组件(Rights.vue),并在router.js添加对应的路由规则 ``` import Rights from './components/power/Rights.vue' ...... path: '/home', component: Home, redirect: '/welcome', children: [ { path: "/welcome", component: Welcome }, { path: "/users", component: Users }, { path: "/rights", component: Rights } ] ...... ``` #### B.添加面包屑导航 在Rights.vue中添加面包屑组件展示导航路径 #### C.显示数据 在data中添加一个rightsList数据,在methods中提供一个getRightsList方法发送请求获取权限列表数据,在created中调用这个方法获取数据 ``` ``` ### 5.角色列表 #### A.添加角色列表路由 添加角色列表子组件(power/Roles.vue),并添加对应的规则 ``` path: '/home', component: Home, redirect: '/welcome', children: [ { path: "/welcome", component: Welcome }, { path: "/users", component: Users }, { path: "/rights", component: Rights }, { path: "/roles", component: Roles } ] ``` #### B.添加面包屑导航 在Roles.vue中添加面包屑组件展示导航路径 #### C.显示数据 在data中添加一个roleList数据,在methods中提供一个getRoleList方法发送请求获取权限列表数据,在created中调用这个方法获取数据 ``` ``` #### D.补充说明 之前学习过类似的添加角色,删除角色,编辑角色请参照之前编写过的代码还有接口文档完成效果。 #### E.生成权限列表 使用三重嵌套for循环生成权限下拉列表 ``` ``` #### F.美化样式 通过设置global.css中的# app样式min-width:1366px 解决三级权限换行的问题 ,通过给一级权限el-row添加display:flex,align-items:center的方式解决一级权限垂直居中的问题,二级权限也类似添加,因为需要给多个内容添加,可以将这个样式设置为一个.vcenter{display:flex;align-items:center} #### G.添加权限删除功能 给每一个权限的el-tag添加closable属性,是的权限右侧出现“X”图标 再给el-tag添加绑定close事件处理函数removeRightById(scope.row,item1.id) removeRightById(scope.row,item2.id) removeRightById(scope.row,item3.id) ``` async removeRightById(role,rightId){ //弹窗提示用户是否要删除 const confirmResult = await this.$confirm('请问是否要删除该权限','删除提示',{ confirmButtonText:'确认删除', cancelButtonText:'取消', type:'warning' }).catch(err=>err) //如果用户点击确认,则confirmResult 为'confirm' //如果用户点击取消, 则confirmResult获取的就是catch的错误消息'cancel' if(confirmResult != "confirm"){ return this.$message.info("已经取消删除") } //用户点击了确定表示真的要删除 //当发送delete请求之后,返回的数据就是最新的角色权限信息 const {data:res} = await this.$http.delete(`roles/${role.id}/rights/${rightId}`) if (res.meta.status !== 200) return this.$message.error('删除角色权限失败') //无需再重新加载所有权限 //只需要对现有的角色权限进行更新即可 role.children = res.data // this.getRoleList(); } ``` #### H.完成权限分配功能 先给分配权限按钮添加事件 分配权限 在showSetRightDialog函数中请求权限树数据并显示对话框 ``` async showSetRightDialog() { //当点击分配权限按钮时,展示对应的对话框 this.setRightDialogVisible = true; //获取所有权限的数据 const {data:res} = await this.$http.get('rights/tree') //如果返回状态为异常状态则报错并返回 if (res.meta.status !== 200) return this.$message.error('获取权限树失败') //如果返回状态正常,将请求的数据保存在data中 this.rightsList = res.data } ``` 添加分配权限对话框,并添加绑定数据setRightDialogVisible 这是一段信息 取 消 确 定 #### I.完成树形结构弹窗 在element.js中引入Tree,注册Tree ``` 取 消 确 定 ``` ## 6.分配角色 打开Users.vue,完成分配角色的功能 A.添加分配角色对话框 ```

当前的用户:{{userInfo.username}}

当前的角色:{{userInfo.role_name}}

分配新角色:

取 消 确 定
``` B.给分配角色按钮添加点击事件,点击之后弹出一个对话框进行角色分配 ``` data(){ ...... //控制显示分配角色对话框 setRoleDialogVisible:false, //保存正在操作的那个用户信息 userInfo:{}, //保存所有的角色信息 rolesList:[], //保存用户选中的角色id selectedRoleId:'' }, methods:{ ...... async setRole( userInfo ){ //保存起来以供后续使用 this.userInfo = userInfo; //获取所有的角色信息,以备下拉列表使用 //发送请求根据id完成删除操作 const { data: res } = await this.$http.get('roles') //判断如果删除失败,就做提示 if (res.meta.status !== 200) return this.$message.error('获取角色列表失败') this.rolesList = res.data; //展示分配角色对话框 this.setRoleDialogVisible = true; } } ``` C.在element.js中引入Select,Option,注册Select,Option ``` ``` D.当用户点击对话框中的确定之后,完成分配角色的操作 ```

当前的用户:{{userInfo.username}}

当前的角色:{{userInfo.role_name}}

分配新角色:

取 消 确 定
methods:{ ....... async saveRoleInfo(){ //当用户点击确定按钮之后 //判断用户是否选择了需要分配的角色 if(!this.selectedRoleId){ return this.$message.error('请选择需要分配的角色') } //发送请求完成分配角色的操作 const {data:res} = await this.$http.put(`users/${this.userInfo.id}/role`,{rid:this.selectedRoleId}) //判断如果删除失败,就做提示 if (res.meta.status !== 200) return this.$message.error('分配角色失败') this.$message.success('分配角色成功') this.getUserList(); //关闭对话框 this.setRoleDialogVisible = false }, setRoleDialogClosed(){ //当关闭对话框的时候,重置下拉框中的内容 this.selectedRoleId = '' this.userInfo = {} } } ``` ## 7.将代码推送到码云 A.将代码推送到暂存区 git add . B.将代码提交到仓库 git commit -m '完成了权限功能开发' C.将rights分支代码推送到码云 git push D.将代码合并到master git checkout master git merge rights E.将master代码推送到码云 git push # ==今日目标== 1.完成商品分类 2.完成参数管理 ## 1.商品分类 ### A.新建分支goods_cate 新建分支goods_cate并推送到码云 git checkout -b goods_cate git push -u origin goods_cate ### B.创建子级路由 创建categories子级路由组件并设置路由规则 ``` import Cate from './components/goods/Cate.vue' path: '/home', component: Home, redirect: '/welcome', children: [ { path: "/welcome", component: Welcome }, { path: "/users", component: Users }, { path: "/rights", component: Rights }, { path: "/roles", component: Roles }, { path: "/categories", component: Cate } ] ``` ### C.添加组件基本布局 在Cate.vue组件中添加面包屑导航以及卡片视图中的添加分类按钮 ``` ``` ### D.请求分类数据 请求分类数据并将数据保存在data中 ``` ``` ### E.使用插件展示数据 使用第三方插件vue-table-with-tree-grid展示分类数据 1).在vue 控制台中点击依赖->安装依赖->运行依赖->输入vue-table-with-tree-gird->点击安装 2).打开main.js,导入vue-table-with-tree-grid import TreeTable from 'vue-table-with-tree-grid' ..... Vue.config.productionTip = false //全局注册组件 Vue.component('tree-table', TreeTable) 3).使用组件展示分类数据 ``` 在数据中添加columns: columns: [ {label:'分类名称',prop:'cat_name'} ] ``` ### F.自定义数据列 使用vue-table-with-tree-grid定义模板列并添加自定义列 ``` //先在columns中添加一个列 columns: [ {label:'分类名称',prop:'cat_name'}, //type:'template'(将该列设置为模板列),template:'isok'(设置该列模板的名称为isok) {label:'是否有效',prop:'',type:'template',template:'isok'}, {label:'排序',prop:'',type:'template',template:'order'}, {label:'操作',prop:'',type:'template',template:'opt'} ] ``` ### G.完成分页功能 ``` //添加对应的事件函数 methods:{ ....... handleSizeChange(newSize){ //当pagesize发生改变时触发 this.queryInfo.pagesize = newSize; this.getCateList(); }, handleCurrentChange(newPage){ //当pagenum发生改变时触发 this.queryInfo.pagenum = newPage; this.getCateList(); } } ``` ### H.完成添加分类 ``` ...... 添加分类 ...... 取 消 确 定 //用来显示或隐藏添加分类对话框 addCateDialogVisible: false, //添加分类的表单数据对象 addCateForm:{ //分类名称 cat_name:'', //添加分类的父级id,0则表示父级为0.添加一级分类 cat_pid:0, //添加分类的等级,0则表示添加一级分类 cat_level:0 }, //添加分类校验规则 addCateFormRules:{ //验证规则 cat_name:[ {required:true , message:'请输入分类名称',trigger:'blur'} ] }, //保存1,2级父级分类的列表 parentCateList:[] ....... showAddCateDialog() { //调用getParentCateList获取分类列表 this.getParentCateList() //显示添加分类对话框 this.addCateDialogVisible = true }, async getParentCateList(){ //获取父级分类数据列表 const { data: res } = await this.$http.get('categories', { params: {type:2} }) if (res.meta.status !== 200) { return this.$message.error('获取商品分类列表数据失败') } this.parentCateList = res.data } ``` 添加级联菜单显示父级分类 先导入Cascader组件,并注册 然后添加使用级联菜单组件: ``` 添加数据 //配置级联菜单中数据如何展示 cascaderProps:{ value:'cat_id', label:'cat_name', children:'children', expandTrigger:'hover' }, //绑定用户选择的分类值 selectedKeys:[] ..... methods:{ ..... parentCateChange(){ //级联菜单中选择项发生变化时触发 console.log(this.selectedKeys) //如果用户选择了父级分类 if(this.selectedKeys.length > 0){ //则将数组中的最后一项设置为父级分类 this.addCateForm.cat_pid = this.selectedKeys[this.selectedKeys.length - 1] //level也要跟着发生变化 this.addCateForm.cat_level = this.selectedKeys.length return }else{ this.addCateForm.cat_pid = 0 this.addCateForm.cat_level = 0 return } }, addCateDialogClosed(){ //当关闭添加分类对话框时,重置表单 this.$refs.addCateFormRef.resetFields() this.selectedKeys = []; this.addCateForm.cat_pid = 0 this.addCateForm.cat_level = 0 }, addCate() { //点击确定,完成添加分类 console.log(this.addCateForm) this.$refs.addCateFormRef.validate(async valid => { if (!valid) return //发送请求完成添加分类 const { data: res } = await this.$http.post( 'categories', this.addCateForm ) if (res.meta.status !== 201) { return this.$message.error('添加分类失败') } this.$message.success('添加分类成功') this.getCateList() this.addCateDialogVisible = false }) } } ``` ### I.推送代码 制作完添加分类之后,将代码提交到仓库,推送到码云,将goods_cate分支合并到master git add . git commit -m '完成商品分类' git push git checkout master git merge goods_cate ## 2.参数管理 只允许给三级分类内容设置参数,参数分为动态参数和静态参数属性 ### A.添加子级组件 添加Params.vue子组件,并在router.js中引入该组件并设置路由规则 ``` import Params from './components/goods/Params.vue' ...... path: '/home', component: Home, redirect: '/welcome', children: [ { path: "/welcome", component: Welcome }, { path: "/users", component: Users }, { path: "/rights", component: Rights }, { path: "/roles", component: Roles }, { path: "/categories", component: Cate }, { path: "/params", component: Params } ] ``` ### B.完成组件基本布局 完成Params.vue组件的基本布局 其中警告提示信息使用了el-alert,在element.js引入该组件并注册 ``` ``` ### C.完成级联选择框 完成商品分类级联选择框 ``` 选择商品分类: ...... ``` ### D.展示参数 展示动态参数数据以及静态属性数据 ``` 添加参数 添加属性 ``` ### B.数据展示 添加数据表格展示数据以及分页功能的实现,搜索功能的实现 在main.js中添加过滤器: ``` //创建过滤器将秒数过滤为年月日,时分秒 Vue.filter('dateFormat',function(originVal){ const dt = new Date(originVal) const y = dt.getFullYear() const m = (dt.getMonth()+1+'').padStart(2,'0') const d = (dt.getDate()+'').padStart(2,'0') const hh = (dt.getHours()+'').padStart(2,'0') const mm = (dt.getMinutes()+'').padStart(2,'0') const ss = (dt.getSeconds()+'').padStart(2,'0') return `${y}-${m}-${d} ${hh}:${mm}:${ss}` }) ``` ``` 添加商品 //绑定数据以及添加方法 ``` ### C.实现删除商品 ``` //绑定按钮点击事件 //事件函数代码编写 async removeGoods(goods_id) { //根据id删除对应的参数或属性 //弹窗提示用户是否要删除 const confirmResult = await this.$confirm( '请问是否要删除该商品', '删除提示', { confirmButtonText: '确认删除', cancelButtonText: '取消', type: 'warning' } ).catch(err => err) //如果用户点击确认,则confirmResult 为'confirm' //如果用户点击取消, 则confirmResult获取的就是catch的错误消息'cancel' if (confirmResult != 'confirm') { return this.$message.info('已经取消删除') } //没有取消就是要删除,发送请求完成删除 const {data:res} = await this.$http.delete(`goods/${goods_id}`) if (res.meta.status !== 200) { return this.$message.error('删除商品失败') } this.$message.success('删除商品成功') this.getGoodsList() } ``` ## 4.添加商品 ### A.添加编程式导航 在List.vue中添加编程式导航,并创建添加商品路由组件及规则 ``` //在List.vue中添加编程式导航 添加商品 goAddPage(){ this.$router.push('/goods/add') } ``` 在router.js中引入goods/Add.vue,并添加路由规则 ``` import GoodAdd from './components/goods/Add.vue' path: '/home', component: Home, redirect: '/welcome', children: [ { path: "/welcome", component: Welcome }, { path: "/users", component: Users }, { path: "/rights", component: Rights }, { path: "/roles", component: Roles }, { path: "/categories", component: Cate }, { path: "/params", component: Params }, { path: "/goods", component: GoodList }, { path: "/goods/add", component: GoodAdd } ] ``` ### B.布局Add.vue组件 布局过程中需要使用Steps组件,在element.js中引入并注册该组件,并在global.css中给组件设置全局样式 ``` import {Steps,Step} from 'element-ui' Vue.use(Step) Vue.use(Steps) //global.css .el-steps{ margin:15px 0; } .el-step__title{ font-size: 13px; } ``` 然后再在Add.vue中进行页面布局 ``` ``` ### C.添加tab栏切换验证 也就是说不输入某些内容,无法切换到别的tab栏 ``` //首先给tabs添加tab切换前事件 ...... //再到methods编写事件函数beforeTabLeave beforeTabLeave(activeName,oldActiveName){ //在tab栏切换之前触发,两个形参为切换前,后的tab栏name if(oldActiveName === '0'){ //在第一个标签页的时候 if(this.addForm.goods_cat.length !== 3){ this.$message.error('请选择商品的分类') return false }else if(this.addForm.goods_name.trim() === ''){ this.$message.error('请输入商品名称') return false }else if(this.addForm.goods_price.trim() === '0'){ this.$message.error('请输入商品价格') return false }else if(this.addForm.goods_weight.trim() === '0'){ this.$message.error('请输入商品重量') return false }else if(this.addForm.goods_number.trim() === '0'){ this.$message.error('请输入商品数量') return false } } } ``` ### D.展示信息 展示商品参数信息,商品属性信息 在商品参数信息展示中使用的el-checkbox,el-checkbox-group组件,打开element.js引入组件并注册组件 ``` //在用户点击tab栏时触发事件 ........ //在参数信息,商品属性面板中添加循环生成结构的代码 //在data数据中添加保存动态参数和静态属性的数组 export default { data() { return { ...... //动态参数列表 manyTableData: [], //静态属性列表 onlyTableData:[] } },methods: { ....... async tabClicked() { //当用户点击切换tab栏时触发 if (this.activeIndex === '1') { //发送请求获取动态参数 const { data: res } = await this.$http.get( `categories/${this.cateId}/attributes`, { params: { sel: 'many' } } ) if (res.meta.status !== 200) { return this.$message.error('获取动态参数列表失败') } //将attr_vals字符串转换为数组 res.data.forEach(item => { item.attr_vals = item.attr_vals.length === 0 ? [] : item.attr_vals.split(' ') }) this.manyTableData = res.data } else if (this.activeIndex === '2') { //发送请求获取静态属性 const { data: res } = await this.$http.get( `categories/${this.cateId}/attributes`, { params: { sel: 'only' } } ) if (res.meta.status !== 200) { return this.$message.error('获取静态属性列表失败') } this.onlyTableData = res.data } } }, //添加 计算属性获取三级分类 computed: { cateId() { if (this.addForm.goods_cat.length === 3) { return this.addForm.goods_cat[2] } return null } } } ``` # ==今日目标== 1.完成商品添加 2.完成订单列表 3.完成数据统计展示 ## 1.添加商品 ### A.完成图片上传 使用upload组件完成图片上传 在element.js中引入upload组件,并注册 因为upload组件进行图片上传的时候并不是使用axios发送请求 所以,我们需要手动为上传图片的请求添加token,即为upload组件添加headers属性 ``` //在页面中添加upload组件,并设置对应的事件和属性 点击上传 //在el-card卡片视图下面添加对话框用来预览图片 //在data中添加数据 data(){ return { ...... //添加商品的表单数据对象 addForm: { goods_name: '', goods_price: 0, goods_weight: 0, goods_number: 0, goods_cat: [], //上传图片数组 pics: [] }, //上传图片的url地址 uploadURL: 'http://127.0.0.1:8888/api/private/v1/upload', //图片上传组件的headers请求头对象 headerObj: { Authorization: window.sessionStorage.getItem('token') }, //保存预览图片的url地址 previewPath: '', //控制预览图片对话框的显示和隐藏 previewVisible:false } }, //在methods中添加事件处理函数 methods:{ ....... handlePreview(file) { //当用户点击图片进行预览时执行,处理图片预览 //形参file就是用户预览的那个文件 this.previewPath = file.response.data.url //显示预览图片对话框 this.previewVisible = true }, handleRemove(file) { //当用户点击X号删除时执行 //形参file就是用户点击删除的文件 //获取用户点击删除的那个图片的临时路径 const filePath = file.response.data.tmp_path //使用findIndex来查找符合条件的索引 const index = this.addForm.pics.findIndex(item => item.pic === filePath) //移除索引对应的图片 this.addForm.pics.splice(index, 1) }, handleSuccess(response) { //当上传成功时触发执行 //形参response就是上传成功之后服务器返回的结果 //将服务器返回的临时路径保存到addForm表单的pics数组中 this.addForm.pics.push({ pic: response.data.tmp_path }) } } ``` ### B.使用富文本插件 想要使用富文本插件vue-quill-editor,就必须先从依赖安装该插件 引入并注册vue-quill-editor,打开main.js,编写如下代码 ``` //导入vue-quill-editor(富文本编辑器) import VueQuillEditor from 'vue-quill-editor' //导入vue-quill-editor的样式 import 'quill/dist/quill.core.css' import 'quill/dist/quill.snow.css' import 'quill/dist/quill.bubble.css' ...... //全局注册组件 Vue.component('tree-table', TreeTable) //全局注册富文本组件 Vue.use(VueQuillEditor) ``` 使用富文本插件vue-quill-editor ``` 添加商品 //在数据中添加goods_introduce //添加商品的表单数据对象 addForm: { goods_name: '', goods_price: 0, goods_weight: 0, goods_number: 0, goods_cat: [], //上传图片数组 pics: [], //商品的详情介绍 goods_introduce:'' } //在global.css样式中添加富文本编辑器的最小高度 .ql-editor{ min-height: 300px; } //给添加商品按钮添加间距 .btnAdd{ margin-top:15px; } ``` ### C.添加商品 完成添加商品的操作 在添加商品之前,为了避免goods_cat数组转换字符串之后导致级联选择器报错 我们需要打开vue控制条,点击依赖,安装lodash,把addForm进行深拷贝 ``` //打开Add.vue,导入lodash ``` ### D.推送代码 推送goods_list分支到码云 将代码添加到暂存区: git add . 将代码提交到本地仓库: git commit -m "完成商品功能开发" 将代码推送到码云: git push 切换到master主分支: git checkout master 将goods_list分支代码合并到master: git merge goods_list 将master推送到码云: git push ## 2.订单列表 ### A.创建分支 创建order子分支并推送到码云 创建order子分支: git checkout -b order 将order分支推送到码云: git push -u origin order ### B.创建路由 创建订单列表路由组件并添加路由规则 ``` //在components中新建order文件夹,新建Order.vue组件,组件中添加代码如下 //打开router.js导入Order.vue并添加规则 import Order from './components/order/Order.vue' path: '/home', component: Home, redirect: '/welcome', children: [ { path: "/welcome", component: Welcome }, { path: "/users", component: Users }, { path: "/rights", component: Rights }, { path: "/roles", component: Roles }, { path: "/categories", component: Cate }, { path: "/params", component: Params }, { path: "/goods", component: GoodList }, { path: "/goods/add", component: GoodAdd }, { path: "/orders", component: Order } ] ``` ### C.实现数据展示及分页 ``` ``` ### D.制作省市区县联动 打开今天的资料,找到素材文件夹,复制citydata.js文件到components/order文件夹中 然后导入citydata.js文件 ``` ``` 具体代码如下: ``` //给修改地址按钮添加点击事件 //添加修改地址对话框,在卡片视图下方添加 取 消 确 定 //js部分的代码 ``` ### E.制作物流进度对话框 因为我们使用的是element-ui中提供的Timeline组件,所以需要导入并注册组件 打开element.js,编写代码会进行导入和注册 ``` import { Timeline,TimelineItem } from 'element-ui' Vue.use(Timeline) Vue.use(TimelineItem) ``` 打开Order.vue文件,添加代码实现物流进度对话框 ``` {{activity.context}} ``` ### F.推送代码 将order分支代码推送至码云 将代码添加到暂存区: git add . 将代码提交到本地仓库: git commit -m "完成订单列表功能开发" 将代码推送到码云: git push 切换到master主分支: git checkout master 将goods_list分支代码合并到master: git merge order 将master推送到码云: git push ## 3.数据统计 ### A.创建子分支 创建report子分支并推送到码云 创建report子分支: git checkout -b report 将report分支推送到码云: git push -u origin report ### B.创建路由 创建数据统计路由组件并添加路由规则 ``` //在components中新建report文件夹,新建Report.vue组件,组件中添加代码如下 ``` 打开router.js,导入Report.vue并设置路由规则 ``` import Report from './components/report/Report.vue' path: '/home', component: Home, redirect: '/welcome', children: [ { path: "/welcome", component: Welcome }, { path: "/users", component: Users }, { path: "/rights", component: Rights }, { path: "/roles", component: Roles }, { path: "/categories", component: Cate }, { path: "/params", component: Params }, { path: "/goods", component: GoodList }, { path: "/goods/add", component: GoodAdd }, { path: "/orders", component: Order }, { path: "/reports", component: Report } ] ``` ### C.导入ECharts并使用 ``` ``` ### D.推送代码 推送report分支到码云 将代码添加到暂存区: git add . 将代码提交到本地仓库: git commit -m "完成数据报表功能开发" 将代码推送到码云: git push 切换到master主分支: git checkout master 将report分支代码合并到master: git merge report 将master推送到码云: git push # ==今日目标== 1.完成项目优化 2.完成项目上线 ## 1.项目优化 实现步骤: A.生成打包报告,根据报告优化项目 B.第三方库启用CDN C.Element-UI组件按需加载 D.路由懒加载 E.首页内容定制 ## 2.添加进度条 给项目添加进度条效果,先打开项目控制台,打开依赖,安装nprogress 打开main.js,编写如下代码 ``` //导入进度条插件 import NProgress from 'nprogress' //导入进度条样式 import 'nprogress/nprogress.css' ..... //请求在到达服务器之前,先会调用use中的这个回调函数来添加请求头信息 axios.interceptors.request.use(config => { //当进入request拦截器,表示发送了请求,我们就开启进度条 NProgress.start() //为请求头对象,添加token验证的Authorization字段 config.headers.Authorization = window.sessionStorage.getItem("token") //必须返回config return config }) //在response拦截器中,隐藏进度条 axios.interceptors.response.use(config =>{ //当进入response拦截器,表示请求已经结束,我们就结束进度条 NProgress.done() return config }) ``` ## 3.根据报错修改代码 根据ESLint的警告提示更改对应的代码 在.prettierrc文件中更改设置"printWidth":200, 将每行代码的文字数量更改为200 ``` { "semi":false, "singleQuote":true, "printWidth":200 } ``` ## 4.执行build 安装一个插件(babel-plugin-transform-remove-console)在项目build阶段移除所有的console信息 打开项目控制台,点击依赖->开发依赖,输入babel-plugin-transform-remove-console,安装 打开babel.config.js,编辑代码如下: ``` //项目发布阶段需要用到的babel插件 const productPlugins = [] //判断是开发还是发布阶段 if(process.env.NODE_ENV === 'production'){ //发布阶段 productPlugins.push("transform-remove-console") } module.exports = { "presets": [ "@vue/app" ], "plugins": [ [ "component", { "libraryName": "element-ui", "styleLibraryName": "theme-chalk" } ], ...productPlugins ] } ``` ## 5.生成打包报告 A.命令行形式生成打包报告 vue-cli-service build --report B.在vue控制台生成打包报告 点击“任务”=>“build”=>“运行” 运行完毕之后点击右侧“分析”,“控制台”面板查看报告 ## 6.修改webpack的默认配置 默认情况下,vue-cli 3.0生成的项目,隐藏了webpack配置项,如果我们需要配置webpack 需要通过vue.config.js来配置。 在项目根目录中创建vue.config.js文件, ``` module.exports = { chainWebpack:config=>{ //发布模式 config.when(process.env.NODE_ENV === 'production',config=>{ //entry找到默认的打包入口,调用clear则是删除默认的打包入口 //add添加新的打包入口 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') }) } } ``` 补充: chainWebpack可以通过链式编程的形式,修改webpack配置 configureWebpack可以通过操作对象的形式,修改webpack配置 ## 7.加载外部CDN 默认情况下,依赖项的所有第三方包都会被打包到js/chunk-vendors.******.js文件中,导致该js文件过大 那么我们可以通过externals排除这些包,使它们不被打包到js/chunk-vendors.******.js文件中 ``` module.exports = { chainWebpack:config=>{ //发布模式 config.when(process.env.NODE_ENV === 'production',config=>{ //entry找到默认的打包入口,调用clear则是删除默认的打包入口 //add添加新的打包入口 config.entry('app').clear().add('./src/main-prod.js') //使用externals设置排除项 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') }) } } ``` 设置好排除之后,为了使我们可以使用vue,axios等内容,我们需要加载外部CDN的形式解决引入依赖项。 打开开发入口文件main-prod.js,删除掉默认的引入代码 ``` import Vue from 'vue' import App from './App.vue' import router from './router' // import './plugins/element.js' //导入字体图标 import './assets/fonts/iconfont.css' //导入全局样式 import './assets/css/global.css' //导入第三方组件vue-table-with-tree-grid import TreeTable from 'vue-table-with-tree-grid' //导入进度条插件 import NProgress from 'nprogress' //导入进度条样式 // import 'nprogress/nprogress.css' // //导入axios import axios from 'axios' // //导入vue-quill-editor(富文本编辑器) import VueQuillEditor from 'vue-quill-editor' // //导入vue-quill-editor的样式 // import 'quill/dist/quill.core.css' // import 'quill/dist/quill.snow.css' // import 'quill/dist/quill.bubble.css' axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/' //请求在到达服务器之前,先会调用use中的这个回调函数来添加请求头信息 axios.interceptors.request.use(config => { //当进入request拦截器,表示发送了请求,我们就开启进度条 NProgress.start() //为请求头对象,添加token验证的Authorization字段 config.headers.Authorization = window.sessionStorage.getItem("token") //必须返回config return config }) //在response拦截器中,隐藏进度条 axios.interceptors.response.use(config =>{ //当进入response拦截器,表示请求已经结束,我们就结束进度条 NProgress.done() return config }) Vue.prototype.$http = axios Vue.config.productionTip = false //全局注册组件 Vue.component('tree-table', TreeTable) //全局注册富文本组件 Vue.use(VueQuillEditor) //创建过滤器将秒数过滤为年月日,时分秒 Vue.filter('dateFormat',function(originVal){ const dt = new Date(originVal) const y = dt.getFullYear() const m = (dt.getMonth()+1+'').padStart(2,'0') const d = (dt.getDate()+'').padStart(2,'0') const hh = (dt.getHours()+'').padStart(2,'0') const mm = (dt.getMinutes()+'').padStart(2,'0') const ss = (dt.getSeconds()+'').padStart(2,'0') return `${y}-${m}-${d} ${hh}:${mm}:${ss}` }) new Vue({ router, render: h => h(App) }).$mount('# app') ``` 然后打开public/index.html添加外部cdn引入代码 ``` 电商后台管理系统
``` ## 8.定制首页内容 开发环境的首页和发布环境的首页展示内容的形式有所不同 如开发环境中使用的是import加载第三方包,而发布环境则是使用CDN,那么首页也需根据环境不同来进行不同的实现 我们可以通过插件的方式来定制首页内容,打开vue.config.js,编写代码如下: ``` module.exports = { chainWebpack:config=>{ config.when(process.env.NODE_ENV === 'production',config=>{ ...... //使用插件 config.plugin('html').tap(args=>{ //添加参数isProd args[0].isProd = true return args }) }) config.when(process.env.NODE_ENV === 'development',config=>{ config.entry('app').clear().add('./src/main-dev.js') //使用插件 config.plugin('html').tap(args=>{ //添加参数isProd args[0].isProd = false return args }) }) } } ``` 然后在public/index.html中使用插件判断是否为发布环境并定制首页内容 ``` <%= htmlWebpackPlugin.options.isProd ? '' : 'dev - ' %>电商后台管理系统 <% if(htmlWebpackPlugin.options.isProd){ %> ........ <% } %> ....... ``` ## 9.路由懒加载 当路由被访问时才加载对应的路由文件,就是路由懒加载。 路由懒加载实现步骤: 1.安装 @babel/plugin-syntax-dynamic-import 打开vue控制台,点击依赖->安装依赖->开发依赖->搜索@babel/plugin-syntax-dynamic-import 点击安装。 2.在babel.config.js中声明该插件,打开babel.config.js ``` //项目发布阶段需要用到的babel插件 const productPlugins = [] //判断是开发还是发布阶段 if(process.env.NODE_ENV === 'production'){ //发布阶段 productPlugins.push("transform-remove-console") } module.exports = { "presets": [ "@vue/app" ], "plugins": [ [ "component", { "libraryName": "element-ui", "styleLibraryName": "theme-chalk" } ], ...productPlugins, //配置路由懒加载插件 "@babel/plugin-syntax-dynamic-import" ] } ``` 3.将路由更改为按需加载的形式,打开router.js,更改引入组件代码如下: ``` import Vue from 'vue' import Router from 'vue-router' const Login = () => import(/* webpackChunkName:"login_home_welcome" */ './components/Login.vue') // import Login from './components/Login.vue' const Home = () => import(/* webpackChunkName:"login_home_welcome" */ './components/Home.vue') // import Home from './components/Home.vue' const Welcome = () => import(/* webpackChunkName:"login_home_welcome" */ './components/Welcome.vue') // import Welcome from './components/Welcome.vue' const Users = () => import(/* webpackChunkName:"user" */ './components/user/Users.vue') // import Users from './components/user/Users.vue' const Rights = () => import(/* webpackChunkName:"power" */ './components/power/Rights.vue') // import Rights from './components/power/Rights.vue' const Roles = () => import(/* webpackChunkName:"power" */ './components/power/Roles.vue') // import Roles from './components/power/Roles.vue' const Cate = () => import(/* webpackChunkName:"goods" */ './components/goods/Cate.vue') // import Cate from './components/goods/Cate.vue' const Params = () => import(/* webpackChunkName:"goods" */ './components/goods/Params.vue') // import Params from './components/goods/Params.vue' const GoodList = () => import(/* webpackChunkName:"goods" */ './components/goods/List.vue') // import GoodList from './components/goods/List.vue' const GoodAdd = () => import(/* webpackChunkName:"goods" */ './components/goods/Add.vue') // import GoodAdd from './components/goods/Add.vue' const Order = () => import(/* webpackChunkName:"order" */ './components/order/Order.vue') // import Order from './components/order/Order.vue' const Report = () => import(/* webpackChunkName:"report" */ './components/report/Report.vue') // import Report from './components/report/Report.vue' ``` ## 10.项目上线 ### A.通过node创建服务器 在vue_shop同级创建一个文件夹vue_shop_server存放node服务器 使用终端打开vue_shop_server文件夹,输入命令 npm init -y 初始化包之后,输入命令 npm i express -S 打开vue_shop目录,复制dist文件夹,粘贴到vue_shop_server中 在vue_shop_server文件夹中创建app.js文件,编写代码如下: ``` const express = require('express') const app = express() app.use(express.static('./dist')) app.listen(8998,()=>{ console.log("server running at http://127.0.0.1:8998") }) ``` 然后再次在终端中输入 node app.js ### B.开启gzip压缩 打开vue_shop_server文件夹的终端,输入命令:npm i compression -D 打开app.js,编写代码: ``` const express = require('express') const compression = require('compression') const app = express() app.use(compression()) app.use(express.static('./dist')) app.listen(8998,()=>{ console.log("server running at http://127.0.0.1:8998") }) ``` ### C.配置https服务 配置https服务一般是后台进行处理,前端开发人员了解即可。 首先,需要申请SSL证书,进入https://freessl.cn官网 在后台导入证书,打开今天资料/素材,复制素材中的两个文件到vue_shop_server中 打开app.js文件,编写代码导入证书,并开启https服务 ``` const express = require('express') const compression = require('compression') const https = require('https') const fs = require('fs') const app = express() //创建配置对象设置公钥和私钥 const options = { cert:fs.readFileSync('./full_chain.pem'), key:fs.readFileSync('./private.key') } app.use(compression()) app.use(express.static('./dist')) // app.listen(8998,()=>{ // console.log("server running at http://127.0.0.1:8998") // }) //启动https服务 https.createServer(options,app).listen(443) ``` 注意:因为我们使用的证书有问题,所以无法正常使用https服务 ### D.使用pm2管理应用 打开vue_shop_server文件夹的终端,输入命令:npm i pm2 -g 使用pm2启动项目,在终端中输入命令:pm2 start app.js --name 自定义名称 查看项目列表命令:pm2 ls 重启项目:pm2 restart 自定义名称 停止项目:pm2 stop 自定义名称 删除项目:pm2 delete 自定义名称