项目描述: 该项目是一套电商后台管理系统,用于管理用户账号、商品分类、商品信息、订单、数据统计等业务功能
主要功能模块: 用户登录 用户退出 用户管理 用户列表 权限管理 角色权限和权限列表 商品管理 商品分类、分类管理和参数管理 订单管理 数据统计 数据报表
技术栈:Vue、Vue-router、Element UI、axios、Echarts 后端用到了 Node.js
项目初始化:
前端 1.安装 Vue 脚手架
通过 vue ui
2.通过 Vue 脚手架创建项目 3.配置 Vue 路由 安装路由 4.配置 Element UI 组件库
添加插件 vue-cli-plugin-element
5.配置 axios 库
添加依赖 axios
6.初始化 git 远程仓库,托管到 github 或 码云
登录退出功能
- token
登录页面输入用户名和密码进行登录,服务器验证通过后生成该用户的token并返回
客户端存储该token,后续所有的请求都携带该token发送请求,服务端验证token是否通过
- 登录界面布局,通过element ui 组件实现 el-form el-form-item el-input el-button 字体图标
git-checkout -b login 创建分支,git branch 切换分支
-运行 serve 预览项目
yarn serve
main.js 入口文件 删除不必要文件
- 创建登录组件 在 component文件夹下创建 Login.vue 组件
<template>
<div>
登录组件
</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
</style>
- 在 router index.js 里面引入 Login from ./components/Login.vue
import Vue from 'vue'
import Router from 'vue-router'
import Login from '../components/Login.vue'
Vue.use(Router)
export default new Router({
routes: [
{ path: '/', redirect: '/login' }, // 路由重定向
{ path: '/login', component: Login }
]
})
然后在App.vue根组件中放一个路由占位符 router-view
<div id="app">
<router-view></router-view>
</div>
- 登录组件表单 安装less和less-loader Element ui 在plugins里面的element.js 按需引入
import Vue from 'vue'
import { Button, Form, FormItem, Input } from 'element-ui'
Vue.use(Button)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Input)
- 导入小图标 fonticon的使用
将图标下载的 fonts文件内容复制到 assets 在main.js中导入
import './assets/fonts/iconfont.css'
snore Toast 提示怎么关闭
type=“password” //
- v-model数据绑定
:model="form"
v-model="form.name"
<script>
export default {
data() {
return {
form: {
name: '',
region: '',
date1: '',
date2: '',
delivery: false,
type: [],
resource: '',
desc: ''
}
}
},
- 表单数据验证
:rules="rules"
prop=“name”
rules: {
name: [
{ required: true, message: '请输入活动名称', trigger: 'blur' },
{ min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
],
-
表单的重置 resetFields
-
登录组件登录前的预验证 validate
-
登录组件根据预验证是否发起请求 valid
-
登录组件配置弹窗提示 引入Message import {Message } from 'element-ui' Vue.prototype.$message = Message
this.message.success('登录成功')
-
登录组件登录成功后的行为 将登录成功之后的token,保存到客户端的sessionStorage中
-
路由导航守卫控制访问权限 用户没有登录,直接通过url访问特定页面,需要重新导航到登录页面 为路由对象,添加beforeEach导航守卫 router.beforeEach((to,from,next)=>{ if (to.path === '/login') return next() const tokenStr = window.sessionStorage.getItem('token') if(!tokenStr) return next('/login') next() })
-
退出功能 清空token methods:{ logout(){ window.sessionStorage.clear(); this.$router.push('/login') }
-
处理语法警告问题
新建.prettierrc
{
"semi": false,
"singleQuote": true,
"trailingComma": false
}
设置.eslintrc.js
module.exports = {
root: true,
env: {
node: true
},
extends: [
'plugin:vue/essential',
'@vue/standard'
],
parserOptions: {
parser: 'babel-eslint'
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'space-before-function-paren': 0
},
}
用户管理
用户列表
- 主页布局
-
- 主页header布局
-
- 左侧菜单栏
- 通过接口获取菜单数据
-
- 通过axios请求拦截器添加 token,保证拥有获取数据的权限
axios.interceptors.request.use(config => {
console.log(config)
config.headers.Authorization = window.sessionStorage.getItem('token')
return config
})
- 发起请求获取左侧菜单数据
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)
}
-
左侧菜单 UI 绘制
-
左侧菜单格式美化,颜色,图标,unique-opened 是否只保持一个子菜单的展开
-
实现左侧菜单的折叠和展开功能,动态绑定
-
实现首页路由的重定向
{
path: '/home', component: Home,
redirect: '/welcome',
children: [{ path: '/welcome', component: Welcome }]
}
]
-
左侧菜单栏改造为路由模式 在菜单栏上加router
-
用户列表开发
-
绘制用户列表的基本UI结构
-
获取用户列表数据
export default {
data(){
return{
queryInfo:{
query:'',
pagenum:1,
pagesize:2
},
userlist:[],
total:0
}
},
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)
}
},
}
- 渲染用户列表数据
<el-table :data="userlist" border stripe>
<el-table-column label="姓名" prop='username'></el-table-column>
<el-table-column label="邮箱" prop='email'></el-table-column>
<el-table-column label="电话" prop='mobil'></el-table-column>
<el-table-column label="角色" prop='role_name'></el-table-column>
<el-table-column label="状态" prop='mg_state'></el-table-column>
<el-table-column label="操作"></el-table-column>
</el-table>
- 为用户表格添加索引列
<el-table-column type="index"></el-table-column>
- 改造状态列的显示效果
<template slot-scope="scope">
<el-switch v-model="scope.row.mg_state">
</el-switch>
</template>
- 插槽形式自定义渲染
<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"></el-button>
<el-tooltip effect="dark" content="分配角色" placement="top" :enterable="false">
<el-button type="warning" icon="el-icon-setting" size="mini"></el-button>
</el-tooltip>
</template>
-
实现数据分页效果 pagination
-
实现用户数据的修改
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('更新用户状态成功!')
}
- 实现搜索的功能
v-model="queryInfo.query" 实现双向绑定
- 实现用户添加功能
<el-dialog
title="提示"
:visible.sync="addDialogVisible"
width="50%">
<span>这是一段信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click="addDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="addDialogVisible = false">确 定</el-button>
</span>
</el-dialog>
- 添加用户对话框中渲染一个添加用户的表单
<el-dialog
title="提示"
:visible.sync="addDialogVisible"
width="50%">
<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="addDialogVisible = false">确 定</el-button>
</span>
</el-dialog>
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'}
],
mobile:[
{required: true,message:'请输入手机', trigger:'blur'}
]
}
- 实现自定义规则 正则表达式验证手机和邮箱
- 实现添加用户表单的重置功能
- 添加用户的预验证功能
- 发起请求添加一个新用户
- 添加用户修改的操作
- 绘制修改用户表单
- 实现修改表单的关闭之后的重置操作
- 提交修改前表单预验证操作
- 修改用户信息的操作
- 实现用户删除操作
- 完成删除用户的操作
权限管理
- 开发权限列表对应规格
- 权限列表的基本页面布局
- 请求权限列表数据,将获取到的数据渲染成 table 表格
<div>
<el-breadcrumb separator="/">
<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>
- 角色列表路由的切换
- 角色列表的基础布局
- 渲染角色列表中的 table 数据
- 美化权限 UI 结构
- 分配权限的功能
- 优化树形控件,实现复选框功能
<el-tree :data="rightslist" :props="treeProps" show-checkbox node-key="id" default-expand-all
:default-checked-keys="defKeys"></el-tree>
- 已有权限的默认勾选功能
- 完成具体分配权限的功能
- 用户分配角色的功能
商品管理
商品分类用于在购物时,快速找到所需要购买的商品,可以通过电商平台主页直观的看到 商品分类
- 商品分类路由的加载 在components文件夹下面创建goods文件夹,创建Cate.vue 然后在路由router中引入:import Cate from '../components/goods/Cate.vue', 然后在子路由规则中添加: { path: '/categories', component: Cate }
- 绘制商品分类页面的基本结构 头部面包屑导航和添加分类按钮
<div>
<!--面包屑导航区域-->
<el-breadcrumb separator="/">
<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-card>
</div>
- 获取商品分类的数据列表
<script>
export default {
data(){
return {
// 查询条件
queryInfo:{
type:3,
pagenum:1,
pagesize:5
},
// 商品分类的数据列表,默认为空
catelist:[],
// 总数据条数
total:0
}
},
created(){
this.getCateList()
},
methods:{
async getCateList(){
const {data:res} = await this.$http.get('categories',{params:this.queryInfo})
if(res.meta.status !==200){
return this.$message.error('获取商品分类失败!')
}
console.log(res.data)
// 把数据列表赋值给 catelist
this.catelist = res.data.result
// 为总数据条数赋值
this.total = res.data.total
}
}
}
</script>
- 将商品分类的数据渲染为树形表格 先按照官网文档安装 vue-table-with-tree-grid 插件, 在入口文件main.js中导入
import TreeTable from 'vue-table-with-tree-grid' // 导入组件
Vue.component('tree-table', TreeTable) // 注册为全局可用的组件
在Cate.vue中使用
<tree-table :data="catelist" :columns="columns" :selection-type="false" :expand-type="false" :show-index="true" index-text="#" border="true" :show-row-hover="false"></tree-table>
columns:[{
label: '分类名称',
prop: 'cat_name',
}]
- 用自定义模板列的形式将后三列的模板渲染出来
用作用域插槽
<tree-table :data="catelist" :columns="columns" :selection-type="false" :expand-type="false" :show-index="true" index-text="#" border="true" :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>
</tree-table>
columns:[{
label: '分类名称',
prop: 'cat_name',
},
{
label:'是否有效',
// 表示将当前列定义为模板列
type:'template',
// 表示当前这一列使用模板名称
template:'isok'
}
]
- 渲染排序和操作这两列
<!--排序-->
<template slot="order" slot-scope="scope">
<el-tag size="mini" v-if="scope.row.cat_level === 0">一级</el-tag>
<el-tag size="mini" type="success" v-else-if="scope.row.cat_level === 1">二级</el-tag>
<el-tag size="mini" type="warning" v-else>三级</el-tag>
</template>
<!--操作-->
<template slot="opt">
<el-button size="mini" type="primary" icon="el-icon-edit"></el-button>
<el-button size="mini" type="danger" icon="el-icon-delete"></el-button>
</template>
{
label:'排序',
// 表示将当前列定义为模板列
type:'template',
// 表示当前这一列使用模板名称
template:'order'
},
{
label:'操作',
// 表示将当前列定义为模板列
type:'template',
// 表示当前这一列使用模板名称
template:'opt'
}
- 商品分类的分页效果
<!--分页区域-->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryInfo.pagenum"
:page-sizes="[1, 5, 10, 15]"
:page-size="queryInfo.pagesize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
在methods监听事件
// 监听pagesize的变化
handleSizeChange(newSize){
this.queryInfo.pagesize = newSize
this.getCateList()
},
// 监听pagenum的变化
handleCurrentChange(newPage){
this.queryInfo.pagenum = newPage
this.getCateList()
}
- 添加分类的操作,点击添加分类会出现对话框
<!--添加分类的对话框-->
<el-dialog
title="添加分类"
:visible.sync="addCateDialogVisible"
width="50%">
<!--添加分类的表单-->
<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>
<span slot="footer" class="dialog-footer">
<el-button @click="addCateDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="addCateDialogVisible = false">确 定</el-button>
</span>
</el-dialog>
// 控制分类对话框的显示与隐藏
addCateDialogVisible: false,
// 添加分类的表单数据对象
addCateForm:{
// 将要添加分类的名称
cat_name:'',
// 父级分类的ID
cat_id: 0,
// 分类的等级默认要添加的是1级分类
cat_level: 0
},
// 添加分类表单的验证规则对象
addCateFormRules:{
cat_name:[
{
required:true,message:'请输入分类名称',trigger:'blur'
}
]
}
// 点击按钮,显示添加分类的对话框
showAddCateDialog(){
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
}
- 渲染父级分类的级联选择器 Cascader级联选择器 先按需引入Cascader组件
<el-cascader
props.checkStrictly = true
props.expandTrigger="hover"
:options="parentCateList"
:props="cascaderProps" v-model="selectedKeys"
@change="parentCateChanged" clearable>
</el-cascader
// 指定级联选择器的配置对象
cascaderProps:{
value:'cat_id',
label:'cat_name',
children:'children',
},
// 选中的父级分类的ID数组
selectedKeys:[]
}
引入出现Cascader是出现选项框内的信息被遮住,在global.css引入下面代码即可
.el-cascader-panel{
height: 200px;
}
- 处理添加分类的表单数据
// 点击按钮,添加新的分类
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
}
参数管理 商品参数用于显示商品的固定的特征信息,可以通过电商平台商品详情页面直观的看到
商品列表