电商后台管理系统的技术选型
登录业务流程
登录业务的相关技术点
登录 - token原理分析
登录功能实现
登录状态保持
登录逻辑
添加新分支login,在login分支中开发当前项目vue_shop:
打开vue_shop终端,使用git status确定当前项目状态。
确定当前工作目录是干净的之后,创建一个分支进行开发,开发完毕之后将其合并到master
git checkout -b login
然后查看新创建的分支:git branch
打开项目的src目录,点击main.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')
再打开App.vue(根组件),将根组件的内容进行操作梳理(template中留下根节点,script中留下默认导出,去掉组件,style中去掉所有样式)
再打开router.js(路由),将routes数组中的路由规则清除,然后将views删除,将components中的helloworld.vue删除
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
]
})
在components文件夹中新建Login.vue组件,添加template,script,style标签,style标签中的scoped可以防止组件之间的样式冲突,没有scoped则样式是全局的
在router.js中导入组件并设置规则
在App.vue中添加路由占位符
const router = new Router({
routes: [
{ path: '/', redirect: '/login' },
{ path: '/login', component: Login }
]
})
当我们给Login.vue中的内容添加样式的时候,会报错“缺少less-loader”,需要配置less加载器(开发依赖),安装less(开发依赖)
然后需要添加公共样式,在assets文件夹下面添加css文件夹,创建global.css文件,添加全局样式
/* 全局样式表 */
html,body,#app{
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
在main.js中导入global.css,使得全局样式生效 import “./assets/css/global.css”
Login.vue文件中的代码如下
登录
重置
添加element-ui的表单组件
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)
添加第三方字体
添加表单验证的步骤
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' }
]
}
导入axios以发送ajax请求
设置请求的根路径:axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/';
挂载axios:Vue.prototype.$http = axios;
.配置弹窗提示
登录成功之后的操作
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')
})
}
添加一个组件Home.vue,并为之添加规则
this is home
退出
添加路由规则
const router = new Router({
routes: [
{ path: '/', redirect: '/login' },
{ path: '/login', component: Login },
{ path: '/home', component: Home }
]
})
添加路由守卫
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
实现退出功能
export default {
methods:{
logout(){
window.sessionStorage.clear();
this.$router.push('/login');
}
}
}
侧边菜单栏
电商后台管理系统
退出
导航一
子菜单一
Main
通过接口获取菜单数据
通过axios请求拦截器添加token,保证拥有获取数据的权限
后台除了登录接口之外,都需要token权限验证,我们可以通过添加axios请求拦截器来添加token,以保证拥有获取数据的权限
请求侧边栏数据
请求侧边栏数据
{{item.authName}}
{{subItem.authName}}
设置激活子菜单样式
通过更改el-menu的active-text-color属性可以设置侧边栏菜单中点击的激活项的文字颜色
通过更改菜单项模板(template)中的i标签的类名,可以将左侧菜单栏的图标进行设置,我们需要在项目中使用第三方字体图标
iconsObj: {
'125':'iconfont icon-user',
'103':'iconfont icon-tijikongjian',
'101':'iconfont icon-shangpin',
'102':'iconfont icon-danju',
'145':'iconfont icon-baobiao'
}
为了保持左侧菜单每次只能打开一个,显示其中的子菜单,我们可以在el-menu中添加一个属性unique-opened或者也可以数据绑定进行设置(此时true认为是一个bool值,而不是字符串) :unique-opened=“true”
制作侧边菜单栏的伸缩功能
在后台首页添加子级路由
完成用户列表主体区域
绘制用户列表基本结构
使用element-ui面包屑组件完成顶部导航路径(复制面包屑代码,在element.js中导入组件Breadcrumb,BreadcrumbItem)
使用element-ui卡片组件完成主体表格(复制卡片组件代码,在element.js中导入组件Card),再
使用element-ui输入框完成搜索框及搜索按钮,
此时需要使用栅格布局来划分结构(复制卡片组件代码,在element.js中导入组件Row,Col),然后再使用el-button制作添加用户按钮
用户列表组件
首页
用户管理
用户列表
添加用户
请求用户列表数据
将用户列表数据展示
使用表格来展示用户列表数据,使用element-ui表格组件完成列表展示数据(复制表格代码,在element.js中导入组件Table,TableColumn)
在渲染展示状态时,会使用作用域插槽获取每一行的数据
再使用switch开关组件展示状态信息(复制开关组件代码,在element.js中导入组件Switch)
而渲染操作列时,也是使用作用域插槽来进行渲染的,在操作列中包含了修改,删除,分配角色按钮,当我们把鼠标放到分配角色按钮上时希望能有一些文字提示,此时我们需要使用文字提示组件(复制文字提示组件代码,在element.js中导入组件Tooltip),将分配角色按钮包含
实现用户列表分页
使用表格来展示用户列表数据,可以使用分页组件完成列表分页展示数据(复制分页组件代码,在element.js中导入组件Pagination)
更改组件中的绑定数据
添加两个事件的事件处理函数@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();
}
实现更新用户状态
当用户点击列表中的switch组件时,用户的状态应该跟随发生改变。
首先监听用户点击switch组件的事件,并将作用域插槽的数据当做事件参数进行传递
在事件中发送请求完成状态的更改
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('更新状态成功')
},
实现搜索功能
添加数据绑定,添加搜索按钮的点击事件(当用户点击搜索按钮的时候,调用getUserList方法根据文本框内容重新请求用户列表数据)
当在输入框中输入内容并点击搜索之后,会按照搜索关键字搜索,我们希望能够提供一个X删除搜索关键字并重新获取所有的用户列表数据,只需要给文本框添加clearable属性并添加clear事件,在clear事件中重新请求数据即可
实现添加用户
当我们点击添加用户按钮的时候,弹出一个对话框来实现添加用户的功能,首先我们需要复制对话框组件的代码并在element.js文件中引入Dialog组件
接下来我们要为“添加用户”按钮添加点击事件,在事件中将addDialogVisible设置为true,即显示对话框
更改Dialog组件中的内容
添加数据绑定和校验规则
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'}
]
}
}
}
当关闭对话框时,重置表单
给el-dialog添加@close事件,在事件中添加重置表单的代码
addDialogClosed(){
//对话框关闭之后,重置表达
this.$refs.addFormRef.resetFields();
}
点击对话框中的确定按钮,发送请求完成添加用户的操作
首先给确定按钮添加点击事件,在点击事件中完成业务逻辑代码
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()
})
}
在使用图形化 vue-cli 创建项目时,默认启用了 Eslint 规则,启动项目时报错
利用格式化代码,顺着 Eslint 规范来
在项目根路径创建 .prettierrc 文件,文件内容 如下
{
"semi":false,//去除当前文件的分号
"singleQuote":true//把当前文件中所有的双引号替换为单引号
}
直接关闭 Eslint,在 package.json 中添加如下代码
"vue":{
"lintOnSave": false
}
修改用户信息
为用户列表中的修改按钮绑定点击事件
在页面中添加修改用户对话框,并修改对话框的属性
根据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
}
在弹出窗中添加修改用户信息的表单并做响应的数据绑定以及数据验证
数据绑定以及验证
//控制修改用户对话框的显示与否
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'
}
]
}
监听对话框关闭事件,在对话框关闭后,重置表单
editDialogClosed(){
//对话框关闭之后,重置表达
this.$refs.editFormRef.resetFields()
}
在用户点击确定按钮的时候,验证数据成功之后发送请求完成修改
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()
})
}
删除用户
导入MessageBox组件,并将MessageBox组件挂载到实例。
给用户列表中的删除按钮添加事件,并在事件处理函数中弹出确定取消窗,最后再根据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()
}
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 }
]
......
一级权限
二级权限
三级权限
path: '/home', component: Home, redirect: '/welcome', children: [
{ path: "/welcome", component: Welcome },
{ path: "/users", component: Users },
{ path: "/rights", component: Rights },
{ path: "/roles", component: Roles }
]
编辑
删除
分配权限
{{item1.authName}}
{{item2.authName}}
{{item3.authName}}
美化样式
添加权限删除功能
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();
}
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
}
当前的用户:{{userInfo.username}}
当前的角色:{{userInfo.role_name}}
分配新角色:
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;
}
}
当前的用户:{{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 = {}
}
}
商品分类
创建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 }
]
添加组件基本布局
在Cate.vue组件中添加面包屑导航以及卡片视图中的添加分类按钮
商品分类
首页
商品管理
商品分类
添加分类
请求分类数据
请求分类数据并将数据保存在data中
使用插件展示数据
使用第三方插件vue-table-with-tree-grid展示分类数据
//全局注册组件
Vue.component('tree-table', TreeTable
自定义数据列
使用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'}
]
一级
二级
三级
编辑
删除
完成分页功能
//添加对应的事件函数
methods:{
.......
handleSizeChange(newSize){
//当pagesize发生改变时触发
this.queryInfo.pagesize = newSize;
this.getCateList();
},
handleCurrentChange(newPage){
//当pagenum发生改变时触发
this.queryInfo.pagenum = newPage;
this.getCateList();
}
}
完成添加分类
添加分类
......
//用来显示或隐藏添加分类对话框
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
})
}
}
参数管理
只允许给三级分类内容设置参数,参数分为动态参数和静态参数属性
添加子级组件
添加Params.vue子组件,并在router.js中引入该组件并设置路由规则
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 }
]
完成组件基本布局
完成Params.vue组件的基本布局
分类参数
首页
商品管理
分类参数
选择商品分类:
完成级联选择框
选择商品分类:
......
展示参数
添加参数
编辑
删除
添加属性
编辑
删除
数据展示
添加数据表格展示数据以及分页功能的实现,搜索功能的实现
在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}`
})
添加功能代码
添加商品
{{scope.row.add_time | dateFormat}}
//绑定数据以及添加方法
实现删除商品
//绑定按钮点击事件
//事件函数代码编写
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()
}
添加商品
在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 }
]
布局Add.vue组件
添加商品
首页
商品管理
添加商品
添加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
}
}
}
展示信息
在商品参数信息展示中使用的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
}
}
}
创建路由
创建订单列表路由组件并添加路由规则
//在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 }
]
实现数据展示及分页
已付款
未付款
{{scope.row.create_time | dateFormat}}
制作省市区县联动
//给修改地址按钮添加点击事件
//添加修改地址对话框,在卡片视图下方添加
//js部分的代码
制作物流进度对话框
打开element.js
import {
Timeline,TimelineItem
} from 'element-ui'
Vue.use(Timeline)
Vue.use(TimelineItem)
打开order.vue组件
{{activity.context}}
数据统计
创建路由
//在components中新建report文件夹,新建Report.vue组件,组件中添加代码如下
数据报表
首页
数据统计
数据报表
打开router.js
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 }
]
导入ECharts并使用
数据报表
首页
数据统计
数据报表
实现步骤:
A.生成打包报告,根据报告优化项目
B.第三方库启用CDN
C.Element-UI组件按需加载
D.路由懒加载
E.首页内容定制
//导入进度条插件
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
})
{
"semi":false,
"singleQuote":true,
"printWidth":200
}
//项目发布阶段需要用到的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
]
}
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')
})
}
}
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')
})
}
}
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')
电商后台管理系统
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
})
})
}
}
<%= htmlWebpackPlugin.options.isProd ? '' : 'dev - ' %>电商后台管理系统
<% if(htmlWebpackPlugin.options.isProd){ %>
........
<% } %>
.......
//项目发布阶段需要用到的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"
]
}
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'
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
开启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")
})
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)
的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”
]
}
- 将路由更改为按需加载的形式,打开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'
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
开启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")
})
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)