一 项目初始化
- -①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基础布局
- (官网)[element.eleme.cn/#/zh-CN/com…]
- 安装cnpm i babel-plugin-component -D cnpm i element-ui -S
"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: () => {} } },
仪表盘
地图---方法一:原生方式
- 百度地图开放平台-注册--登录--申请密钥 web开发----JavaScript API---开发指南---Hello World 官网 (mapopen-pub-jsapi.bj.bcebos.com/jsapi/refer…) 准备一个容器(给宽高)!! 引入方式1----public-->index.html
<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' } ]