65 后台管理项目
vue create u-shop-admin
根据产品需求,安装项目的时候选择你需要的依赖
router
vuex
css预处理器
二、项目初始化
//删除默认的app.vue内容
//删除views默认的文件夹以及文件
//删除components的文件夹以及文件
//初始化router =>index.js 删除引入组件和routes(路线)的内容
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
]
const router = new VueRouter({
routes
})
export default router
三、根据产品需求安装依赖
技术栈:
element-ui
axios
四、根据产品需求创建出相关的组件
一级路由组件:
index.vue
login.vue
五、搭建路由
router =>index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/login',
component: ()=>import ('../pages/login.vue')
},
{
path: '/index',
component: ()=>import ('../pages/index.vue')
},
{
path:'*',
redirect:'/login'
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
六、创建文件夹以及基本设置
assets 静态资源
components 创建被嵌套的组件
common 全局组件
router 路由
store 仓库
util 实用工具(自己封装模块比如:axios)
filter 过滤器
stylus 共通的css模块
pages 一级路由组件
views 二级路由组件
main.js
//引入 element-ui
import ElementUI from 'element-ui'
Vue.use(ElementUI)
import 'element-ui/lib/theme-chalk/index.css'
util => axios
axios.js
//专门用于管理axios方法
//引入核心库
import axios from 'axios'
//第二步调用axios中create() 重新创建一个实例因为可以重新对axios的一些属性进行配置
let http = axios.create({
// baseURL:'',//基础地址
// timeout: //超时控制
})
//axios拦截器
//请求拦截器 ——interceptors
http.interceptors.request.use((req)=>{
return req
})
//响应拦截器
http.interceptors.response.use((res)=>{
return res
})
//导出封装好的新的实例
export default http
index.js
//这个模块用于封装所有的接口
import http from './axios'
//封装登录接口
// export function Login(data){
// console.log(data,'入参');
// return http.post('api/login',data)
// }
七、完成登录页
<template>
<div class="login">
<!-- 登录输入框 -->
<!--
el-form
model 表单数据对象 object
rules 表单验证规则 object
Form-Item
prop 表单域 model 字段,在使用 validate、resetFields 方法的情况下,该属性是必填的 string 传入 Form 组件的 model 中的字段
-->
<el-form
:model="loginInfo"
label-width="100px"
class="loginForm"
ref="login"
:rules='rules'
>
<el-form-item label="用户名" prop='username'>
<el-input v-model="loginInfo.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop='password'>
<el-input v-model="loginInfo.password"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="login">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
data() {
return {
//表单数据
loginInfo:{
username:'',
password:''
},
//规则验证
rules:{
username: [
//失去焦点的验证
{ required: true, message: '请输入用户名', trigger: 'blur' },
//字符串长度验证 产品经理设定
{ min: 2, max: 8, message: '长度在 2 到 8 个字符', trigger: 'blur' }
],
password: [
//失去焦点的验证
{ required: true, message: '请输入密码', trigger: 'blur' },
//字符串长度验证 产品经理设定
{ min: 6, max: 16, message: '长度在 6 到 16 个字符', trigger: 'blur' }
],
}
};
},
methods: {
//封装一个登录事件
login(){
console.log(this.loginInfo,'表单')
//当前验证器的方法是elementui提供的
this.$refs.login.validate(val=>{
if(val){
console.log(val,'1111');
//验证成功之后就要调取接口
if(this.loginInfo.username=='admin'&&this.loginInfo.password=='123456'){
this.$message.success('登录成功')
//成功之后跳转到首页
this.$router.push('/index')
}else{
this.$message.error('用户名密码错误')
}
}else{
console.log(val,22222)
this.$message.error('用户名密码验证失败')
}
})
}
},
};
</script>
<style lang="stylus" scoped>
@import '../stylus/index.styl';
.login
width 100vw
height 100vh
background $firstColor
.loginForm
position:absolute
top 50%
left 50%
margin -150px 0 0 -150px
width 400px
height 250px
</style>
八、实现Index页面的基本布局
pages=>index.vue
<template>
<div>
<!--
搭建基本布局
el-header 默认是60px 高度 默认属性height
el-aside 默认宽度是300px
-->
<el-container>
<el-header height="80px">XXX大型后台管理系统</el-header>
<el-container>
<el-aside width="250px">
<!--
el-menu
default-active 当前激活菜单的 index string
background-color background-color 菜单的背景色(仅支持 hex 格式) string
text-color 菜单的文字颜色(仅支持 hex 格式) string
active-text-color 当前激活菜单的文字颜色(仅支持 hex 格式) string
unique-opened 是否只保持一个子菜单的展开 boolean
-->
<el-menu
default-active=""
background-color="#4B6173"
text-color="#fff"
active-text-color="#ffd04b"
unique-opened
>
<el-menu-item index="0">
<i class="el-icon-s-grid"></i>
<span slot="title">首页</span>
</el-menu-item>
<el-submenu index="1">
<template slot="title">
<i class="el-icon-setting"></i>
<span>系统管理</span>
</template>
<el-menu-item index="4">
<span slot="title">菜单管理</span>
</el-menu-item>
<el-menu-item index="5">
<span slot="title">角色管理</span>
</el-menu-item>
<el-menu-item index="6">
<span slot="title">管理员管理</span>
</el-menu-item>
</el-submenu>
<el-submenu index="2">
<template slot="title">
<i class="el-icon-goods"></i>
<span>商城管理</span>
</template>
<el-menu-item index="8">
<span slot="title">商品管理</span>
</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<el-main>中心主题内容</el-main>
</el-container>
</el-container>
</div>
</template>
<script>
export default {
data() {
return {};
},
};
</script>
<style lang="stylus" scoped>
@import '../stylus/index.styl';
.el-header
background $secondColor
.el-menu
min-height 600px
</style>
九、拆分左侧导航菜单
component=>vNav
拆分结束一个记得引入回index组件
<template>
<div>
<!--
el-menu
default-active 当前激活菜单的 index string
background-color background-color 菜单的背景色(仅支持 hex 格式) string
text-color 菜单的文字颜色(仅支持 hex 格式) string
active-text-color 当前激活菜单的文字颜色(仅支持 hex 格式) string
unique-opened 是否只保持一个子菜单的展开 boolean
router 是否使用 vue-router 的模式,启用该模式会在激活导航时以 index 作为 path 进行路由跳转 boolean
-->
<el-menu
:default-active="defaultActive"
background-color="#4B6173"
text-color="#fff"
active-text-color="#ffd04b"
unique-opened
router
>
<el-menu-item index="/home">
<i class="el-icon-s-grid"></i>
<span slot="title">首页</span>
</el-menu-item>
<el-submenu index="1">
<template slot="title">
<i class="el-icon-setting"></i>
<span>系统管理</span>
</template>
<el-menu-item index="/menu">
<span slot="title">菜单管理</span>
</el-menu-item>
<el-menu-item index="/role">
<span slot="title">角色管理</span>
</el-menu-item>
<el-menu-item index="6">
<span slot="title">管理员管理</span>
</el-menu-item>
</el-submenu>
<el-submenu index="2">
<template slot="title">
<i class="el-icon-goods"></i>
<span>商城管理</span>
</template>
<el-menu-item index="8">
<span slot="title">商品管理</span>
</el-menu-item>
</el-submenu>
</el-menu>
</div>
</template>
<script>
export default {
data() {
return {
defaultActive:'/home'
};
},
mounted() {
//组件一加载切换 path选中状态
this.defaultActive = this.$route.path
},
};
</script>
<style lang='stylus' scoped>
.el-menu
min-height 600px
</style>
十、通过el-menu中router控制路由跳转
router 是否使用 vue-router 的模式,启用该模式会在激活导航时以 index 作为 path 进行路由跳转 boolea
<el-menu
router
>
十一、刷新页面控制左侧默认选中状态
<el-menu
:default-active="defaultActive"
>
<script>
export default {
data() {
return {
defaultActive:'/home'
};
},
mounted() {
//组件一加载切换 path选中状态
this.defaultActive = this.$route.path
},
};
</script>
十二、实现菜单管理页面静态布局(未拆分)
<template>
<div>
<!-- 面包屑 -->
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item><a href="">菜单管理</a></el-breadcrumb-item>
</el-breadcrumb>
<!-- 添加按钮 -->
<el-button type="primary" size="small" plain>添加</el-button>
<!-- 列表 -->
<el-table :data="tableData" border style="width: 100%">
<el-table-column prop="date" label="日期" width="180"> </el-table-column>
<el-table-column prop="name" label="姓名" width="180"> </el-table-column>
<el-table-column prop="address" label="地址"> </el-table-column>
<el-table-column label='操作'>
<template>
<el-button type='info' circle icon='el-icon-edit' size='small'></el-button>
<el-button type='danger' circle icon='el-icon-delete' size='small'></el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [
{
date: "2016-05-02",
name: "王小虎",
address: "上海市普陀区金沙江路 1518 弄",
},
{
date: "2016-05-04",
name: "王小虎",
address: "上海市普陀区金沙江路 1517 弄",
},
{
date: "2016-05-01",
name: "王小虎",
address: "上海市普陀区金沙江路 1519 弄",
},
{
date: "2016-05-03",
name: "王小虎",
address: "上海市普陀区金沙江路 1516 弄",
},
],
};
},
};
</script>
<style lang="stylus" scoped>
.el-button{
margin 10px
}
</style>
十三、拆分全局面包屑
common=>el-bread.vue
<template>
<div>
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item><a :href="$route.path">{{$route.name}}</a></el-breadcrumb-item>
</el-breadcrumb>
</div>
</template>
<script>
export default {
data() {
return {};
},
};
</script>
<style scoped></style>
main.js
//引入全局组件
import gCom from './common'
for(let i in gCom){
Vue.component(i,gCom[i])
}
router =>index.js
{
path:'/menu',
component:()=>import('../views/menu/index'),
name:'菜单管理'
},
{
path:'/role',
component:()=>import('../views/role'),
name:'角色管理'
},
十三、菜单管理组件静态骨架拆分
menu=>index.vue
<template>
<div>
<!-- 面包屑 -->
<el-bread></el-bread>
<!-- 添加按钮 -->
<el-button @click="addDialog" type="primary" size="small" plain>添加</el-button>
<!-- 列表 -->
<v-list></v-list>
<!-- 弹框 -->
<v-dialog @cancel='cancel' :isShow='isShow'></v-dialog>
</div>
</template>
<script>
//引入列表
import vList from './list'
//引入弹框
import vDialog from './dialog'
export default {
data() {
return {
isShow:false //用来控制谭宽的显示
};
},
components:{
vList,
vDialog
},
methods: {
//封装一个打开弹框事件
addDialog(){
//打开弹框
this.isShow = true
},
//子组件控制关闭弹框
cancel(e){
this.isShow =e
}
},
};
</script>
<style lang="stylus" scoped>
.el-button{
margin 10px
}
</style>
menu=>list.vue
<template>
<div>
<el-table :data="tableData" border style="width: 100%">
<el-table-column prop="date" label="日期" width="180"> </el-table-column>
<el-table-column prop="name" label="姓名" width="180"> </el-table-column>
<el-table-column prop="address" label="地址"> </el-table-column>
<el-table-column label="操作">
<template>
<el-button
type="info"
circle
icon="el-icon-edit"
size="small"
></el-button>
<el-button
type="danger"
circle
icon="el-icon-delete"
size="small"
></el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [
{
date: "2016-05-02",
name: "王小虎",
address: "上海市普陀区金沙江路 1518 弄",
},
{
date: "2016-05-04",
name: "王小虎",
address: "上海市普陀区金沙江路 1517 弄",
},
{
date: "2016-05-01",
name: "王小虎",
address: "上海市普陀区金沙江路 1519 弄",
},
{
date: "2016-05-03",
name: "王小虎",
address: "上海市普陀区金沙江路 1516 弄",
},
],
};
},
};
</script>
<style scoped></style>
menu=>dialog.vue
<template>
<div>
<!--
before-close 关闭前的回调,会暂停 Dialog 的关闭 function(done),done 用于关闭 Dialog
-->
<el-dialog title="收货地址" :before-close='cancel' :visible.sync="isShow">
<div slot="footer" class="dialog-footer">
<el-button @click="cancel">取 消</el-button>
<el-button type="primary" @click="isShow = false"
>确 定</el-button
>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
};
},
props:['isShow'],
methods: {
//封装一个关闭弹框事件
cancel(){
//子传父
this.$emit('cancel',false)
},
// haha(){
// console.log('hahhahah');
// }
},
};
</script>
<style scoped></style>
十四、注释token验证
因为登录还没有做,没有token
后台管理的所有接口都需要token验证,所以先关闭后端代码
后端服务=》app.js 大概42行位置
// 如果想被登录拦截器拦截判断的, 接口放到下面
// app.use(async (req, res, next) => {
// if (!req.headers.authorization) {
// res.send(MError("请设置请求头,并携带验证字符串"));
// } else {
// if (!await checkToken(req)) { // 过期
// res.send(Guest([],"登录已过期或访问权限受限"));
// } else {
// next();
// }
// }
// });
十五、实现添加按钮触发弹框(举例)
-
index.vue(父组件)
<v-dialog :isShow='isShow' @cancel='cancel'></v-dialog>
data(){
return{
isShow:false
}
}
methods:{
cancel(e){
this.isShow=e
}
} -
dialog.vue(子组件)
<button @click='cancel'>取消</button>
props:['isShow']
methods:{
cancel(){
this.$emit('cancel',false)
}
}
十六、如何在前端解决跨域问题
config =>vue.config.js
//这就是vue的配置项
module.exports ={
devServer:{
//解决跨域问题配置
//配置代理
proxy:'http://localhost:3000'
}
}
!!!!重启
十五、调取菜单列表
-
menu=>list.vue
<template>
<div>
<el-table :data="list" border style="width: 100%">
<el-table-column prop="id" label="菜单编号" width="180">
</el-table-column>
<el-table-column prop="title" label="菜单名称" width="180">
</el-table-column>
<el-table-column prop="pid" label="上级菜单"> </el-table-column>
<el-table-column prop="icon" label="菜单图标"> </el-table-column>
<el-table-column prop="url" label="菜单地址"> </el-table-column>
<el-table-column prop="status" label="状态">
<template slot-scope="item">
<div>
<el-tag v-if="item.row.status==1" type="success">启用</el-tag>
<el-tag v-else type="danger">禁用</el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="item">
<el-button
type="info"
circle
icon="el-icon-edit"
size="small"
@click='edit(item.row.id)'
></el-button>
<el-button
type="danger"
circle
icon="el-icon-delete"
size="small"
@click="del(item.row.id)"
></el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
//引入封装好菜单列表
import { getMenuList,delMenu } from "../../util/axios";
export default {
data() {
return {
list: [],
};
},
mounted() {
//菜单列表接口调取
getMenuList().then((res) => {
console.log(res, "caidan列表");
if (res.code == 200) {
this.list = res.list;
} else {
this.$message.error(res.msg);
}
});
},
methods: {
//封装一个删除事件
del(id){
this.$confirm('你确定要删除这条数据吗????', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
//调取删除接口
delMenu({id})
.then(res=>{
if(res.code==200){
this.$message.success(res.msg)
//删除成功之后刷新
location.reload()
}else{
this.$message.error(res.msg)
}
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
//封装一个编辑事件 给父组件 传id
edit(id){
this.$emit('edit',id)
}
},
};
</script>
<style scoped></style>
十六、完善编辑功能并调取接口
1、list表格点击编辑传送id给父组件index
-
视图
<el-button
type="info"
circle
icon="el-icon-edit"
size="small"
@click='edit(item.row.id)'
></el-button>
-
逻辑
//点击编辑获取id
edit(id){
//把id给父组件menu,之后供弹框使用
this.$emit('edit',id)
},
2、点击list编辑按钮联动触发index中edit事件打开弹框并修改弹框状态为编辑
-
视图
<!-- 表格列表渲染 -->
<v-list @edit='edit'></v-list>
-
逻辑
//编辑事件
//封装一个编辑打开弹框并传值的事件
edit(e){
//打开弹框
this.diaInfo.isShow = true
//告诉弹框你是编辑
this.diaInfo.isAdd = false
// console.log(this.$refs.dialogInfo,'dialogInfo');
this.$refs.dialogInfo.lookup(e)
}
3、点击编辑获取当前数据
-
视图
<!-- 弹框表单 -->
<v-dialog ref='dialogInfo' ></v-dialog>
-
逻辑
//封装一个查询一条数据事件(数据的回显)
lookup(id) {
//调取获取一条数据接口
getMenuInfo({ id }).then((res) => {
console.log(res, "单条菜单");
if (res.code == 200) {
this.menuform = res.list;
//给对象添加id
this.menuform.id = id
} else {
this.$message.error(res.msg);
}
});
},
4、点击确定编辑提交表单并调取修改接口
-
视图
<el-button v-else type="primary" @click="edit">编 辑</el-button>
-
逻辑
//封装一个编辑菜单事件
edit() {
this.$refs.menuform.validate((val) => {
if (val) {
// console.log(this.menuform, "菜单弹框信息");
//调取编辑接口
editMenu(this.menuform)
.then((res) => {
console.log(res, "响应");
if (res.code == 200) {
//关闭弹框 //刷新页面
this.cancel();
//强制刷新
location.reload();
} else {
this.$message.error(res.msg);
}
})
.catch((err) => {
console.log(err, "错误拦截");
});
} else {
console.log("验证失败");
}
});
},
十七、权限简单铺垫
后台管理一定会有权限问题!!!
后台管理最终的就是权限问题!!!
不同的账号会有不同的权限,它登录之后,左侧侧边栏会渲染不同的内容。
一般后台管理都有一个账号,叫超集管理员,一般默认admin。它拥有所有的权限。
比如会有普通管理员。它可能拥有一部分权限,比如管理员权限,比如商城权限,还有普通会员,它只有商城管理的权限。
十八、el-tree
-
视图
<!-- el-tree
data 展示数据 array
show-checkbox 节点是否可被选择
node-key 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的
props
label 指定节点标签为节点对象的某个属性值
children 指定子树为节点对象的某个属性值
default-expand-all 是否默认展开所有节点
-->
<el-tree
ref="tree"
:data="menulist"
show-checkbox
node-key="id"
:props="{
children: 'children',
label: 'title',
}"
default-expand-all
>
</el-tree>
-
赋值
this.$refs.tree.setCheckedKeys(要赋值的数组);
-
取值
this.$refs.tree.getCheckedKeys()
十九、搭建管理员静态骨架
-
创建相关组件
-
搭建路由
-
封装接口
-
实现弹框的静态骨架搭建
-
实现添加弹框的接口调用
-
在主组件(管理员)中创建分页的静态骨架
<!-- 分页列表 -->
<el-pagination
:page-size="pageInfo.size"
background
layout="prev, pager, next"
:total="pageInfo.count"
@current-change="changePage"
>
</el-pagination>
-
封装一个初始函数
//封装总数函数
getCount() {
getUserCount().then((res) => {
if (res.code === 200) {
console.log(res, "总数");
this.pageInfo.count = res.list[0].total;
}
});
},
//封装列表函数
getList() {
getUserList({
size: this.pageInfo.size,
page: this.pageInfo.page,
}).then((res) => {
if (res.code === 200) {
console.log(res, "总管理员列表");
this.list = res.list ? res.list :[]
//情景判断,如果不是第一页,删除其他页最后一条数据会出现一个空数组状态,这个是不符合需求
//解决这个bug思路 如果它不是第一页并且它是空数据,我就page减一并且调取列表
if(this.pageInfo.page>1&&this.list.length==0){
//页码减一
this.pageInfo.page--
//重新调用接口
this.getList()
return
}
this.list = res.list;
}
});
},
init() {
//初始化数据函数
//调取总数接口
this.getCount();
//调取接口列表
this.getList();
},
-
切换页码调取函数
//封装一个修改页码触发是事件
changePage(e) {
//e代表的是当前页码
console.log(e, "eeeeeeeeee");
//重新给初识页码赋值
this.pageInfo.page = e
//重新调取列表
this.getList()
},
-
添加、编辑、和删除实现之后重新调取列表
-
视图
<!-- 列表 -->
<v-list @init='init' ></v-list>
<!-- 弹框的渲染 -->
<v-dialog
@init="init"></v-dialog>-
逻辑(子组件)
this.$emit('init') -
二十、清空数据库所有表内容
按照联系规则添加!!!
二十一、针对与菜单的图标和菜单地址进行优化
menu=>menu.vue
-
视图
<el-form-item
v-if="menuform.type == 1"
label="菜单图标"
:label-width="formLabelWidth"
>
<el-select v-model="menuform.icon" placeholder="请选择">
<el-option
v-for="item in iconlist"
:key="item"
:label="item"
:value="item"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item v-else label="菜单地址" :label-width="formLabelWidth">
<el-select v-model="menuform.url" placeholder="请选择">
<el-option
v-for="item in urllist"
:key="item.path"
:label="item.name"
:value="item.path"
>
</el-option>
</el-select>
</el-form-item>
-
逻辑
//引入封装好的路由地址
import { routerChild } from "../../router";
data(){
return {
//封装一个菜单图标数组
iconlist: [
"el-icon-s-tools",
"el-icon-setting",
"el-icon-s-goods",
"el-icon-goods",
],
//封装地址初始值
urllist: routerChild,
}
}
router =>index.js
//设定一个子路由列表并导出
export const routerChild =[
{
path:'/menu',
component:()=>import('../views/menu/index'),
name:'菜单管理'
},
{
path:'/role',
component:()=>import('../views/role/role'),
name:'角色管理'
},
{
path:'/manger',
component:()=>import('../views/manger/manger'),
name:'管理员管理'
},
]
children:[
{
path:'/home',
component:()=>import('../views/home')
},
...routerChild,
{
path:'',
redirect:'/home'
}
]
二十二、 调用qs插件
/*
有时候后端(java或者php)它们在post传参接收参数的时候,希望你用formData方式
可以调用qs插件
下载安装 npm install(i) qs
它具有传参安全性
*/
//引入 qs转化插件
import qs from 'qs'
举例:
//封装一个管理员登录
export function userLogin(data){
return http.post('/api/userlogin',qs.stringify(data))
}
qs.stringify(data)
二十三、实现登录组件调取接口
page=>login.vue
//验证成功之后就要调取接口
userLogin(this.loginInfo).then((res) => {
console.log(res, "登录结果");
if (res.code == 200) {
//把登录结果进行存储
sessionStorage.setItem('userinfo',JSON.stringify(res.list))
this.$message.success(res.msg);
//成功之后跳转到首页
this.$router.push("/home");
} else {
this.$message.error(res.msg);
}
});
二十四、登录成功之后动态渲染侧边栏菜单
<template>
<div>
<el-menu
:default-active="defaultActive"
background-color="#4B6173"
text-color="#fff"
active-text-color="#ffd04b"
unique-opened
router
>
<el-menu-item index="/home">
<i class="el-icon-s-grid"></i>
<span slot="title">首页</span>
</el-menu-item>
<el-submenu v-for="item in navlist" :index="item.id.toString()" :key='item.id'>
<template slot="title">
<i :class="item.icon"></i>
<span>{{item.title}}</span>
</template>
<el-menu-item v-for="menu in item.children" :index="menu.url" :key='menu.id'>
<span slot="title">{{menu.title}}</span>
</el-menu-item>
</el-submenu>
</el-menu>
</div>
</template>
<script>
export default {
data() {
return {
defaultActive:'/home',
navlist:[]
};
},
mounted() {
//组件一加载切换 path选中状态
this.defaultActive = this.$route.path
//判断存储是否有值
this.navlist = sessionStorage.getItem('userinfo') ? JSON.parse(sessionStorage.getItem('userinfo')).menus :[]
},
};
</script>
<style lang='stylus' scoped>
.el-menu
min-height 600px
</style>
二十五、实现全局导航守卫
router =>index.js
//封装全局登录拦截
router.beforeEach((to,from,next)=>{
//如果去的是登录就next
if(to.path=='/login'){
next()
return
}
//如果有存储状态就next
if(sessionStorage.getItem('userinfo')){
next()
return
}
//以上条件都不符合 就强制跳转到登录
next('/login')
})
二十六、设置请求头
注意点:释放之前后端服务注释的token验证。
util=>axios=>axios.js
//axios拦截器
//请求拦截器 ——interceptors
http.interceptors.request.use((req)=>{
//除了登录,其他接口访问数据均需设置请求头 token
req.headers.authorization = JSON.parse(sessionStorage.getItem('userinfo')).token
return req
})
二十七、开发流程
按照图片流程,之后做登录。登录成功之后,你就还有登录结果信息。
登录信息进行存储
左侧侧边栏进行动态渲染
再做权限优化,全局的登录拦截以及token设置
二十八、商品分类
-
创建组件
-
搭建路由
-
设置菜单并设置角色权限
手动操作项目
-
封装分类的所有接口
-
完成弹框静态骨架搭建
-
文件上传(FormData)
一旦有文件上传,你的传输参数的方式,必须是FORMData形式(否则浏览器无法解析)
content-type类型 :
enctype 属性规定在发送到服务器之前应该如何对表单数据进行编码。
默认地,表单数据会编码为 "application/x-www-form-urlencoded"。就是说,在发送到服务器之前,所有字符都会进行编码(空格转换为 "+" 加号,特殊符号转换为 ASCII HEX 值)。
<form enctype="value">
一、application/x-www-form-urlencoded 在发送前编码所有字符(默认)
二、multipart/form-data 不对字符编码。 在使用包含文件上传控件的表单时,必须使用该值。
三、text/plain 空格转换为 "+" 加号,但不对特殊字符编码。(很少用)
如果没有用标签。怎么去改变 enctype类型。可以利用js方法
FormData()(专门用于改变表单类型)
接口API地址:https://developer.mozilla.org/zh-CN/docs/Web/API/FormData
取值不能直接‘.’ ,只能用get方式。添加方式用的是append()因为它的存储类型是key/value 键值对
-
实现当前模块的增删改查(除去图片)
二十九、全局拦截错误
-
token过期
util =>axios
//错误代码是什么不重要,后端回提示你,只要这个错误是全局的,你就可以同意放置在这里进行管理。非统一,散落到各个接口中进行错误拦截
//引入路由文件
import router from '../../router'
//响应拦截器
http.interceptors.response.use((res)=>{
//全局拦截错误信息
if(res.data.code==403){
alert(res.data.msg)
//token 过期 跳转到登录
router.push('/login')
}
return res.data
})
-
vuex状态管理的应用
store => index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import goodscate from './modules/goodscate'
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
goodscate
}
})
store=>modules=>goodscate
//引入封装好的接口
import { getCateList } from "../../../util/axios";
const state = {
catelist: [],
};
const getters = {
getCateList(state) {
return state.catelist;
},
};
const mutations = {
REQ_CATELIST(state,payload){
state.catelist = payload
}
};
const actions = {
//封装一个触发分类列表的行动
getCateListAction({commit}) {
// //页面一加载调用角色列表
getCateList().then((res) => {
if (res.code === 200) {
commit('REQ_CATELIST',res.list)
}
});
},
};
export default {
state,
getters,
mutations,
actions,
namespaced: true,
};
-
实现当前模块的增删改查(除去图片,利用vuex实现)
-
elementUI图片上传插件
-
视图
<!--
上传图片两种场景:
一、后端直接提供服务器地址 直接用action挂地址
二、通过调取接口上传,action = '#',通过接口与服务端进行交互
图片上传视图
el-upload
action 必选参数,上传的地址
on-preview 点击文件列表中已上传的文件时的钩子 查看
on-remove 文件列表移除文件时的钩子 删除
list-type 文件列表的类型
auto-upload 是否在选取文件后立即进行上传 默认是true
on-change 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用
file-list 上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}]
on-exceed 文件超出个数限制时的钩子 function(files, fileList) —
limit 最大允许上传个数
-->
<el-upload
action="#"
list-type="picture-card"
:on-preview="handlePreview"
:on-remove="handleRemove"
:auto-upload="false"
:on-change= 'onChange'
:file-list = 'fileList'
:limit='1'
:on-exceed='onExceed'
>
<i class="el-icon-plus"></i>
</el-upload>
<!-- 展示图片 -->
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt="" />
</el-dialog>-
逻辑
data(){
return{
dialogImageUrl: "", //图片展示的属性
dialogVisible: false, //控制图片展示的弹框
}
},
methods: {
//图片上传或者改变的时候触发的函数
onChange(file,filelist){
console.log(file, filelist, "改变");
//raw 包含了 图片的基本信息,大小,名称,修改时间,类型。。。
this.cateInfo.img = file.raw
},
//移除
handleRemove(file, fileList) {
console.log(file, fileList, "移除");
},
//查看
handlePreview(file) {
this.dialogImageUrl = file.url;
this.dialogVisible = true;
},
} -
-
图片的回显
//设定数据属性
fileList:[],//用来回显图片,存储图片数据的
//调取接口之后得到的结果
//针对于图片进行回显
//为什么要进行判断,因为图片并不是必填项
//如果数据库img这个属性有值,我们就回显。否则就是[](不回显)
this.fileList = this.cateInfo.img? [{url:this.$imgUrl+this.cateInfo.img}] :[]
三十、商品规格
-
创建商品规格组件
-
搭建路由
-
设置菜单并添加权限角色
-
实现商品规格的静态骨架
-
封装商品规格的Vuex模块
-
动态添减表单项
-
视图
<el-form-item
label="规格属性"
:label-width="formLabelWidth"
v-for="(item, idx) in attrArr"
:key="idx"
>
<el-input
style="width:75%"
v-model="item.value"
autocomplete="off"
></el-input>
<el-button v-if="idx == 0" @click="addInp" type="primary"
>新增规格属性</el-button
>
<el-button v-else @click="delInp(idx)" type="danger">删除</el-button>
</el-form-item>-
数据
//用于动态增减表单项
attrArr: [
{
value: "",
},
],-
逻辑
//封装一个动态添加 input 事件
addInp() {
//场景的限制,最多添加6次
if (this.attrArr.length < 6) {
this.attrArr.push({
value: "",
});
} else {
this.$message.warning("最多添加6个input框!!!");
}
},
//删除input
delInp(i) {
this.attrArr.splice(i, 1);
},
//添加和编辑
this.specsInfo.attrs = this.attrArr
.map((item) => item.value)
.join(",");
//数据回显
this.specsInfo.attrs.map((item, idx) => {
console.log(item, "数组中的每一项");
//针对于默认第一项进项单独赋值
if (idx == 0) {
this.attrArr[0].value = item;
} else {
this.attrArr.push({
value: item,
});
}
});
//清空的时候要单独清空动态表单项
reset() {
this.specsInfo = {
specsname: "", //商品规格名称
attrs: "", //规格属性值
status: 1, //状态1正常2禁用
};
//清空验证规则
this.$refs.specsInfo.resetFields();
//重置动态表单增减
this.attrArr= [
{
value: "",
},
]
}, -
-
list 视图
<div>
<!-- {{item.row.attrs}} -->
<el-tag type='info' v-for="attr in item.row.attrs" :key='attr'>{{attr}}</el-tag>
</div>
三十一、商品管理
-
创建商品管理的组件
-
设置商品管理相关的路由
-
设置商品管理的菜单以及权限
-
封装商品管理的接口API
-
设置商品管理相关的Vuex状态仓库
-
实现商品弹框的静态骨架(除去富文本编辑器)
-
实现一二级分类联动
视图
<el-form-item
label="一级分类"
:label-width="formLabelWidth"
prop="first_cateid"
>
<el-select
v-model="goodsInfo.first_cateid"
placeholder="请选择"
@change="changeCate(false)"
>
<el-option
:value="item.id"
:key="item.id"
:label="item.catename"
v-for="item in catelist"
></el-option>
</el-select>
</el-form-item>
<el-form-item
label="二级分类"
:label-width="formLabelWidth"
prop="second_cateid"
>
<el-select v-model="goodsInfo.second_cateid" placeholder="请选择">
<el-option
:value="item.id"
:key="item.id"
:label="item.catename"
v-for="item in secondArr"
></el-option>
</el-select>
</el-form-item>逻辑
data属性
//创建一个二级分类数组
secondArr: [],
methods方法中
//封装一个一级分类切换事件
changeCate(n) {
//我们可以通过数组操作放法,findIndex 去查找匹配的下标索引
console.log(this.goodsInfo.first_cateid, "一级分类id");
// this.catelist.findIndex(item=>{
// return item.id == this.goodsInfo.first_cateid
// })
let index = this.catelist.findIndex(
(item) => item.id == this.goodsInfo.first_cateid
);
this.secondArr = this.catelist[index].children;
//场景,当一级切换的时候,二级不留痕迹
//如果开关条件是真就清空 否则不清空,防止回显的时候双向数据绑定失效
if (!n) {
this.goodsInfo.second_cateid = "";
}
console.log(this.secondArr, "一级分类id");
},-
商品规格以及规格属性联动
视图
<el-form-item
label="商品规格"
:label-width="formLabelWidth"
prop="specsid"
>
<el-select
@change="changeAttr(false)"
v-model="goodsInfo.specsid"
placeholder="请选择"
>
<el-option
:value="item.id"
:key="item.id"
:label="item.specsname"
v-for="item in specslist"
></el-option>
</el-select>
</el-form-item>
<el-form-item
label="商品属性"
:label-width="formLabelWidth"
prop="specsattr"
>
<!-- multiple 是否多选 -->
<el-select
multiple
v-model="goodsInfo.specsattr"
placeholder="请选择"
>
<el-option
:value="item"
:key="item"
:label="item"
v-for="item in attrsArr"
></el-option>
</el-select>
</el-form-item>逻辑
data属性
//创建商品规格属性数组
attrsArr: [],
methods方法
//封装一个切换规格事件
changeAttr(n) {
let index = this.specslist.findIndex(
(item) => item.id == this.goodsInfo.specsid
);
this.attrsArr = this.specslist[index].attrs;
//清空规格属性
if (!n) {
this.goodsInfo.specsattr = "";
}
}, -
-
实现商品管理的增删改查(除去富文本编辑器)
-
数据回显
注意二级分类得不到label,创建一个开关!!!
请参考lookup -
三十二、富文本编辑器
-
官网
wangEditor https://www.wangeditor.com/
UEditor(百度) http://fex.baidu.com/ueditor/
-
概念
Typescript 开发的 Web 富文本编辑器, 轻量、简洁、易用、开源免费
-
安装
npm i wangeditor
-
使用方式(vue脚手架内部引入)
//视图
<div id='id值'></div>
//data属性
editor:null
//引入核心库
import E from 'wangEditor'
//实例化 得到富文本编辑器的所有属性和方法
this.editor = new E('#id值')
//this.editor 得到了富文本编辑器的所有属性和方法
//创建富文本编辑器的视图
this.editor.create()
//注意点:addRange(): The given range isn't in document. 明显富文本超出范围了
//赋值
this.editor.txt.html('要赋值的内容')
//取值
this.editor.txt.html()
三十三、轮播图管理
三十四、实现会员管理的展示与编辑
三十五、实现秒杀管理
-
创建秒杀管理的组件
-
设置秒杀管理相关的路由
-
设置秒杀管理的菜单以及权限
-
封装秒杀管理的接口API
-
设置秒杀管理相关的Vuex状态仓库
-
实现秒杀弹框的静态骨架

浙公网安备 33010602011771号