Vue 电商PC后台管理(ElementUI)
Vue 电商PC后台管理(ElementUI)
1.项目概述
1.1电商项目基本业务概述
根据不同的场景,电商系统一般都提供了PC端、移动 APP、移动 Web、微信小程序等多种终端访问方式。
1.2电商后台管理系统的功能
电商后台 管理系统同用于管理用户账号、商品分类、商品信息、订单、数据统计等业务功能。
1.3电商后台管理系统的开发模式(前后端分离)
电商后台管理系统整体采用前后端分离的开发模式,其中前端项目是基于Vue技术栈的SPA项目。
1.4电商后台管理系统的技术选型
1.前端项目技术栈
- Vue
- Vue-router
- Element-UI
- Axios
- Echarts
2.后端项目技术栈
- Node.js
- Express
- Jwt
- Mysql
- Sequelize
2.项目初始化
2.1前端项目初始化步骤
① 安装vue脚手架
② 通过 vue 脚手架创建项目
③ 配置 vue 路由
④ 配置 Element-UI 组件库
⑤ 配置 axios 库
⑥ 初始化 git 远程仓库
⑦ 将本地项目托管到 Github 或码云中
2.2后台项目的环境安装配置
① 安装MySQL数据库
② 安装Node.js 环境
③ 配置项目相关信息
④ 启动项目
⑤ 使用Postman 测试后台项目接口是否正常
3.登录/退出功能
3.1登录概述
1.登录业务流程
① 在登录页面输入用户名和密码
② 调用后台接口进行验证
③ 通过验证之后,根据后台的响应状态跳转到项目主页
2.登录业务的相关技术点
- http是无状态的
- 通过cookie在客户端记录状态
- 通过session在服务器端记录状态
- 通过token方式维持状态
3.2登录 —— token 原理分析
3.3登录功能实现
1.登录页面的布局
通过Element-UI组件实现布局
- el-form
- el-form-item
- el-input
- el-button
- 字体图标
首先打开 vue-shop 项目
- 查看文件,文件夹在工作区,暂存区的状态
git status
- 创建并切换
login
分支
git checkout -b login
- 查看所有分支
git branch
2.Login.vue 文件解读
这是一个 Vue 单文件组件
祖传 Vue 布局如下
<template>
<div>
</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scope>
</style>
我们需要在
<template>
标签中进行布局在
<script>
标签中编写用户行为和数据定义在
<style>
标签中定义样式
我用到了 ElementUI 库,主要是记录一下这个库的用法
先放上源码,我会对下面的代码进行解读
<template>
<div class="login_container">
<div class="login_box">
<!-- 头像区域 -->
<div class="avatar_box">
<img src="../assets/logo.png" alt="">
</div>
<!-- 登录表单区域 -->
<el-form :model="loginForm" :rules="loginFormRules" ref="loginFormRef" 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" type="password" prefix-icon="iconfont icon-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: "",
password: ""
},
// 这是登录表单验证
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: 16, message: '长度在 6 到 16 个字符', trigger: 'blur' }
]
}
}
},
methods: {
resetLoginForm(){
// 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果
this.$refs.loginFormRef.resetFields()
},
login(){
this.$refs.loginFormRef.validate((valid)=>{
console.log(valid);
})
}
}
}
</script>
<style lang="less" scope>
.login_container {
background: #2b4b6b;
height: 100%;
position: relative;
}
.login_box {
width: 450px;
height: 300px;
background: #ffffff;
border-radius: 3px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.avatar_box {
width: 130px;
height: 130px;
border: solid 1px #eee;
border-radius: 50%;
background: #ffffff;
padding: 10px;
box-shadow: 0 0 10px #ddd;
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
img {
width: 100%;
height: 100%;
border-radius: 50%;
background: #eee;
}
}
.login_form {
position: absolute;
bottom: 0;
width: 100%;
padding: 0 10px;
box-sizing: border-box;
}
.btns {
display: flex;
justify-content: flex-end;
}
</style>
在
<el-form>
标签中添加:model
属性,这是登录表单的数据绑定对象,Ta需要和<el-input>
标签中的v-module
配合使用
:rules
是登录表单验证,Ta需要在 JS 区域的 data 中返回一个登录表单验证对象,其中的属性为需要验证的内容,如何使用?在el-form-item
标签中新增prop
属性,使它的值为刚刚在 data 中定义的属性值
<el-input>
标签中的prefix-icon
属性是用来设置前置字体图标的,我引用的阿里矢量图库,也可以通过suffix-icon
属性在 input 组件的尾部增加显示图标。
<el-form>
表单中的ref
属性是为当前表单注册一个实例对象,Ta的属性名就是这个实例对象的名称,比如说我自定义的名称为loginFormRef
,那我就可以通过this.$refs.loginFormRef
来访问到这个实例对象。
resetFields
可以通过this.$refs.loginFormRef.resetFields()
调用此方法,此方法的作用是:对整个表单进行重置,将所有字段值重置为初始值并移除校验结果。
validate
可以通过this.$refs.loginFormRef.validate()
调用此方法,此方法的作用是:对整个表单进行校验的方法,参数为一个回调函数。该回调函数会在校验结束后被调用,并传入两个参数:是否校验成功和未通过校验的字段。若不传入回调函数,则会返回一个 promise。Function(callback: Function(boolean, object))
3.路由导航守卫控制访问权限
如果用户没有登录,但是直接通过URL访问特定页面,需要重新导航到登录页面
// 为路由对象,添加 beforeEach 导航守卫
router.beforeEach((to, from, next) => {
// 如果用户访问的登录页,直接放行
if (to.path === "/login") return next()
// 从 sessionStorage 中获取到保存的 token 值
const tokenStr = window.sessionStorage.getItem("token")
// 没有 token ,强制跳转到登录页
if (!tokenStr) return next("/login")
next()
})
3.4退出
退出功能实现原理
基于token 的方式实现退出比较简单,只需要销毁本地的token即可。这样,后续的请求就不会携带token,必须重新登录生成一个新的token之后才可以访问页面。
// 清空token
window.sessionStorage.clear()
// 跳转到登录页
this.$router.push('/login')
解决 eslintrc 格式化报错问题
在项目中新建 .prettierrc
文件,文件内容如下
{
"semi": false,
"singleQuote": true
}
“semi”: false 表示移除分号(????
“singleQuote”: true 表示用单引号(’’)来表示字符串
最后在每个需要修改的文件中使用快捷键
Ctrl+Alt+\
进行格式化文档
在 .eslintrc.js
--> rules 下新建一条命令
"space-before-function-paren": 0
3.5将本地代码提交到码云上
- 查看文件,文件夹在工作区,暂存区的状态
git status
此时提示你的是红色信息
它会提示你
Changes not staged for commit:
尚未提交
- 把所有的文件都添加到暂存区
git add .
把所有的文件都添加到暂存区之后继续使用
git status
,提示信息就变成绿色了
- 把暂存区的所有代码提交到本地仓库中
git commit -m "完成了登录功能"
- 查看当前所在分支
git branch
当前分支是 login ,我们需要把 login 中的所有代码合并到 master 主分支中
注意:你需要先切换到 master 分支之后再合并 login
- 切换到 master 分支
git checkout master
出现以下提示说明成功
Switched to branch ‘master’
Your branch is up to date with ‘origin/master’.
- 如果没有成功删除
.gitignore
暂存文件,重新从第一步开始再使用
git branch
可以看到已经切换到了 master 主分支
我的 .gitignore
文件内容如下:
node_modules
- 基于 master 合并 login 中的所有文件
git merge login
- 把本地仓库推送到远程仓库
git push
但是我们发现码云中只要 master 一个分支,并没有 login 分支
所有我们需要将 login 也推送到码云中
- 首先切换到 login 分支
git checkout login
- 把 login 分支推送到码云
git push -u origin login
-u 是指定推送 login 到码云
4.主页布局
4.1整体布局
先上下划分,再左右划分
<el-container class="home-container">
<!-- 头部区域 -->
<el-header>
<div>
<img src="https://cdn.jsdelivr.net/gh/extheor/images/%E7%A0%81%E5%B0%8F%E4%BD%99.jpg" alt="">
<span>码小余后台管理</span>
</div>
<el-button type="info" @click="goBack">退出</el-button>
</el-header>
<!-- 页面主题区域 -->
<el-container>
<!-- 侧边栏 -->
<el-aside width="200px">Aside</el-aside>
<!-- 右侧内容主体 -->
<el-main>Main</el-main>
</el-container>
</el-container>
4.2左侧菜单布局
<el-menu background-color="#333744" text-color="#fff" active-text-color="#ffd04b">
<!-- 一级菜单 -->
<el-submenu index="1">
<!-- 一级菜单的模板区域 -->
<template slot="title">
<!-- 图标 -->
<i class="el-icon-location"></i>
<!-- 文本 -->
<span>导航一</span>
</template>
<!-- 二级菜单 -->
<el-menu-item index="1-4-1">
<!-- 图标 -->
<i class="el-icon-location"></i>
<!-- 文本 -->
<span>导航一</span>
</el-menu-item>
</el-submenu>
</el-menu>
4.3通过接口获取菜单数据
通过 axios 请求拦截器添加 token,保证拥有获取数据的权限。
// axios请求拦截
axios.interceptors.request.use((config) => {
// console.log(config);
config.headers.Authorization = window.sessionStorage.getItem("token");
return config;
});
Home.vue 文件解读
<template>
<el-container class="home-container">
<!-- 头部区域 -->
<el-header>
<div>
<img src="https://cdn.jsdelivr.net/gh/extheor/images/%E7%A0%81%E5%B0%8F%E4%BD%99.jpg" alt="">
<span>码小余后台管理</span>
</div>
<el-button type="info" @click="goBack">退出</el-button>
</el-header>
<!-- 页面主体区域 -->
<el-container>
<!-- 侧边栏 -->
<el-aside :width="isCollapse ? '64px' : '200px'">
<div class="toggle-button" @click="toggleCollapse"><span>|||</span></div>
<!--
:unique-opened - 表示是否只显示一个一级菜单
:collapse - 表示是否折叠左侧菜单
:collapse-transition - 是否开启默认折叠动画效果
:router - 是否开启前端路由
:default-active - 对当前点击的菜单高亮显示
-->
<el-menu background-color="#333744" text-color="#fff" active-text-color="#409EFF" :unique-opened="true" :collapse="isCollapse" :collapse-transition="false" :router="true" :default-active="activePath">
<!-- 一级菜单 -->
<el-submenu :index="item.id + ''" v-for="item in menuList" :key="item.id">
<!-- 一级菜单的模板区域 -->
<template slot="title">
<!-- 图标 -->
<i :class="iconObj[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)">
<!-- 图标 -->
<i class="el-icon-menu"></i>
<!-- 文本 -->
<span>{{ subItem.authName }}</span>
</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: [],
iconObj: {
"125": "iconfont icon-yonghuguanli",
"103": "iconfont icon-quanxianguanli",
"101": "iconfont icon-shangpinguanli",
"102": "iconfont icon-dingdanguanli",
"145": "iconfont icon-shujutongji"
},
// 是否折叠
isCollapse: false,
// 点击二级菜单高亮
activePath: ""
}
},
// 页面刷新的操作
created(){
this.getMenuList()
this.activePath = window.sessionStorage.getItem("activePath")
},
methods: {
goBack(){
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(this.menuList);
},
toggleCollapse(){
this.isCollapse = !this.isCollapse
},
saveNavState(activePath){
window.sessionStorage.setItem("activePath", activePath)
this.activePath = activePath
}
}
}
</script>
<style lang="less" scope>
.home-container {
height: 100%;
}
.el-header {
background-color: #373d41;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 !important;
> div {
height: 100%;
color: #fff;
font-size: 20px;
display: flex;
align-items: center;
// padding: 0;
img {
height: 100%;
border-radius: 50%;
margin-right: 10px;
}
}
}
.el-aside {
background-color: #333744;
.el-menu {
border: 0;
}
.toggle-button {
color: #ffffff;
height: 50px;
> span {
display: block;
position: absolute;
transform: rotate(-90deg) translateY(9px);
transform-origin: 150%;
cursor: pointer;
}
&:hover {
background-color: #292C36;
}
}
}
.el-main {
background-color: #eaedf1;
}
.iconfont {
font-weight: 800;
margin-right: 10px;
}
</style>
User.vue 文件解读
<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>
<!-- Card卡片视图区域 -->
<el-card class="box-card">
<!-- 搜索与添加区域 -->
<el-row :gutter="20">
<el-col :span="8">
<!--
:clearable - 是否可清空
@clear - 在点击由 clearable 属性生成的清空按钮时触发
-->
<el-input placeholder="请输入内容" v-model="queryInfo.query" :clearable="true" @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>
<!--
用户列表区域
border - 表格样式
stripe - 表格条纹
-->
<el-table :data="userList" border stripe>
<el-table-column type="index"></el-table-column>
<el-table-column prop="username" label="姓名"></el-table-column>
<el-table-column prop="email" label="邮箱"></el-table-column>
<el-table-column prop="mobile" label="电话"></el-table-column>
<el-table-column prop="role_name" label="角色"></el-table-column>
<el-table-column label="状态">
<template v-slot="scope">
<!-- {{ scope.row }} -->
<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>
<!-- 修改按钮 -->
<el-tooltip class="item" effect="dark" content="修改角色" placement="top" :enterable="false">
<el-button type="primary" icon="el-icon-edit" size="mini"></el-button>
</el-tooltip>
<!-- 删除按钮 -->
<el-tooltip class="item" effect="dark" content="删除角色" placement="top" :enterable="false">
<el-button type="danger" icon="el-icon-delete" size="mini"></el-button>
</el-tooltip>
<!-- 分配角色按钮 -->
<el-tooltip class="item" effect="dark" content="分配角色" placement="top" :enterable="false">
<el-button type="warning" icon="el-icon-setting" size="mini"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<!--
分页区域
@size-change - 选择页数的列表发生切换,返回最新的一页显示多少条
@current-change - 页码值发生切换,返回最新的页数
:current-page - 当前显示的是第几页的数据
:page-sizes - 选择页数的下拉列表
:page-size - 每页显示多少条数据
layout - 需要展示的功能组件
:total - 总条数(目前有 bug ,只能写成固定的数字才能正常显示)
-->
<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="4">
</el-pagination>
</el-card>
<!--
添加用户Dialog对话框
:visible.sync - 控制添加对话框的显示与隐藏
-->
<el-dialog
title="提示"
:visible.sync="addDialogVisible"
width="50%"
@close="addDialogClosed"
>
<!-- 内容主体区域 -->
<el-form ref="addFormRef" :model="addForm" :rules="addFormRules" 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 type="password" v-model="addForm.password"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input type="email" 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>
</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: 3, max: 15, message: '长度在 3 到 15 个字符', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ validator: checkEmail, trigger: 'blur' }
],
mobile: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ validator: checkMobile, trigger: 'blur' }
]
}
}
},
created(){
this.getUserList()
},
methods: {
async getUserList(){
const {data: res} = await this.$http.get("users", {params: this.queryInfo})
// console.log(res);
if(res.meta.status !== 200) return this.$message.error(res.meta.msg)
this.userList = res.data.users
this.total = res.total
},
// 监听 pagesize 改变的事件
handleSizeChange(newSize){
console.log(newSize);
this.queryInfo.pagesize = newSize
// pagesize 发生变化需要重新发起 axios 请求来展示数据
this.getUserList()
},
// 监听 页码值 改变的事件
handleCurrentChange(newPage){
console.log(newPage);
this.queryInfo.pagenum = newPage
this.getUserList()
},
async userStateChanged(userinfo){
// console.log("userinfo:",userinfo);
const {data: res} = await this.$http.put(`users/${userinfo.id}/state/${userinfo.mg_state}`)
console.log(res);
if(res.meta.status !== 200){
userinfo.mg_state = !userinfo.mg_state
return this.$message.error("更新用户状态失败!")
}
this.$message.success("更新用户状态成功!")
},
addDialogClosed(){
this.$refs.addFormRef.resetFields()
}
}
}
</script>
<style lang="less" scope>
</style>
权限管理
权限管理业务分析
通过权限管理模块控制不同的用户可以进行哪些操作,具体可以通过角色的方式进行控制,即每个用户分配一个特定的角色,角色包括不同的功能权限。
比如说:王者荣耀,你充钱的玩家有皮肤,不充钱的玩家没有皮肤…
项目要点
如何实现一个好看的3级 UI 结构
<!-- 角色列表区域 -->
<el-table
:data="rolesList"
width="100%"
border
stripe
>
<!--
展开列
type="expand" - 开启展开列功能
-->
<el-table-column type="expand">
<template v-slot="scope">
<el-row v-for="(item1, i1) in scope.row.children" :key="item1.id" :class="['bdbottom', i1 === 0 ? 'bdtop' : 'bdbottom', 'vcenter']">
<!-- 渲染一级权限 -->
<el-col :span="5">
<el-tag>{{ item1.authName }}</el-tag>
<i class="el-icon-caret-right"></i>
</el-col>
<!-- 渲染二级权限 -->
<el-col :span="19">
<el-row v-for="(item2, i2) in item1.children" :key="item2.id" :class="[i2 === 0 ? '' : 'bdtop', 'vcenter']">
<el-col :span="6">
<el-tag type="success">{{ item2.authName }}</el-tag>
<i class="el-icon-caret-right"></i>
</el-col>
<!-- 渲染三级权限 -->
<el-col :span="18">
<el-tag type="warning" v-for="(item3, i3) in item2.children" :key="item3.id">{{ item3.authName }}</el-tag>
</el-col>
</el-row>
</el-col>
</el-row>
</template>
</el-table-column>
</el-table>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-njOQiVro-1605152538773)(…/upload/image-20201107144025650.png)]
如何优雅的写一个树形表格
<!--
表格
(参考 https://github.com/MisterTaki/vue-table-with-tree-grid)
ref - 为当前组件注册一个引用
:data - 表格各行的数据
:columns - 表格各列的配置,这是一个列表,其中包含对象,对象的属性
* label - 列标题名称
* prop - 对应列内容的属性名
:show-index - 是否显示数据索引
:index-text - 数据索引名称
:stripe - 是否显示间隔斑马纹
:border - 是否显示纵向边框
:show-row-hover - 鼠标悬停时,是否高亮当前行
:tree-type - 是否为树形表格
:selection-type - 是否为多选类型表格
:expand-type - 是否为展开行类型表格
-->
<tree-table
class="treetable"
ref="treeTableRef"
:data="cateList"
:columns="columns"
:show-index="true"
index-text=""
stripe
border
:show-row-hover="false"
:tree-type="true"
:expand-type="false"
:selection-type="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: lightgreen"></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" slot-scope="scope">
<el-button icon="el-icon-edit" type="primary" size="mini">编辑</el-button>
<el-button icon="el-icon-delete" type="danger" size="mini">删除</el-button>
</template>
</tree-table>
如何优雅的写一个分页器
<!--
分页区域
@size-change - 选择页数的列表发生切换,返回最新的一页显示多少条
@current-change - 页码值发生切换,返回最新的页数
:current-page - 当前显示的是第几页的数据
:page-sizes - 选择页数的下拉列表
:page-size - 每页显示多少条数据
layout - 需要展示的功能组件
:total - 总条数(目前有 bug ,只能写成固定的数字才能正常显示)
-->
<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>
如何用 ElementUI 实现一个动态级联选择器
没错,就是它
首先,它需要一个数组,数组中有很多对象,并且这个数组数据是从 后台接口 动态获取的,所以说它返回来的数据也是固定的,但是 这个Cascader 级联选择器,它里面需要接收必须有 value 和 label 的对象(value为分类id, label为分类名称),那么我应该考虑如何把后台返回来的列表变成符合要求的列表,我的解决方案是 把后台数据列表先转换成字符串,再使用替换方法,再转成列表返回,这样问题就解决了,但是它还是有bug,它的选择器的长度,超乎我的想象,于是我用F12找到对应的元素,进行修改,到此,完美结束
第一种方法:
<!--
:options - 用来指定数据源
:props - 用来指定配置对象
change-on-select - 是否允许选择任意一级的选项
clearable - 是否支持清空选项
-->
<el-cascader
v-model="selectedKeys"
:options="options"
:props="{ expandTrigger: 'hover' }"
@change="parentCateChanged"
change-on-select
clearable >
</el-cascader>
export default {
data(){
return {
// 选择的分类id列表
selectedKeys: []
// 父级分类列表
parentCateList: [],
// options 列表
options: [],
}
}
}
// 更改 parentCateList 列表中属性名重新赋值给 options 列表,来供el-cascader组件中的 :options 使用
getOptionsList(){
this.options = JSON.parse(JSON.stringify(this.parentCateList).replace(/cat_id/g, "value").replace(/cat_name/g, "label").replace(/cat_deleted/g, "del").replace(/cat_level/g, "del").replace(/cat_pid/g, "del"))
this.options.forEach((item1)=>{
delete item1.del
item1.children.forEach((item2)=>{
delete item2.del
})
})
},
// 修改 cscader 默认的超长选择框的高度
.el-cascader-panel {
height: 300px;
}
第二种方法,老师的方法:
<el-cascader
expand-trigger="hover"
v-model="selectedKeys"
:options="parentCateList"
:props="cascaderProps"
@change="parentCateChanged"
change-on-select
clearable >
</el-cascader>
export default {
data(){
return {
// 选择的分类id列表
selectedKeys: []
// 父级分类列表
parentCateList: [],
// 指定级联选择器的配置对象
cascaderProps: {
value: "cat_id",
label: "cat_name",
children: "children"
},
}
}
}
如何实现步骤条和标签栏的同步
<!--
步骤条区域
:space - 步骤条的线长
:active - 开始的索引值
align-center - 居中
-->
<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-tabs v-model="activeIndex" :tab-position="'left'" style="height: 200px;">
<el-tab-pane label="基本信息" name="0">基本信息</el-tab-pane>
<el-tab-pane label="商品参数" name="1">商品参数</el-tab-pane>
<el-tab-pane label="商品属性" name="2">商品属性</el-tab-pane>
<el-tab-pane label="商品图片" name="3">商品图片</el-tab-pane>
<el-tab-pane label="商品内容" name="4">商品内容</el-tab-pane>
</el-tabs>
export default {
data(){
return {
activeIndex: '0'
}
}
}
首先和主要的就是 data 中的
activeIndex
的定义,首先,你应该知道它是双向绑定在<el-steps>
标签和<el-tabs>
标签中的,<el-steps>
标签是用:active
绑定的,它的定义是:索引值(数字类型)为几,那么它就跳到第几步,<el-tabs>
标签是用v-model
绑定的,它的定义是:你点的是哪一栏,v-model 的值(字符串)就是什么,关键是这两个的类型不同,所以我定义了activeIndex
的值默认是字符串的 ‘0’,需要数字的地方直接减 0 就行了
如何实现商品列表中编辑商品的功能
首先,你应该能够获取到这一行的数据,无论是小康的(在路径后面传入该商品对应的id,然后通过id查询商品列表),还是我的(把该行的数据存入sessonStorage 中,然后在需要的地方直接拿出来),这都能获取到该行的数据,关键是你怎么把数据重新渲染到编辑页面,我是直接给 editForm ,需要提交的表单进行重新赋值,让该商品的数据重新赋值给 editForm 中对应的属性上,但是,需要主要的是,商品分类是该商品没有给到的数据,那你就应该通过商品 id 来查询该商品,你在它的返回结果就可以找到 goods_cat(商品分类)这个数据,但是,它返回的这个 goods_cat 是一个字符串(这是因为我在Add.vue 中提交表单时作了处理,因为它最终只能提交字符串格式的数据),所以我需要把它先转换为一个数组,然后再重新赋值给 editForm,最后只需要改一下编辑的接口即可成功修改该商品。
List.vue
// 通过编程式导航跳转到编辑商品页面
goEditpage(row){
this.$router.push(`/goods/edit`)
const rowObj = JSON.stringify(row)
console.log(rowObj);
window.sessionStorage.setItem("edit", [rowObj])
}
Edit.vue
async created(){
this.getCateList()
const rowObj = JSON.parse(window.sessionStorage.getItem("edit"))
console.log(rowObj);
// 根据 ID 查询商品
const {data: res} = await this.$http.get("goods/" + rowObj.goods_id)
console.log(res.data);
this.goodsId = rowObj.goods_id
this.editForm.goods_cat = res.data.goods_cat.split(",").map(Number)
this.editForm.goods_name = rowObj.goods_name
this.editForm.goods_number = rowObj.goods_number
this.editForm.goods_price = rowObj.goods_price
this.editForm.goods_weight = rowObj.goods_weight
// console.log(this.editForm);
},
需要注意的是,把数字字符串转成数组之后它的每一项还是字符串,所以需要用 map(Number) 把其中的每一项都变成数字。
项目优化 - 添加进度条
就是顶部一闪而过的进度条
npm install --save nprogress
// 导入 NProgress 包对应的JS和CSS
import NProgress from "nprogress";
import "nprogress/nprogress.css";
// 在 request 拦截器中,展示进度条 NProgress.start()
axios.interceptors.request.use((config) => {
// console.log(config);
NProgress.start();
config.headers.Authorization = window.sessionStorage.getItem("token");
return config;
});
// 在 resopnse 拦截器中,隐藏进度条 NProgress.done()
axios.interceptors.response.use((response) => {
NProgress.done();
return response;
});
如何在上线时移除项目中所有的console
使用 babel-plugin-transform-remove-console 插件移除
在开发依赖中安装
npm install babel-plugin-transform-remove-console --save-dev
按需使用 这个插件,在开发时不移除 console,在发布时移除 console
babel.config.js
// 这是项目发布阶段需要用到的 babel 插件
const prodPlugins = []
if(process.env.NODE_ENV === "production"){
proPlugins.push("transform-remove-console")
}
module.exports = {
// 发布产品时候的插件数组
...prodPlugins
}
项目优化 - 通过 externals 加载外部CDN资源
具体配置如下:
config.set ('externals', {
vue: 'vue',
'vue-router': 'vueRouter',axios: 'axios',
loaash: '_',
echarts: 'echarts', nprogress: 'NProgress',
'vue-quill-editor': 'vueQuillEditor'})
项目优化 - 路由懒加载
当打包构建项目时,JavaScript包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
具体需要3步:
① 安装 @babel/plugin-syntax-dynamic-import
包。
② 在babel.config.js
配置文件中声明该插件。
③ 将路由改为按需加载的形式,示例代码如下;
const Foo = () => import(/* webpackChunkName: "group-foo" */ "./Foo.vue")
const Bar = () => import(/* webpackChunkName: "group-foo" */ "./Bar.vue")
const Baz = () => import(/* webpackChunkName: "group-boo" */ "./Baz.vue")
项目优化 - 开启 gzip 压缩配置
使用 gzip 可以减小文件体积,使传输速度更快。
② 可以通过服务器端使用Express做gzip 压缩。其配置如下:
//安装相应包
npm install compression -D
//导入包
const compression = require('compression');
//启用中间件
app.use(compression());
注意:先注册 gizp 压缩,再托管静态资源
使用pm2管理应用
pm2 可以使你的终端窗口被关闭后也可以正常的处于开启服务的状态
① 在服务器中安装pm2: npm i pm2 -g
② 启动项目: pm2 start脚本--name自定义名称
③ 查看运行项目: pm2 ls
④ 重启项目: pm2 restart 自定义名称
⑤ 停止项目: pm2 stop 自定义名称
== “production”){
proPlugins.push(“transform-remove-console”)
}
module.exports = {
// 发布产品时候的插件数组
…prodPlugins
}
### 项目优化 - 通过 externals 加载外部CDN资源
> 具体配置如下:
```javascript
config.set ('externals', {
vue: 'vue',
'vue-router': 'vueRouter',axios: 'axios',
loaash: '_',
echarts: 'echarts', nprogress: 'NProgress',
'vue-quill-editor': 'vueQuillEditor'})
项目优化 - 路由懒加载
当打包构建项目时,JavaScript包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
具体需要3步:
① 安装 @babel/plugin-syntax-dynamic-import
包。
② 在babel.config.js
配置文件中声明该插件。
③ 将路由改为按需加载的形式,示例代码如下;
const Foo = () => import(/* webpackChunkName: "group-foo" */ "./Foo.vue")
const Bar = () => import(/* webpackChunkName: "group-foo" */ "./Bar.vue")
const Baz = () => import(/* webpackChunkName: "group-boo" */ "./Baz.vue")
项目优化 - 开启 gzip 压缩配置
使用 gzip 可以减小文件体积,使传输速度更快。
② 可以通过服务器端使用Express做gzip 压缩。其配置如下:
//安装相应包
npm install compression -D
//导入包
const compression = require('compression');
//启用中间件
app.use(compression());
注意:先注册 gizp 压缩,再托管静态资源
使用pm2管理应用
pm2 可以使你的终端窗口被关闭后也可以正常的处于开启服务的状态
① 在服务器中安装pm2: npm i pm2 -g
② 启动项目: pm2 start脚本--name自定义名称
③ 查看运行项目: pm2 ls
④ 重启项目: pm2 restart 自定义名称
⑤ 停止项目: pm2 stop 自定义名称
⑥ 删除项目: pm2 delete 自定义名称
本文地址:https://blog.csdn.net/Cool_breeze_/article/details/109640761
上一篇: wsl-windows下具有真实质感的linux环境
下一篇: 深入理解Java中的String