65 后台管理项目

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状态仓库

  • 实现秒杀弹框的静态骨架

三十六、把登录结果存储到Vuex 中

posted @ 2021-05-07 22:58  一花一世界111  阅读(192)  评论(0)    收藏  举报