vue项目实战-电商后台管理系统
Posted 晓宜
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue项目实战-电商后台管理系统相关的知识,希望对你有一定的参考价值。
项目简介:
该项目为电商后台的管理系统。设计了登录页面。
管理人员需要通过输入正确的用户名和密码才能登录。登陆成功之后进入管理页面:
管理页面由五个子模块组成:用户管理,权限管理,商品管理,订单管理,数据统计;
每个子模块有若干子模块组成,用户管理下->用户列表,权限管理->角色列表,权限管理,商品管理->商品列表,分类参数,商品分配,订单管理->订单列表,数据统计->数据报表
登录页面
登录页面中对用户输入的内容进行预校验,如果不符合要求则,则不向后端发送请求,同事挂载路由守卫,防止强制跳转。同时设置令牌校验,避免重复登录。如果用户输入格式正确的用户名以及密码时,向后端发送请求,请求通过则跳转到管理页面,否则返回登录页面。
路由导航守卫:
// 挂载路由导航守卫
router.beforeEach((to, from, next) =>
// to 将要访问的路径
// from 代表从哪个路径跳转而来
// next 是一个函数,表示放行
// next() 放行 next('/login') 强制跳转
if (to.path === '/login') return next()
// 获取token
const tokenStr = window.sessionStorage.getItem('token')
if (!tokenStr) return next('/login')
next()
)
登录页面核心代码:
<template>
<div class="login_container">
<div class="login_box">
<!-- 头像区域 -->
<div class="avatar_box">
<img src="../assets/logo.png" alt="">
</div>
<!-- 登录表单区域 -->
<el-form ref="loginFormRef" :model="loginForm" :rules="loginFormRules" label-width="0px" class="login_form">
<!-- 用户名 -->
<el-form-item prop="username">
<el-input v-model="loginForm.username" prefix-icon="iconfont icon-user"></el-input>
</el-form-item>
<!-- 密码 -->
<el-form-item prop="password">
<el-input v-model="loginForm.password" prefix-icon="iconfont icon-3702mima" type="password"></el-input>
</el-form-item>
<!-- 按钮区域 -->
<el-form-item class="btns">
<el-button type="primary" @click="login">登录</el-button>
<el-button type="info" @click="resetLoginForm">重置</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
export default
data()
return
// 这是登录表单的数据绑定对象
loginForm:
username: 'admin',
password: '123456'
,
// 这是表单的验证规则对象
loginFormRules:
// 验证用户名是否合法
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'
]
,
methods:
// 点击重置按钮,重置登录表单
resetLoginForm()
// console.log(this);
this.$refs.loginFormRef.resetFields()
,
login()
this.$refs.loginFormRef.validate(async valid =>
if (!valid) return
const data: res = await this.$http.post('login', this.loginForm)
if (res.meta.status !== 200) return this.$message.error('登录失败!')
this.$message.success('登录成功')
// 1. 将登录成功之后的 token,保存到客户端的 sessionStorage 中
// 1.1 项目中出了登录之外的其他API接口,必须在登录之后才能访问
// 1.2 token 只应在当前网站打开期间生效,所以将 token 保存在 sessionStorage 中
window.sessionStorage.setItem('token', res.data.token)
// 2. 通过编程式导航跳转到后台主页,路由地址是 /home
this.$router.push('/home')
)
</script>
<style lang="less" scoped>
.login_container
background-color: #2b4b6b;
height: 100%;
.login_box
width: 450px;
height: 300px;
background-color: #fff;
border-radius: 3px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
.avatar_box
height: 130px;
width: 130px;
border: 1px solid #eee;
border-radius: 50%;
padding: 10px;
box-shadow: 0 0 10px #ddd;
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
img
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #eee;
.login_form
position: absolute;
bottom: 0;
width: 100%;
padding: 0 20px;
box-sizing: border-box;
.btns
display: flex;
justify-content: flex-end;
</style>
菜单实现
管理页面有一个侧面的两级菜单,菜单的数据来自于后端,点击二级菜单会跳转到相应的子页面中。在el-menu中设置router属性,即可通过index添加到路由上进行跳转。
<template>
<el-container class="home-container">
<!-- 头部区域 -->
<el-header>
<div>
<img src="../assets/heima.png" alt="">
<span>电商后台管理系统</span>
</div>
<el-button type="info" @click="logout">退出</el-button>
</el-header>
<!-- 页面主体区域 -->
<el-container>
<!-- 侧边栏 -->
<el-aside :width="isCollapse ? '64px' : '200px'">
<div class="toggle-button" @click="toggleCollapse">|||</div>
<!-- 侧边栏菜单区域 -->
<el-menu background-color="#333744" text-color="#fff" active-text-color="#409EFF" unique-opened :collapse="isCollapse" :collapse-transition="false" router :default-active="activePath">
<!-- 一级菜单 -->
<el-submenu :index="item.id + ''" v-for="item in menulist" :key="item.id">
<!-- 一级菜单的模板区域 -->
<template slot="title">
<!-- 图标 -->
<i :class="iconsObj[item.id]"></i>
<!-- 文本 -->
<span>item.authName</span>
</template>
<!-- 二级菜单 -->
<el-menu-item :index="'/' + subItem.path" v-for="subItem in item.children"
:key="subItem.id" @click="saveNavState('/' + subItem.path)">
<template slot="title">
<!-- 图标 -->
<i class="el-icon-menu"></i>
<!-- 文本 -->
<span>subItem.authName</span>
</template>
</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<!-- 右侧内容主体 -->
<el-main>
<!-- 路由占位符 -->
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</template>
<script>
export default
data()
return
// 左侧菜单数据
menulist: [],
iconsObj:
'125': 'iconfont icon-user',
'103': 'iconfont icon-tijikongjian',
'101': 'iconfont icon-shangpin',
'102': 'iconfont icon-danju',
'145': 'iconfont icon-baobiao'
,
// 是否折叠
isCollapse: false,
// 被激活的链接地址
activePath: ''
,
created()
this.getMenuList()
this.activePath = window.sessionStorage.getItem('activePath')
,
methods:
logout()
window.sessionStorage.clear()
this.$router.push('/login')
,
// 获取所有的菜单
async getMenuList()
const data: res = await this.$http.get('menus')
if (res.meta.status !== 200) return this.$message.error(res.meta.msg)
this.menulist = res.data
console.log(res)
,
// 点击按钮,切换菜单的折叠与展开
toggleCollapse()
this.isCollapse = !this.isCollapse
,
// 保存链接的激活状态
saveNavState(activePath)
window.sessionStorage.setItem('activePath', activePath)
this.activePath = activePath
</script>
<style lang="less" scoped>
.home-container
height: 100%;
.el-header
background-color: #373d41;
display: flex;
justify-content: space-between;
padding-left: 0;
align-items: center;
color: #fff;
font-size: 20px;
> div
display: flex;
align-items: center;
span
margin-left: 15px;
.el-aside
background-color: #333744;
.el-menu
border-right: none;
.el-main
background-color: #eaedf1;
.iconfont
margin-right: 10px;
.toggle-button
background-color: #4a5064;
font-size: 10px;
line-height: 24px;
color: #fff;
text-align: center;
letter-spacing: 0.2em;
cursor: pointer;
</style>
用户管理
用户列表
用户管理下有用户列表,这里渲染了后端的用户列表,可以编辑用户信息,删除用户,为用户分配角色,还可以对用户是否禁用进行管理;除此之外,还添加了查询用户,添加用户,和分页功能。
核心代码:
<template>
<div>
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to=" path: '/home' ">首页</el-breadcrumb-item>
<el-breadcrumb-item>用户管理</el-breadcrumb-item>
<el-breadcrumb-item>用户列表</el-breadcrumb-item>
</el-breadcrumb>
<!-- 卡片视图区域 -->
<el-card>
<!-- 搜索与添加区域 -->
<el-row :gutter="20">
<el-col :span="8">
<el-input placeholder="请输入内容" v-model="queryInfo.query" clearable @clear="getUserList">
<el-button slot="append" icon="el-icon-search" @click="getUserList"></el-button>
</el-input>
</el-col>
<el-col :span="4">
<el-button type="primary" @click="addDialogVisible = true">添加用户</el-button>
</el-col>
</el-row>
<!-- 用户列表区域 -->
<el-table :data="userlist" border stripe>
<el-table-column type="index"></el-table-column>
<el-table-column label="姓名" prop="username"></el-table-column>
<el-table-column label="邮箱" prop="email"></el-table-column>
<el-table-column label="电话" prop="mobile"></el-table-column>
<el-table-column label="角色" prop="role_name"></el-table-column>
<el-table-column label="状态">
<template slot-scope="scope">
<el-switch v-model="scope.row.mg_state" @change="userStateChanged(scope.row)">
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" width="180px">
<template slot-scope="scope">
<!-- 修改按钮 -->
<el-button type="primary" icon="el-icon-edit" size="mini" @click="showEditDialog(scope.row.id)"></el-button>
<!-- 删除按钮 -->
<el-button type="danger" icon="el-icon-delete" size="mini" @click="removeUserById(scope.row.id)"></el-button>
<!-- 分配角色按钮 -->
<el-tooltip effect="dark" content="分配角色" placement="top" :enterable="false">
<el-button type="warning" icon="el-icon-setting" size="mini" @click="setRole(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="queryInfo.pagenum" :page-sizes="[1, 2, 5, 10]" :page-size="queryInfo.pagesize" layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</el-card>
<!-- 添加用户的对话框 -->
<el-dialog title="添加用户" :visible.sync="addDialogVisible" width="50%" @close="addDialogClosed">
<!-- 内容主体区域 -->
<el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="70px">
<el-form-item label="用户名" prop="username">
<el-input v-model="addForm.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="addForm.password"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="addForm.email"></el-input>
</el-form-item>
<el-form-item label="手机" prop="mobile">
<el-input v-model="addForm.mobile"></el-input>
</el-form-item>
</el-form>
<!-- 底部区域 -->
<span slot="footer" class="dialog-footer">
<el-button @click="addDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="addUser">确 定</el-button>
</span>
</el-dialog>
<!-- 修改用户的对话框 -->
<el-dialog title="修改用户" :visible.sync="editDialogVisible" width="50%" @close="editDialogClosed">
<el-form :model="editForm" :rules="editFormRules" ref="editFormRef" label-width="70px">
<el-form-item label="用户名">
<el-input v-model="editForm.username" disabled></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="editForm.email"></el-input>
</el-form-item>
<el-form-item label="手机" prop="mobile">
<el-input v-model="editForm.mobile"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="editUserInfo">确 定</el-button>
</span>
</el-dialog>
<!-- 分配角色的对话框 -->
<el-dialog title="分配角色" :visible.sync="setRoleDialogVisible" width="50%" @close="setRoleDialogClosed">
<div>
<p>当前的用户:userInfo.username</p>
<p>当前的角色:userInfo.role_name</p>
<p>分配新角色:
<el-select v-model="selectedRoleId" placeholder="请选择">
<el-option v-for="item in rolesList" :key="item.id" :label="item.roleName" :value="item.id">
</el-option>
</el-select>
</p>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="setRoleDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="saveRoleInfo">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default
data()
// 验证邮箱的规则
var checkEmail = (rule, value, cb) =>
// 验证邮箱的正则表达式
const regEmail = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\\.[a-zA-Z0-9_-])+/
if (regEmail.test(value))
// 合法的邮箱
return cb()
cb(new Error('请输入合法的邮箱'))
// 验证手机号的规则
var checkMobile = (rule, value, cb) =>
// 验证手机号的正则表达式
const regMobile = /^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]8$/
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, trigger: 'blur'
],
mobile: [
required: true, message: '请输入手机号', trigger: 'blur' ,
validator: checkMobile, trigger: 'blur'
]
,
// 控制修改用户对话框的显示与隐藏
editDialogVisible: false,
// 查询到的用户信息对象
editForm: ,
// 修改表单的验证规则对象
editFormRules:
email: [
required: true, message: '请输入用户邮箱', trigger: 'blur' ,
validator: checkEmail, trigger: 'blur'
],
mobile: [
required: true, message: '请输入用户手机', trigger: 'blur' ,
validator: checkMobile, trigger: 'blur'
]
,
// 控制分配角色对话框的显示与隐藏
setRoleDialogVisible: false,
// 需要被分配角色的用户信息
userInfo: ,
// 所有角色的数据列表
rolesList: [],
// 已选中的角色Id值
selectedRoleId: ''
,
created()
this.getUserList()
,
methods:
async getUserList()
const data: res = await this.$http.get('users',
params: this.queryInfo
)
if (res.meta.status !== 200)
return this.$message.error('获取用户列表失败!')
this.userlist = res.data.users
this.total = res.data.total
console.log(res)
,
// 监听 pagesize 改变的事件
handleSizeChange(newSize)
// console.log(newSize)
this.queryInfo.pagesize = newSize
this.getUserList()
,
// 监听 页码值 改变的事件
handleCurrentChange(newPage)
console.log(newPage)
this.queryInfo.pagenum = newPage
this.getUserList()
,
// 监听 switch 开关状态的改变
async userStateChanged(userinfo)
console.log(userinfo)
const data: res = await this.$http.put(
`users/$userinfo.id/state/$userinfo.mg_state`
)
if (res.meta.status !== 200)
userinfo.mg_state = !userinfo.mg_state
return this.$message.error('更新用户状态失败!')
this.$message.success('更新用户状态成功!')
,
// 监听添加用户对话框的关闭事件
addDialogClosed()
this.$refs.addFormRef.resetFields()
,
// 点击按钮,添加新用户
addUser()
this.$refs.addFormRef.validate(async valid =>
if (!valid) return
// 可以发起添加用户的网络请求
const data: res = await this.$http.post('users', this.addForm)
if (res.meta.status !== 201)
this.$message.error('添加用户失败!')
this.$message.success('添加用户成功!')
// 隐藏添加用户的对话框
this.addDialogVisible = false
// 重新获取用户列表数据
this.getUserList()
)
,
// 展示编辑用户的对话框
async showEditDialog(id)
// console.log(id)
const data: res = await this.$http.get('users/' + id)
if (res.meta.status !== 200)
return this.$message.error('查询用户信息失败!')
this.editForm = res.data
this.editDialogVisible = true
,
// 监听修改用户对话框的关闭事件
editDialogClosed()
this.$refs.editFormRef.resetFields()
,
// 修改用户信息并提交
editUserInfo()
this.$refs.editFormRef.validate(async valid =>
if (!valid) return
// 发起修改用户信息的数据请求
const data: res = await this.$http.put(
'users/' + this.editForm.id,
email: this.editForm.email,
mobile: this.editForm.mobile
)
if (res.meta.status !== 200)
return this.$message.error('更新用户信息失败!')
// 关闭对话框
this.editDialogVisible = false
// 刷新数据列表
this.getUserList()
// 提示修改成功
this.$message.success('更新用户信息成功!')
)
,
// 根据Id删除对应的用户信息
async removeUserById(id)
// 弹框询问用户是否删除数据
const confirmResult = await this.$confirm(
'此操作将永久删除该用户, 是否继续?',
'提示',
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
).catch(err => err)
// 如果用户确认删除,则返回值为字符串 confirm
// 如果用户取消了删除,则返回值为字符串 cancel
// console.log(confirmResult)
if (confirmResult !== 'confirm')
return this.$message.info('已取消删除')
const data: res = await this.$http.delete('users/' + id)
if (res.meta.status !== 200)
return this.$message.error('删除用户失败!')
this.$message.success('删除用户成功!')
this.getUserList()
,
// 展示分配角色的对话框
async setRole(userInfo)
this.userInfo = userInfo
// 在展示对话框之前,获取所有角色的列表
const data: res = await this.$http.get('roles')
if (res.meta.status !== 200)
return this.$message.error('获取角色列表失败!')
this.rolesList = res.data
this.setRoleDialogVisible = true
,
// 点击按钮,分配角色
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 =
</script>
<style lang="less" scoped>
</style>
权限管理
角色列表
角色列表中可以创建新的角色,创建的新的角色可以在用户管理中赋予用户,同时可以为已有的角色赋予权限
<template>
<div>
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to=" path: '/home' ">首页</el-breadcrumb-item>
<el-breadcrumb-item>权限管理</el-breadcrumb-item>
<el-breadcrumb-item>角色列表</el-breadcrumb-item>
</el-breadcrumb>
<!-- 卡片视图 -->
<el-card>
<!-- 添加角色按钮区域 -->
<el-row>
<el-col>
<el-button type="primary">添加角色</el-button>
</el-col>
</el-row>
<!-- 角色列表区域 -->
<el-table :data="rolelist" border stripe>
<!-- 展开列 -->
<el-table-column type="expand">
<template slot-scope="scope">
<el-row :class="['bdbottom', i1 === 0 ? 'bdtop' : '', 'vcenter']" v-for="(item1, i1) in scope.row.children" :key="item1.id">
<!-- 渲染一级权限 -->
<el-col :span="5">
<el-tag closable @close="removeRightById(scope.row, item1.id)">item1.authName</el-tag>
<i class="el-icon-caret-right"></i>
</el-col>
<!-- 渲染二级和三级权限 -->
<el-col :span="19">
<!-- 通过 for 循环 嵌套渲染二级权限 -->
<el-row :class="[i2 === 0 ? '' : 'bdtop', 'vcenter']" v-for="(item2, i2) in item1.children" :key="item2.id">
<el-col :span="6">
<el-tag type="success" closable @close="removeRightById(scope.row, item2.id)">item2.authName</el-tag>
<i class="el-icon-caret-right"></i>
</el-col>
<el-col :span="18">
<el-tag type="warning" v-for="item3 in item2.children" :key="item3.id" closable @close="removeRightById(scope.row, item3.id)">item3.authName</el-tag>
</el-col>
</el-row>
</el-col>
</el-row>
<!-- <pre>
scope.row
</pre> -->
</template>
</el-table-column>
<!-- 索引列 -->
<el-table-column type="index"></el-table-column>
<el-table-column label="角色名称" prop="roleName"></el-table-column>
<el-table-column label="角色描述" prop="roleDesc"></el-table-column>
<el-table-column label="操作" width="300px">
<template slot-scope="scope">
<el-button size="mini" type="primary" icon="el-icon-edit">编辑</el-button>
<el-button size="mini" type="danger" icon="el-icon-delete">删除</el-button>
<el-button size="mini" type="warning" icon="el-icon-setting" @click="showSetRightDialog(scope.row)">分配权限</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 分配权限的对话框 -->
<el-dialog title="分配权限" :visible.sync="setRightDialogVisible" width="50%" @close="setRightDialogClosed">
<!-- 树形控件 -->
<el-tree :data="rightslist" :props="treeProps" show-checkbox node-key="id" default-expand-all :default-checked-keys="defKeys" ref="treeRef"></el-tree>
<span slot="footer" class="dialog-footer">
<el-button @click="setRightDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="allotRights">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default
data()
return
// 所有角色列表数据
rolelist: [],
// 控制分配权限对话框的显示与隐藏
setRightDialogVisible: false,
// 所有权限的数据
rightslist: [],
// 树形控件的属性绑定对象
treeProps:
label: 'authName',
children: 'children'
,
// 默认选中的节点Id值数组
defKeys: [],
// 当前即将分配权限的角色id
roleId: ''
,
created()
this.getRolesList()
,
methods:
// 获取所有角色的列表
async getRolesList()
const data: res = await this.$http.get('roles')
if (res.meta.status !== 200)
return this.$message.error('获取角色列表失败!')
this.rolelist = res.data
console.log(this.rolelist)
,
// 根据Id删除对应的权限
async removeRightById(role, rightId)
// 弹框提示用户是否要删除
const confirmResult = await this.$confirm(
'此操作将永久删除该文件, 是否继续?',
'提示',
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
).catch(err => err)
if (confirmResult !== 'confirm')
return this.$message.info('取消了删除!')
const data: res = await this.$http.delete(
`roles/$role.id/rights/$rightId`
)
if (res.meta.status !== 200)
return this.$message.error('删除权限失败!')
// this.getRolesList()
role.children = res.data
,
// 展示分配权限的对话框
async showSetRightDialog(role)
this.roleId = role.id
// 获取所有权限的数据
const data: res = await this.$http.get('rights/tree')
if (res.meta.status !== 200)
return this.$message.error('获取权限数据失败!')
// 把获取到的权限数据保存到 data 中
this.rightslist = res.data
console.log(this.rightslist)
// 递归获取三级节点的Id
this.getLeafKeys(role, this.defKeys)
this.setRightDialogVisible = true
,
// 通过递归的形式,获取角色下所有三级权限的id,并保存到 defKeys 数组中
getLeafKeys(node, arr)
// 如果当前 node 节点不包含 children 属性,则是三级节点
if (!node.children)
return arr.push(node.id)
node.children.forEach(item => this.getLeafKeys(item, arr))
,
// 监听分配权限对话框的关闭事件
setRightDialogClosed()
this.defKeys = []
,
// 点击为角色分配权限
async allotRights()
const keys = [
...this.$refs.treeRef.getCheckedKeys(),
...this.$refs.treeRef.getHalfCheckedKeys()
]
const idStr = keys.join(',')
const data: res = await this.$http.post(
`roles/$this.roleId/rights`,
rids: idStr
)
if (res.meta.status !== 200)
return this.$message.error('分配权限失败!')
this.$message.success('分配权限成功!')
this.getRolesList()
this.setRightDialogVisible = false
</script>
<style lang="less" scoped>
.el-tag
margin: 7px;
.bdtop
border-top: 1px solid #eee;
.bdbottom
border-bottom: 1px solid #eee;
.vcenter
display: flex;
align-items: center;
</style>
权限列表
权限列表对不同的权限做出展示,只发送一个请求即可获取所有需要的数据
<template>
<div>
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to=" path: '/home' ">首页</el-breadcrumb-item>
<el-breadcrumb-item>权限管理</el-breadcrumb-item>
<el-breadcrumb-item>权限列表</el-breadcrumb-item>
</el-breadcrumb>
<!-- 卡片视图 -->
<el-card>
<el-table :data="rightsList" border stripe>
<el-table-column type="index"></el-table-column>
<el-table-column label="权限名称" prop="authName"></el-table-column>
<el-table-column label="路径" prop="path"></el-table-column>
<el-table-column label="权限等级" prop="level">
<template slot-scope="scope">
<el-tag v-if="scope.row.level === '0'">一级</el-tag>
<el-tag type="success" v-else-if="scope.row.level === '1'">二级</el-tag>
<el-tag type="warning" v-else>三级</el-tag>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script>
export default
data()
return
// 权限列表
rightsList: []
,
created()
// 获取所有的权限
this.getRightsList()
,
methods:
// 获取权限列表
async getRightsList()
const data: res = await this.$http.get('rights/list')
if (res.meta.status !== 200)
return this.$message.error('获取权限列表失败!')
this.rightsList = res.data
console.log(this.rightsList)
</script>
<style lang="less" scoped>
</style>
商品管理
商品分类
<template>
<div>
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to=" path: '/home' ">首页</el-breadcrumb-item>
<el-breadcrumb-item>商品管理</el-breadcrumb-item>
<el-breadcrumb-item>商品分类</el-breadcrumb-item>
</el-breadcrumb>
<!-- 卡片视图区域 -->
<el-card>
<el-row>
<el-col>
<el-button type="primary" @click="showAddCateDialog">添加分类</el-button>
</el-col>
</el-row>
<!-- 表格 -->
<tree-table class="treeTable" :data="catelist" :columns="columns" :selection-type="false" :expand-type="false" show-index index-text="#" border :show-row-hover="false">
<!-- 是否有效 -->
<template slot="isok" slot-scope="scope">
<i class="el-icon-success" v-if="scope.row.cat_deleted === false" style="color: lightgreen;"></i>
<i class="el-icon-error" v-else style="color: red;"></i>
</template>
<!-- 排序 -->
<template slot="order" slot-scope="scope">
<el-tag size="mini" v-if="scope.row.cat_level===0">一级</el-tag>
<el-tag type="success" size="mini" v-else-if="scope.row.cat_level===1">二级</el-tag>
<el-tag type="warning" size="mini" v-else>三级</el-tag>
</template>
<!-- 操作 -->
<template slot="opt">
<el-button type="primary" icon="el-icon-edit" size="mini">编辑</el-button>
<el-button type="danger" icon="el-icon-delete" size="mini">删除</el-button>
</template>
</tree-table>
<!-- 分页区域 -->
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="querInfo.pagenum" :page-sizes="[3, 5, 10, 15]" :page-size="querInfo.pagesize" layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</el-card>
<!-- 添加分类的对话框 -->
<el-dialog title="添加分类" :visible.sync="addCateDialogVisible" width="50%" @close="addCateDialogClosed">
<!-- 添加分类的表单 -->
<el-form :model="addCateForm" :rules="addCateFormRules" ref="addCateFormRef" label-width="100px">
<el-form-item label="分类名称:" prop="cat_name">
<el-input v-model="addCateForm.cat_name"></el-input>
</el-form-item>
<el-form-item label="父级分类:">
<!-- options 用来指定数据源 -->
<!-- props 用来指定配置对象 -->
<el-cascader expand-trigger="hover" :options="parentCateList" :props="cascaderProps" v-model="selectedKeys" @change="parentCateChanged" clearable change-on-select>
</el-cascader>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="addCateDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="addCate">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default
data()
return
// 查询条件
querInfo:
type: 3,
pagenum: 1,
pagesize: 5
,
// 商品分类的数据列表,默认为空
catelist: [],
// 总数据条数
total: 0,
// 为table指定列的定义
columns: [
label: '分类名称',
prop: 'cat_name'
,
label: '是否有效',
// 表示,将当前列定义为模板列
type: 'template',
// 表示当前这一列使用模板名称
template: 'isok'
,
label: '排序',
// 表示,将当前列定义为模板列
type: 'template',
// 表示当前这一列使用模板名称
template: 'order'
,
label: '操作',
// 表示,将当前列定义为模板列
type: 'template',
// 表示当前这一列使用模板名称
template: 'opt'
],
// 控制添加分类对话框的显示与隐藏
addCateDialogVisible: false,
// 添加分类的表单数据对象
addCateForm:
// 将要添加的分类的名称
cat_name: '',
// 父级分类的Id
cat_pid: 0,
// 分类的等级,默认要添加的是1级分类
cat_level: 0
,
// 添加分类表单的验证规则对象
addCateFormRules:
cat_name: [ required: true, message: '请输入分类名称', trigger: 'blur' ]
,
// 父级分类的列表
parentCateList: [],
// 指定级联选择器的配置对象
cascaderProps:
value: 'cat_id',
label: 'cat_name',
children: 'children'
,
// 选中的父级分类的Id数组
selectedKeys: []
,
created()
this.getCateList()
,
methods:
// 获取商品分类数据
async getCateList()
const data: res = await this.$http.get('categories',
params: this.querInfo
)
if (res.meta.status !== 200)
return this.$message.error('获取商品分类失败!')
console.log(res.data)
// 把数据列表,赋值给 catelist
this.catelist = res.data.result
// 为总数据条数赋值
this.total = res.data.total
,
// 监听 pagesize 改变
handleSizeChange(newSize)
this.querInfo.pagesize = newSize
this.getCateList()
,
// 监听 pagenum 改变
handleCurrentChange(newPage)
this.querInfo.pagenum = newPage
this.getCateList()
,
// 点击按钮,展示添加分类的对话框
showAddCateDialog()
// 先获取父级分类的数据列表
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('获取父级分类数据失败!')
console.log(res.data)
this.parentCateList = res.data
,
// 选择项发生变化触发这个函数
parentCateChanged()
console.log(this.selectedKeys)
// 如果 selectedKeys 数组中的 length 大于0,证明选中的父级分类
// 反之,就说明没有选中任何父级分类
if (this.selectedKeys.length > 0)
// 父级分类的Id
this.addCateForm.cat_pid = this.selectedKeys[this.selectedKeys.length - 1]
// 为当前分类的等级赋值
this.addCateForm.cat_level = this.selectedKeys.length
else
// 父级分类的Id
this.addCateForm.cat_pid = 0
// 为当前分类的等级赋值
this.addCateForm.cat_level = 0
,
// 点击按钮,添加新的分类
addCate()
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
)
,
// 监听对话框的关闭事件,重置表单数据
addCateDialogClosed()
this.$refs.addCateFormRef.resetFields()
this.selectedKeys = []
this.addCateForm.cat_level = 0
this.addCateForm.cat_pid = 0
</script>
<style lang="less" scoped>
.treeTable
margin-top: 15px;
.el-cascader
width: 100%;
</style>
商品列表
<template>
<div>
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to=" path: '/home' ">首页</el-breadcrumb-item>
<el-breadcrumb-item>商品管理</el-breadcrumb-item>
<el-breadcrumb-item>商品列表</el-breadcrumb-item>
</el-breadcrumb>
<!-- 卡片视图区域 -->
<el-card>
<el-row :gutter="20">
<el-col :span="8">
<el-input placeholder="请输入内容" v-model="queryInfo.query" clearable @clear="getGoodsList">
<el-button slot="append" icon="el-icon-search" @click="getGoodsList"></el-button>
</el-input>
</el-col>
<el-col :span="4">
<el-button type="primary" @click="goAddpage">添加商品</el-button>
</el-col>
</el-row>
<!-- table表格区域 -->
<el-table :data="goodslist" border stripe>
<el-table-column type="index"></el-table-column>
<el-table-column label="商品名称" prop="goods_name"></el-table-column>
<el-table-column label="商品价格(元)" prop="goods_price" width="95px"></el-table-column>
<el-table-column label="商品重量" prop="goods_weight" width="70px"></el-table-column>
<el-table-column label="创建时间" prop="add_time" width="140px">
<template slot-scope="scope">
scope.row.add_time | dateFormat
</template>
</el-table-column>
<el-table-column label="操作" width="130px">
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" size="mini"></el-button>
<el-button type="danger" icon="el-icon-delete" size="mini" @click="removeById(scope.row.goods_id)"></el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="queryInfo.pagenum" :page-sizes="[5, 10, 15, 20]" :page-size="queryInfo.pagesize" layout="total, sizes, prev, pager, next, jumper" :total="total" background>
</el-pagination>
</el-card>
</div>
</template>
<script>
export default
data()
return
// 查询参数对象
queryInfo:
query: '',
pagenum: 1,
pagesize: 10
,
// 商品列表
goodslist: [],
// 总数据条数
total: 0
,
created()
this.getGoodsList()
,
methods:
// 根据分页获取对应的商品列表
async getGoodsList()
const data: res = await this.$http.get('goods',
params: this.queryInfo
)
if (res.meta.status !== 200)
return this.$message.error('获取商品列表失败!')
this.$message.success('获取商品列表成功!')
console.log(res.data)
this.goodslist = res.data.goods
this.total = res.data.total
,
handleSizeChange(newSize)
this.queryInfo.pagesize = newSize
this.getGoodsList()
,
handleCurrentChange(newPage)
this.queryInfo.pagenum = newPage
this.getGoodsList()
,
async removeById(id)
const confirmResult = await this.$confirm(
'此操作将永久删除该商品, 是否继续?',
'提示',
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
).catch(err => err)
if (confirmResult !== 'confirm')
return this.$message.info('已经取消删除!')
const data: res = await this.$http.delete(`goods/$id`)
if (res.meta.status !== 200)
return this.$message.error('删除失败!')
this.$message.success('删除成功!')
this.getGoodsList()
,
goAddpage()
this.$router.push('/goods/add')
</script>
<style lang="less" scoped>
</style>
增加商品
在商品分类中点击新增商品,则跳转到新增商品窗口
<template>
<div>
<!-- 面包屑导航区域 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to=" path: '/home' ">首页</el-breadcrumb-item>
<el-breadcrumb-item>商品管理</el-breadcrumb-item>
<el-breadcrumb-item>添加商品</el-breadcrumb-item>
</el-breadcrumb>
<!-- 卡片视图 -->
<el-card>
<!-- 提示区域 -->
<el-alert title="添加商品信息" type="info" center show-icon :closable="false">
</el-alert>
<!-- 步骤条区域 -->
<el-steps :space="200" :active="activeIndex - 0" finish-status="success" align-center>
<el-step title="基本信息"></el-step>
<el-step title="商品参数"></el-step>
<el-step title="商品属性"></el-step>
<el-step title="商品图片"></el-step>
<el-step title="商品内容"></el-step>
<el-step title="完成"></el-step>
</el-steps>
<!-- tab栏区域 -->
<el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="100px" label-position="top">
<el-tabs v-model="activeIndex" :tab-position="'left'" :before-leave="beforeTabLeave" @tab-click="tabClicked">
<el-tab-pane label="基本信息" name="0">
<el-form-item label="商品名称" prop="goods_name">
<el-input v-model="addForm.goods_name"></el-input>
</el-form-item>
<el-form-item label="商品价格" prop="goods_price">
<el-input v-model="addForm.goods_price" type="number"></el-input>
</el-form-item>
<el-form-item label="商品重量" prop="goods_weight">
<el-input v-model="addForm.goods_weight" type="number"></el-input>
</el-form-item>
<el-form-item label="商品数量" prop="goods_number">
<el-input v-model="addForm.goods_number" type="number"></el-input>
</el-form-item>
<el-form-item label="商品分类" prop="goods_cat">
<el-cascader expand-trigger="hover" :options="catelist" :props="cateProps" v-model="addForm.goods_cat" @change="handleChange">
</el-cascader>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="商品参数" name="1">
<!-- 渲染表单的Item项 -->
<el-form-item :label="item.attr_name" v-for="item in manyTableData" :key="item.attr_id">
<!-- 复选框组 -->
<el-checkbox-group v-model="item.attr_vals">
<el-checkbox :label="cb" v-for="(cb, i) in item.attr_vals" :key="i" border></el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="商品属性" name="2">
<el-form-item :label="item.attr_name" v-for="item in onlyTableData" :key="item.attr_id">
<el-input v-model="item.attr_vals"></el-input>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="商品图片" name="3">
<!-- action 表示图片要上传到的后台API地址 -->
<el-upload :action="uploadURL" :on-preview="handlePreview" :on-remove="handleRemove" list-type="picture" :headers="headerObj" :on-success="handleSuccess">
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
</el-tab-pane>
<el-tab-pane label="商品内容" name="4">
<!-- 富文本编辑器组件 -->
<quill-editor v-model="addForm.goods_introduce"></quill-editor>
<!-- 添加商品的按钮 -->
<el-button type="primary" class="btnAdd" @click="add">添加商品</el-button>
</el-tab-pane>
</el-tabs>
</el-form>
</el-card>
<!-- 图片预览 -->
<el-dialog title="图片预览" :visible.sync="previewVisible" width="50%">
<img :src="previewPath" alt="" class="previewImg">
</el-dialog>
</div>
</template>
<script>
import _ from 'lodash'
export default
data()
return
activeIndex: '0',
// 添加商品的表单数据对象
addForm:
goods_name: '',
goods_price: 0,
goods_weight: 0,
goods_number: 0,
// 商品所属的分类数组
goods_cat: [],
// 图片的数组
pics: [],
// 商品的详情描述
goods_introduce: '',
attrs: []
,
addFormRules:
goods_name: [
required: true, message: '请输入商品名称', trigger: 'blur'
],
goods_price: [
required: true, message: '请输入商品价格', trigger: 'blur'
],
goods_weight: [
required: true, message: '请输入商品重量', trigger: 'blur'
],
goods_number: [
required: true, message: '请输入商品数量', trigger: 'blur'
],
goods_cat: [
required: true, message: '请选择商品分类', trigger: 'blur'
]
,
// 商品分类列表
catelist: [],
cateProps:
label: 'cat_name',
value: 'cat_id',
children: 'children'
,
// 动态参数列表数据
manyTableData: [],
// 静态属性列表数据
onlyTableData: [],
// 上传图片的URL地址
uploadURL: 'http://127.0.0.1:8888/api/private/v1/upload',
// 图片上传组件的headers请求头对象
headerObj:
Authorization: window.sessionStorage.getItem('token')
,
previewPath: '',
previewVisible: false
,
created()
this.getCateList()
,
methods:
// 获取所有商品分类数据
async getCateList()
const data: res = await this.$http.get('categories')
if (res.meta.status !== 200)
return this.$message.error('获取商品分类数据失败!')
this.catelist = res.data
console.log(this.catelist)
,
// 级联选择器选中项变化,会触发这个函数
handleChange()
console.log(this.addForm.goods_cat)
if (this.addForm.goods_cat.length !== 3)
this.addForm.goods_cat = []
,
beforeTabLeave(activeName, oldActiveName)
// console.log('即将离开的标签页名字是:' + oldActiveName)
// console.log('即将进入的标签页名字是:' + activeName)
// return false
if (oldActiveName === '0' && this.addForm.goods_cat.length !== 3)
this.$message.error('请先选择商品分类!')
return false
,
async tabClicked()
// console.log(this.activeIndex)
// 证明访问的是动态参数面板
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('获取动态参数列表失败!')
console.log(res.data)
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('获取静态属性失败!')
console.log(res.data)
this.onlyTableData = res.data
,
// 处理图片预览效果
handlePreview(file)
console.log(file)
this.previewPath = file.response.data.url
this.previewVisible = true
,
// 处理移除图片的操作
handleRemove(file)
// console.log(file)
// 1. 获取将要删除的图片的临时路径
const filePath = file.response.data.tmp_path
// 2. 从 pics 数组中,找到这个图片对应的索引值
const i = this.addForm.pics.findIndex(x => x.pic === filePath)
// 3. 调用数组的 splice 方法,把图片信息对象,从 pics 数组中移除
this.addForm.pics.splice(i, 1)
console.log(this.addForm)
,
// 监听图片上传成功的事件
handleSuccess(response)
console.log(response)
// 1. 拼接得到一个图片信息对象
const picInfo = pic: response.data.tmp_path
// 2. 将图片信息对象,push 到pics数组中
this.addForm.pics.push(picInfo)
console.log(this.addForm)
,
// 添加商品
add()
this.$refs.addFormRef.validate(async valid =>
if (!valid)
return this.$message.error('请填写必要的表单项!')
// 执行添加的业务逻辑
// lodash cloneDeep(obj)
const form = _.cloneDeep(this.addForm)
form.goods_cat = form.基于 Vue2 的尚品汇电商前后台项目
开发时间:2022.11.07-2022.12.07
前台项目 后台管理 atguigu-store-frontend atguigu-store-backend
文章目录
一、快速开始
二、系统概述
2.1 项目简介
此项目为尚品汇在线电商应用,前后端分离开发。前端项目包括前台应用系统及后台管理系统两大部分。前台项目基于 Vue 和前端三件套,后台项目基于 vue-admin-template 和 element-ui 组件库。
采用模块化、组件化、工程化的模式开发,基于最新最热的前端技术如 Vue 全家桶、ES6、Webpack、Axios 等,包括首页、商品搜索列表、商品详情、购物车、订单、支付、用户登录与注册等多个子模块,功能齐全。
2.2 技术选型
三、功能展示
3.1 前台项目
-
三级联动导航分类
-
swiper 轮播图
-
商品搜索
-
面包屑搜索与结果排序过滤
-
分页条
-
商品详情查看
-
商品图片轮播与细节查看
-
商品加入游客购物车
-
购物车详情
-
用户注册
-
用户登录
-
购物车商品下单
-
订单提交
-
订单支付
-
支付成功
-
订单列表
3.2 后台管理
-
管理员登录
-
数据可视化
-
品牌管理
-
平台售卖属性管理
-
SPU(Standard Product Unit) 管理
-
SPU 详情查看
-
SKU(Stock Keeping Unit ) 管理
以上是关于vue项目实战-电商后台管理系统的主要内容,如果未能解决你的问题,请参考以下文章
Vue项目实战:电商后台管理系统(Vue+VueRouter+Axios+Element)