vue后端管理系统

205 阅读16分钟

一 项目初始化

  • -①APP.vue -②utils工具函数 require.js 比如:拦截器 -③src api请求接口 (存储封装的接口函数)
  • vueCli自定义路径别名
  • vue.config.js devServe:{配置端口号 反向代理}

二 项目基础路由

  • components公共子组件 -views -ItemLists

    • components 存储当前组件自己的子组件

      • index.vue

路由组件命名要求

  • components公共子组件

  • 路由组件在 views目录下遵循统一 目录结构 -views -ItemLists

    • components 存储当前组件自己的子组件

      • index.vue — 要求 每个路由组件 定义name属性 值 同外面目录名(便于vue-devtools审查这个项目 ) 若干个子组件 后台管理常用路由 Layout index.vue

DashBoard index.vue

ItemLists index.vue

ItemAdd index.vue

ItemUpdate index.vue

CartLists index.vue

UserInfo index.vue

WebSetting index.vue

NotFound index.vue

三 搭建基础路由

router index.js

四 配置反向代理

vue.config.js

 // 配置 反向代理
  devServer: {
    // 项目自己启动
    open: true,
    host: 'localhost',
    port: 3001,
    proxy: {
      // 反向代理 所有请求必须 以 /api 才会触发 当前这个反向代理
      '/conner': {
        target: 'https://api.it120.cc',
        // 是否切换源
        changeOrigin: true,
        // 路径重写
        pathRewrite: {
          '^/conner': '/conner'
        }
      }
      /* 代理服务器 发出的 真实请求地址是:
          target + 路径重新的值 + 请求的path(/a/b/c)
          ①假设路径重写 为 / api
            axios.get('/api/a/b/c')
            真正的请求地址  https://api.it120.cc/api/a/b/c
          ②假设 /api重写为了 ''
             axios.get('/api/a/b/c')
             真正请求地址https://api.it120.cc/a/b/c
       */
    }
  },

五 接口的二次封装

  • 使用场景

  • 一个接口可能在 多个路由组件中 调用 ,选择将所有的接口路由请求提取到api目录,单独管理

    axios二次封装 (功能完善后粘贴过来)

  • 一般会将前端代码和后端接口部署在一个服务器,上线之后就不存在跨越问题了: 这个不同环境下源不一样,baseURL不一样, 解决方法: 开发的时候是:api 上线之后(生产的时候)是:源/api 定义两个环境变量 src目录下 env.development文件 写:VUE_APP_BASEURL=生产源/api env.production文件 写:VUE_APP_BASEURL=api

//拦截器 拦截器

  • 使用场景:做 登录接口健全 登录状态过期的判断
import axios from 'axios'
const request = axios.create({
  baseURL: process.env.VUE_APP_BASEURL,
  timeout: 6000
})
request.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  return config
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error)
})
// 添加响应拦截器
request.interceptors.response.use(function (response) {
  // 2xx 范围内的状态码都会触发该函数。
  // 对响应数据做点什么
  return response
}, function (error) {
  // 超出 2xx 范围的状态码都会触发该函数。
  // 对响应错误做点什么
  return Promise.reject(error)
})
export default request
<script>
import { fetchItems } from '_api'
export default {
  name: 'ItemList',
  data () {
    return {
      items: []
    }
  },
  methods: {
    fetchItems () {
      fetchItems({ page: 1, pageSize: 10 }).then(res => {
        console.log(res)
      })
    }
  },
  created () {
    this.fetchItems()
  }
}
</script>

mock接口

  • 前后端分离 同时开发时,前端在没有真实接口的情况下 ,根据接口文档mock接口 要求:1 每个接口 和请求的方式 path和后端的真实接口 保持一致 2.每个接口 返回的数据格式和真实接口保持一致

本地mock

mock接口

前后端分离 前后端同时开发 前端在开发中 没有真实接口,前端根据接口文档mock接口 要求 1.每个接口 和 请求的方式 path和后端真实接口保持一致 2.每个接口 返回的数据格式 和真实接口保持一致 利用mockjs mock接口 原理 拦截ajax请求 请求触发 但是没有真实发出去(network审查不到)

  • 安装 cnpm i mockjs -D
  • mock api
const Mock = require('mockjs')
// 模拟接口
Mock.mock('api/getItemList','post', {
// 参数1   模拟接口path  参数2 请求的方式 参数3 返回数据
+ 产生随机数api
```js
  code: 200,
  msg: 'succss',
  // 量词 数组 长度 字符串 number(大小范围)
  'data|10': [] //固定长度 10个数组
  'data|10-20': [] //范围长度 10-20
  'str'|2:'☆☆'   //☆☆☆☆
  'str'|2-3:'☆☆' //☆☆☆☆-☆☆☆☆
  'num|0-100':0   //初始值为0
  // 量词 修饰字段 id
  "id|+1" :1 //自增1
  //随机数据 占位符 占位符需要在 字段值中 在引号中前面要加@才能生效,避免冲突
})

案例 api--index.js

const Mock = require('mockjs')
// 模拟接口
Mock.mock('api/getItemList','post', {
  code:200,
  msg:'请求成功',
  data:{
     "total|100-500": 100,
   'item|10':[
     {
    'id|+1':1,
    itemName:'@ctitle',
    mold:'@ctitle',
    'onsale|0-1':0,
    "thumb":"@image('100x100','@color','@name')",
    "stire|1-1000":1,
    "createAt":0
   }
     ]
  }
})

+main.js require('./mock') 按需引入,后面需要删除的,(在代码中按需引入一些包)

import axios from 'axios'
import { fetchItems } from '_api'
export default {
  name: 'ItemList',
  data () {
    return {
      items: []
    }
  },
  methods: {
/*     fetchItems () {
      fetchItems({ page: 1, pageSize: 10 }).then(res => {
        console.log(res)
      })
    } */
    fetchItems () {
      axios.post('/api/getIists').then(res => {
        console.log(res);
      })
    }
  },
  created () {
    this.fetchItems()
  }
}

在线接口mock平台

可以真的发出请求 产生随机数 利用mockjs语法

  • 安装 cnpm i mockjs -D
  • PowerShell中无法执行vue命令
  • 如果报错了,是PowerShell权限不够的问题,如何解决? 右键---以管理员身份运行--输入set-ExecutionPolicy RemoteSigned
  • 如何遇到这个问题 打开cmd 用cmd命令行

六、layout基础布局

  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
  • 配置后 重新启动项目 npm run serve

分析网页布局

一、主页 对应两个路由组件

  • layout组件
  • 二级路由 在 layout中间部分 进行渲染
  • Container 布局容器
  <el-container>
      <el-aside width="200px">Aside</el-aside>
      <el-container>
        <el-header>Header</el-header>
        <el-main>Main</el-main>
      </el-container>
    </el-container>

1. main.js 引入组件

import { Container, Aside, Header, Main } from 'element-ui' Vue.use(Container) .use(Aside) .use(Header) .use(Main)

  • 不是所有组件都需要加deep 若子组件的style里没有scope 可以不加deep,先尝试不加,改不掉的时候再加

2.子路由放在 (LayOut--index.vue)

  <el-main>
      <router-view />
  </el-main>
  • 子路由的出口,在此处渲染

vue 项目中css文件、字体图标、图片的引入和用法

我们一般会在 src 目录下的 assets 的文件夹里面统一放置 css 文件和图片。 (网站)[t.zoukankan.com/smile-fanyi…]

(1) css

1、全局引入 在 main.js 中: import "./assets/css/common.css"; 2、组件引入

@import "../../assets/css/base.scss"; /* 实际项目中写法根据自己的文件路径 */ 

注意:组件内引入不能用@代理路径,会报错的!如:

@import "@/assets/css/base.scss" 如果是引入多个的话:

@import "../../assets/css/base.scss"; //注意这里必须加分号 @import "https://unpkg.com/element-ui@2.3.7/lib/theme-chalk/index.css";

注意:引入语句后面最好加上分号,只引用一个还好,引用多个不加分号会报错。 如果用 require 引入的话,是在 中引入:

(2)字体图标

介绍两个 css 常用的字体图标库:阿里巴巴矢量图标(iconfont) 和 Font Awesome

1、iconfont(iconfont不能用npm安装的方式使用,只能下载文件,把文件拷贝到项目中使用) step1:打开官网 www.iconfont.cn/ ,用自己的 github 账号登录,在输入框里面输入自己想要的图标,输入中文即可(如:“保存”、“购物车”等): step2:选择自己想要的图标,点击第一个图标“添加入库”。 step3:添加过后,可以看到右上角库里面的数字就变了。 step4:点击右上角购物车的图标,点击“添加至项目”。没有项目的话就自己新建一个项目。 step5:点击“下载至本地” step6:下载完毕,解压。在你的项目下src/assets文件夹里建立一个 fonts 文件夹,放入解压之后的字体图标文件。如下图: 其中demo的文件可以不放入,但是我们可以在demo_index.html 中查看图标的类名。 step7:进入 inconfont.css 文件,修改以下路径: @font-face 中引用都是当前目录下(assets/fonts)的文件,里面是相对路径。 step8:在 main.js 文件中引入 iconfont.css : step9:如何在项目中使用字体图标呢,其实很简单,创建一个 i 标签或者 span 标签,添加两个类名,一个固定的是 iconfont ,另一个是你想要的那个图标对应的类名:

如果项目中碰到后续需要添加字体新图标的,可以参考这篇文章:iconfont 怎么添加新的图标 2、Font Awesome 2-1、npm 安装的方式使用 step1:命令行执行 npm install --save font-awesome 。在 package.json 中可以查看到: step2:在 main.js 中引入: import 'font-awesome/css/font-awesome.css' step3:在组件中使用:参考官网的案例,直接定义 i 标签,添加相应的类名即可。 2-2、下载文件的方式使用 step1:百度搜索“Font Awesome”,打开网址,下载旧版 v4.7(v5版需要付费)。 step2:下载过后的文件解压,得到一个 font-awesome-4.7.0 的文件夹,里面的内容为:

step3:在 src/assets 下新建一个 font-awesome 的文件夹,把上面的解压后 font-awesome-4.7.0 文件里面的内容全部拷贝过来。 注意:别动里面的目录结构,因为 css/font-awesome.min.css 引用的是 assets/font-awesome/fonts 下的文件。 step4:在 main.js 中引入: import './assets/font-awesome/css/font-awesome.min.css'; step5:在组件中使用:参考官网的案例,直接定义 i 标签,添加响应的类名即可。

(3)图片

一般会在 src/assets 文件夹下建立一个 imgs 的文件夹,里面专门用来存放图片。 通过 img 标签引入图片 方法一(通过 require() 引入):

data() { return { //imgUrl: require('../../assets/imgs/test.png') //实际项目中注意路径 imgUrl: require('@/assets/imgs/test.png') // @ is an alias to /src } } vue中,如果没有在 vue.config.js 中定义,@ 默认指的路径就是 /src

方法二(通过 import 引入):

//import testpng from '../../assets/imgs/test.png' //相对路径 import testpng from '@/assets/imgs/test.png' // 别名路径 data() { return { imgUrl: testpng } } 个人觉得项目中直接用 @ 别名路径 更方便,避免一层层的去查看文件位置。 ###(4)背景图片 在 js 和 template 中引入图片资源需要使用 require 想在 template 上写,require一下地址,如:

想在 css 里面写,写法如下: 注意:这里写相对路径 background: url("../../assets/imgs/1.jpg") no-repeat 和路径别名 background: url("@/assets/imgs/1.jpg") no-repeat 都不行。 必须用 ~@ 的别名

侧边栏Aside

LayOut 单独创建一个 components 把侧边组件单独拿出来 定义一个子路由 CommontAsize.vue vbase

 <template>
  <div class="common-aside">
    侧边组件
  </div>
</template>
​
​
<script>
  export default {
    
  }
</script><style lang="scss" scoped></style>

侧边组件的出口 在LayOut--index.vue处渲染

  <el-aside width="200px">
        <common-aside />
   </el-aside>
<script>
import CommonAside from './components/CommonAside.vue'
export default {
  name: 'LayOut',
  components:{
    CommonAside
  }
}
</script>

三、导航菜单

(官网)[element.eleme.cn/#/zh-CN/com…] CommontAsize.vue

css 若权重不够,加!important
::v-deep .el-menu-item.is-active{
  background-color: #fff !important;
}

#(一)放在侧边导航的 ItemLists CartLists ItemAdd UserInfo Websetting

引入字体图标

(官网)www.iconfont.cn/ [18609682340 密码 ****** ****** ] 下载项目也可以,好处是可以自定义类名的前缀 ①搜索 商城 ---商城入库---cart--- 下载代码 ②文件解压---新建文件夹fonts---(iconfont.css---iconfont.ttf)(将此放在同一目录fonts下) ③---放到assets文件夹 ④main.js引入iconfont.css即可 (css定义字体图标)

step9: step9:如何在项目中使用字体图标呢,其实很简单,创建一个 i 标签或者 span 标签,添加两个类名,一个固定的是 iconfont ,另一个是你想要的那个图标对应的类名: **

也可以使用element-ui内置图标

Icon图标

index 高亮

   <el-menu-item index="/dashBoard">
   高亮 --对应的path是谁 谁高亮
    default-active="/dashBoard"
    default-active对应的是每个导航的index属性
   (官网)[https://element.eleme.cn/#/zh-CN/component/menu]
   Menu Attribute  
  参数                说明                           类型         可选值   默认值
  • default-active 当前激活菜单的 index string — —

  • router 是否使用 vue-router 的模式, 启用该模式会在激活导航时 以 index 作为 path 进行路由跳转 boolean — false 写法 :router="true" 简写 router (当值为布尔值时,省略值,默认是true) 这是一个自定义组件 写法: index = 路由的地址

    :router="true"

    :default-active="$route.path

四、放在头部的导航

CommonHead.vue 引入方式同侧边栏

NavTables.vue 引入方式同侧边栏

  • 超出一定宽度时,默认不缩放 .tab{ flex-shrink: 0; }

五、侧边导航菜单折叠

export default {
  namespaced: true,
  state: {
    //侧边导航折叠状态
    navCollapse:false
  },
  mutations: {
    /* HIDE_MENU(state) {
      //导航折叠
      state. navCollapse = true
    },
    SHOW_MENU(state) {
      //导航展开
      state. navCollapse = false
    } */
    用这种方式
    定义一个按钮,状态切换(因为此处就一个按钮)
    TOGGLE_MENU(state) {
      //导航菜单 展开和关闭
      state.navCollapse = !state.navCollapse
    }
  }
}
+ 2.store---index.js挂载
```js
import Vue from 'vue'
import Vuex from 'vuex'
import nav from './modules/nav'
Vue.use(Vuex)
​
export default new Vuex.Store({
  strict:true,
  modules: {
    nav
  }
})
  • 3.CommonAside.vue 状态绑定 <el-menu :collapse="$store.state.nav.navCollapse"
# 调用mutations方法(TOGGLE_MENU) 改变状态
   图标也要跟随状态切换(侧边菜单展开显示大图,折叠展示小图)
```html
   <div class="logo">
         <img :src="require('../../../assets/image/min.jpg')" v-if="$store.state.nav.navCollapse" class="logo1" alt="" />
          <img :src="require('../../../assets/image/logo.png')" v-else class="logo2" alt="" />
   </div>
  • 4.LayOut---index.vue
  <el-aside width="auto"> 内容撑开
    折叠时是auto,展开时是200
      <el-aside :width="$store.state.nav.navCollapse?'auto':'200px'">
  • 5.CommonHead.vue 状态绑定(定义切换图标) <i @click="toggleMenu" :class="[$store.state.nav.navCollapse? 'el-icon-s-unfold' :'el-icon-s-fold', 'head-icon']">
  export default {
    methods: {
      toggleMenu () {
        this.$store.commit('nav/ TOGGLE_MENU') //加了模块名, 因为开启了命名空间namespaced: true,
      }
    }
  }

六 增加navTab实现

  • 思路: store--modules-nav.js 点击tab动态菜单实现路由跳转 数组在不同的地方展示,只要路由变化 就会添加 数组是个公共的,路由变化 push 一个按钮 进去 按钮需要:跳转名字,传递两个对象{当前路由name,当前路由path} // tab是对象{name: '增加商品'path:'xxx'}

+state.navTabs.push(tab) 监听路由变化 (路由变化时增加)--->给路由新增一个 路由后置守卫 1.主页 点击 回到仪表盘 // 通过配置全局后置路由守卫 // 监听路由的变化 从而动态添加路由tab

+ import store from './store'
router.afterEach((to, from) => {
  const params = {
    name: to.name,
    path: to.path
  }
  // 动态添加navtab
  // 调用vuex里面的方法
  store.commit('nav/ADD_TAB', params)
})
​
+ store--modules-nav.js
插入之前判断
1.是否已经有当前tab 有,不插入
2.是否是 /dashBorad 仪表盘是不插入
​
​
+ tab的高亮处理  NavTabs.vue
    <span class="sp">主页</span>
  </div>
  <div  v-for="tab in $store.state.nav.navTabs" :class="['tab',{
    active: $route.path === tab.path}]" 
     @click="switchTab(tab.path)"
    :key="tab.path">
 export default {
   // 点击tab动态菜单实现路由跳转
    switchTab(path) {
      this.$router.push(path)
    },
  }

七 删除navTab实现

  • store--modules-nav.js 删除当前的这个index
  • 父元素绑定了一个事件跳转路由,需要加stop阻止 引入router---index.js 在这里写 思路: 1.删除navTab 2.当前导航tab删除后 ,跳转到 数组的 上一个 tab对应的路由中 (其实就是跳转到 数组的最后一个tab的path) 已经全部删除,需要跳转到仪表盘

面试题 # vuex状态持久化

当刷新浏览器 vuex状态 会丢失(初始化) 如何保持状态

原理: 将状态 在 缓存中备份(数据改变时),当刷新时 ,取缓存中的备份

  • 方法一:使用插件 vuex-persist 缺点: 不够灵活,无法使用 某个模块 中某些状态持久化,某些不持久化,无法使用某些状态 使用sessionStorage 持久化 另一些 localStorage持久化
npm i vuex-persist -S
​
import VuexPersistence from  'vuex-persist'const vuexLocal = new VuexPersistence({
  // 决定什么什么缓存备份
  storage: window.localStorage,
  // 决定哪些状态需要缓存
  reducer: state => {
    return {
      nav: state.nav
    }
  }
})
​
​
export default new Vuex.Store({
  strict: true,
  modules: {
    nav
  },
  plugins: [vuexLocal.plugin]
})
​

方法二:

  • 手写 注意: 如果这个数据是多次都要修改,那么记住 只要变化就需要缓存备份一下 // 缓存备份 sessionStorage.setItem('navTabs', JSON.stringify(state.navTabs)) // 取缓存 先判断有没有,有的话,JSON.parse(); 没有的话,初始值:给个空数组或者空对象 ①为什么要给空数组/空对象:如果第一次打开浏览器 或者 通过某些删除垃圾的插件把缓存清除,取缓存的时候,值为undefined 调用JSON.parse会报错 ②为什么要给空数组/空对象 因为如果给的是字符串,第一次无缓存,因为第一次调用push方法,用字符串会报错

    注意:取的时候,做个三目判断,有的话,才能JSON.parse();没有的话,给这个存储的数据类型初始值为 空对象/空数组

        因为很有可能会调用数据类型的API
    

八 请求商品列表

九 增加商品分类

十 表单验证 CateAdd.vue

官网 import { addCate } from '_api'

  • 正则校验
  • 判断校验是否通过 先获取表单 ref="cateAdd" methods: { onSubmit () { //提交表单新增商品分类 this.$refs.cateAdd.validate } } +拿到表单 调用validate 方法做校验 对整个表单进行校验的方法,参数为一个回调函数。该回调函数会在校验结束后被调用,并传入两个参数:是否校验成功和未通过校验的字段。 若不传入回调函数,则会返回一个 promise Function(callback: Function(boolean, object))

++ eg: data() { 自定义校验函数 const validator1 = (rule, value, callback) => {

      /* rule 当前 字段所有校验规则
      value 校验时这个字段值 (用value判断)
      cb
        希望校验通过 直接调用cb() 不传参数
        希望校验不通过 cb(new Error('这就是错误提示文本')) */
       if(value !=='y886') {
         callback(new Error('必须输入y886'))
      }else {
       callback()
       }
   }
   自定义校验(自定义函数校验)
     {
       validator: validator1
     }

十一 增加商品分类业务实现 CateAdd.vue

1.CateList--index.vue(在分类列表组件中) ++ pid allCates: []//将所有分类数组 传入增加和修改分类使用 增加和修改分类的时候:从已有的父级分类中选择一个作为当前的分类 ①·······请求········· // 获取所有分类 传递给增加和修改商品作为父级分类选择使用(什么都不传,所有的都获取) fetchAllCateLists () { fetchCateLists().then(res => { if (res.data.code === 200) { this.allCates = res.data.data.item } }) } 传递所有分类allCates --cateList--index.vue ②·······传递········· //使用子组件

 data () {
    return {
      cates: [], 
         allCates: [] //将所有分类数组 传入增加和修改分类使用   
    }

③······调用········· created () { this.fetchCateLists() this.fetchAllCateLists() }, //注册子组件 components:{ CateAdd }

2.CateAdd.vue 用props接受父组件传递的数据

   export default {
    props:{
      cates: {
        type: Array,
        required: true
      }
    },

循环除 顶层分类 外的 所有分类(因为顶层分类不在循环内) prop="pid"字段作为父级分类id el-option 代表 父级分类 所有的备选

+子分类的pid = 父级分类的id

++ mock接口 做增加商品列表 封装(增加商品分类)接口api---index.js

导出 ---CateAdd.vue中引入

提交 import {message} from 'element-ui' onSubmit () { //提交表单新增商品分类 this.refs.cateAdd.validate(valid => { if(valid) { // 成功验证 提交表单 addCate(this.cate).then(res => { //console.log(res) ++ // 操作结果以 弹出提示框 的方式 告诉用户 if (res.data.code === 200) { this.message.success({ type: 'success', message: res.data.msg, duration: 1500, onClose: () => { // 刷新页面 this.$router.go(0) } }) } }) }else { return false } })

上传文件

项目所有跟上传文件相关接口

比如后台管理中 新增各种数据 商品 商品分类 xxxx所有数据中很多都需要上传一个文件(图片)

接口处理逻辑: 接口一定会单独 有一个独立接口 叫 upload 调用这个接口上传 会立即返回上传成功后的 文件地址,增加商品接口拿到 上传的 图片地址后,一起提交给新增商品接口

action="/api/upload"
上传文件接口   前面要携带/api

++ 为什么请求接口前面不需要携带,因为发送方式不一样,是用request发送的,定义了BASEURL utils---request.js(有的人用http,只是文件名不一样而已)

const request = axios.create({
  baseURL: process.env.VUE_APP_BASEURL,
  timeout: 6000
})
   const addCate = (params = {}) => (
  request.post('/addCate',params)
   )
  • 点击上传
先获取点击上传按钮
  ref="upload"
  <el-button @click="uploadFile" size="small" type="success">点击上传</el-button>
  methods: {  
      uploadFile() {
        //手动上传文件 调用upload的一个submit方法
        this.$refs.upload.submit()
      },
accept  接受上传的文件类型(thumbnail-mode 模式下此参数无效)
auto-upload 是否在选取文件后立即进行上传  boolean auto-upload="false"
:before-upload="beforeUpload" 上传之前的拦截
before-upload   上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。
 ```JS
      beforeUpload (file) {
      /*
        参数是1 选中的文件
        return true 则继续上传
        return false 终止上传
      */
      if (file.size > (500 * 1024)) {
        alert('上传文件体积不能超出500kb')
        return false
      } else {
        return true
      }
    },

uploadSucess (res) { // 上传成功回调 if (res.code === 200) { this.cate.thumb = res.data.url this.$message({ type: 'success', message: '上传成功' }) } },

++ 4.on-success 文件上传成功时的钩子  function(response, file, fileList)
[官网](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file)
(https://element.eleme.cn/#/zh-CN/component/upload)
### 仪表盘
++ echarts
+ 安装 npm i echarts - S
1.挂载在原型  main.js
import * as echarts from 'echarts'
Vue.prototype.$echarts = echarts
2.使用 (获取)
<div ref="chart" style="width: 600px; height: 400px"></div>
 this.chart = this.$echarts.init(this.$refs.chart)

商品分类 编辑删除

++ 编辑:获取需要 修改的 商品分类的 初始值 (进行渲染) 点击编辑--显示 修改商品弹窗,拿到需要修改商品的id props: {

cateDetail: {
  type: Object,
  default: () => {}
}

} 更新商品: id 字段名 ++ 第一次值不出来,用侦听器深度侦听 (侦听器触发,点击了不同商品的编辑按钮) 第一次有值了才触发 因为cate里的值不能修改,this.cate = this.cateDetail 浅克隆 watch: { cateDetail: { // 点击了不同商品的编辑按钮 handler (val) { console.log('值改变了') this.cate = this.cateDetail }, immediate: true } }, 父组件中传递CateLists--index.vue 子组件中接受 CateUpdate.vue props: { cates: { type: Array, required: true }, cateDetail: { type: Object, default: () => {} } },

仪表盘

地图---方法一:原生方式

<style type="text/css"> 
html{height:100%} 
body{height:100%;margin:0px;padding:0px} 
#container{height:100%} 
</style> 
<script type="text/javascript" src="https://api.map.baidu.com/api?v=1.0&type=webgl&ak=您的密钥">
</script>
<div id="container"></div>
  ## 方法二 组件库  npmjs.com--->vue-baidu-map(基于百度地图+vue封装的组件库)
[官网](https://github.com/Dafrok/vue-baidu-map/blob/master/README.zh.md)
(https://dafrok.github.io/vue-baidu-map/#/zh/start/usage)
(https://dafrok.github.io/vue-baidu-map/#/zh/map/baidu-map)

后台管理常用的第三方插件

echarts

导出excel (xlsx库) 方式一

导入导出Excel 实现导入导出 富文本编辑器 生成pdf并打印

导出excel 基于xlsx二次封装的Vue组件 方式二 (vue-json-excel)

  • 安装 cnpm i vue-json-excel 官网

登录鉴权 (++ 面试题:登录鉴权思路?)

  • 1.路由鉴权 在路由前置守卫中判断token是否存在,存在则判定登录允许访问,否则不能访问。 只有路由鉴权不安全!因为token只是判断是否存在,无法判断其正确性。
  • 2.接口鉴权 在请求接口过程中,前端一般将token放到请求头传递给后端,后端判断token的正确性 返回不同的code码,代表不同的token状态 (200:正确; 401:token过期; 403:未传token(未登录)
  • ①在请求拦截器中 添加token 进行接口鉴权
  const token = getToken()
  if (token) {
    config.headers.token = token
  }
  • ②在响应拦截器中 根据code 判断token是否过期 或者未登录
  if (res.data.code === 401 || res.data.code ===403){
    //token过期 或者 未登录
    message.error({
      type: 'error',
      message: '登录过期或者未登录',
      onclose() {
        //清除缓存    
​
        //去登录页
        router.push('/login')
      }
    })

+(缓存中备份 并 存储到vuex中) 备份后---- 刷新--- 取缓存中的数据--- 赋 初始值(缓存中备份 并 存储到vuex中) 备份后---- 刷新--- 取缓存中的数据--- 赋 初始值

state: {
    token: localStorage.getItem('token') || '',
    userInfo: localStorage.getItem('userInfo')
      ? JSON.parse(localStorage.getItem('userInfo'))
      : {},
    role: localStorage.getItem('role') || ''
  }

+退出登录 清除缓存(刷新页面)

 // 退出登录
   // (清空VUEX 浏览器缓存)
    CLEAR_USER_INFO (state) {
      state.token = ''
      state.userInfo = {}
      state.role = ''
      localStorage.removeItem('token')
      localStorage.removeItem('userInfo')
      localStorage.removeItem('role')
    }

++ (面试题:)如何二次封装axios?(!!要从功能方向去说,不是哪里创文件,代码怎么写) ①在axios实例中设置baseURL 配置环境变量(可以设置系统环境变量/自定义环境变量)---开发源 :/api 生产源:源/api---设置请求时间 ②配置请求拦截器 和 相应拦截器 ,请求拦截器可以设置 请求全局 loading效果 ,在相应拦截器中关闭loading ③接口鉴权中 在请求过程中 ,一般将token放到请求头中传递给后端,后端判断token的正确性,返回不同的code码 ④在响应拦截器中 通过不同的token状态 进行对应的操作

++ (面试题:)当登录状态过期后,前端应该做什么? -- 在axios响应拦截器中----> 判断code是否=401/403:是---> 弹出错误提醒(登录过期/未登陆)--->清除缓存--->去登录页

动态角色鉴权(实际开发只有一个接口,用来获取 用户 动态权限)!!

(删除之前静态角色的路由数组 ,因为是后端自动返回的) 用户访问这个角色时,会传入token 后端会根据token 判断用户角色 (会 自动返回 每个角色 的 导航数组 和 路由数组) ,动态访问会直接跳到404

  • 此时项目是模拟的,做不到,每个数据都一样的 (用两个角色模拟,为了展示效果) mock.js +用户角色鉴权1 1.导航数组 menus: [ { label: '仪表盘', path: '/dashBoard', icon: 'el-icon-s-data' }, { label: '商品', icon: 'el-icon-s-goods', children: [ { label: '商品管理', path: '/itemLists' }, { label: '商品分类管理', path: '/cateLists' }, { label: '增加商品', path: '/itemAdd' } ] }, { label: '个人中心', path: '/userInfo', icon: 'el-icon-user-solid' }, { label: '设置', path: '/webSetting', icon: 'el-icon-s-tools' } ] 2.路由数组 (前端 只定义 基础路由,所有的子路由都通过mock接口返回)

    routes:[ { path: '/dashBoard', name: '仪表盘', component: 'DashBoard' }, { path: '/itemLists', name: '商品列表', component: 'ItemLists' }, { path: '/itemAdd', name: '增加商品', component: 'ItemAdd' }, { path: '/itemUpdate', name: '更新商品', component:'ItemUpdate' }, { path: '/cateLists', name: '商品分类', component: 'CateLists' }, { path: '/userInfo', name: '个人中心', component: 'UserInfo' }, { path: '/webSetting', name: '设置', component:'WebSetting' } ]