2023.7.9Vue-面经PC端

git

2023.7.9Vue-面经PC端_第1张图片

面经PC端

准备流程

  1. 打开cmd
    2023.7.9Vue-面经PC端_第2张图片

  2. 命令vue create hm-mj-element-pc
    2023.7.9Vue-面经PC端_第3张图片

  3. 选择配置2023.7.9Vue-面经PC端_第4张图片2023.7.9Vue-面经PC端_第5张图片
    2023.7.9Vue-面经PC端_第6张图片
    2023.7.9Vue-面经PC端_第7张图片
    2023.7.9Vue-面经PC端_第8张图片
    2023.7.9Vue-面经PC端_第9张图片
    2023.7.9Vue-面经PC端_第10张图片
    2023.7.9Vue-面经PC端_第11张图片

  4. 启动项目
    2023.7.9Vue-面经PC端_第12张图片

错误

npm i -D @vue/[email protected]

npm i -D @vue/eslint-config-standard@5.1.0

解决版本依赖 (版本降级命令) npm i -D @vue/[email protected]
成功后将6.1.0降级为5.1.0
2023.7.9Vue-面经PC端_第13张图片
②强行解决
2023.7.9Vue-面经PC端_第14张图片

npm run build 项目打包

2023.7.9Vue-面经PC端_第15张图片
这是因为引用资源的路径问题,我们只要在下图的地方修改一下再打包就可以了。

 publicPath: './'

2023.7.9Vue-面经PC端_第16张图片
再次npm run build就可以了

npm i

下载依赖

npm i

element ui 组件库 - 全部 & 按需导入

  1. npm 安装
npm i element-ui -S
  1. 全局导入
//全局导入
//main.js
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.use(ElementUI);
//App.vue
<el-button round>圆角按钮</el-button>
  <el-button type="primary" round>主要按钮</el-button>
  <el-button type="success" round>成功按钮</el-button>
  <el-button type="info" round>信息按钮</el-button>
  <el-button type="warning" round>警告按钮</el-button>
  <el-button type="danger" round>危险按钮</el-button>
  1. 按需导入
  • 安装 babel-plugin-component
npm install babel-plugin-component -D
  • babel.config.js
//babel.config.js
module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
  plugins: [
    [
      'component',
      {
        libraryName: 'element-ui',
        styleLibraryName: 'theme-chalk'
      }
    ]
  ]
}

// 按需导入
//main.js
import 'element-ui/lib/theme-chalk/index.css'
import { Button} from 'element-ui'
Vue.use(Button)
//App.vue
<el-button round>圆角按钮</el-button>
  <el-button type="primary" round>主要按钮</el-button>
  <el-button type="success" round>成功按钮</el-button>
  <el-button type="info" round>信息按钮</el-button>
  <el-button type="warning" round>警告按钮</el-button>
  <el-button type="danger" round>危险按钮</el-button>
  1. 在src中新建utils/element.js—引入并剥离element-ui组件
// // 按需导入
import Vue from 'vue'
import {
  Button,
  Popconfirm,
  Avatar,
  Breadcrumb,
  BreadcrumbItem,
  Pagination,
  Dialog,
  Menu,
  Input,
  Option,
  Table,
  TableColumn,
  Form,
  FormItem,
  Icon,
  Row,
  Col,
  Card,
  Container,
  Header,
  Aside,
  Main,
  Footer,
  Link,
  Image,
  Loading,
  MessageBox,
  Message,
  Drawer,
  MenuItem
} from 'element-ui'

import 'element-ui/lib/theme-chalk/index.css'// 样式

Vue.use(Breadcrumb)
Vue.use(BreadcrumbItem)
Vue.use(Drawer)
Vue.use(Popconfirm)
Vue.use(Avatar)
Vue.use(Pagination)
Vue.use(Dialog)
Vue.use(Menu)
Vue.use(MenuItem)
Vue.use(Input)
Vue.use(Option)
Vue.use(Table)
Vue.use(TableColumn)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Icon)
Vue.use(Row)
Vue.use(Col)
Vue.use(Card)
Vue.use(Container)
Vue.use(Header)
Vue.use(Aside)
Vue.use(Main)
Vue.use(Footer)
Vue.use(Link)
Vue.use(Image)

Vue.use(Loading.directive)

Vue.prototype.$loading = Loading.service
Vue.prototype.$msgbox = MessageBox
Vue.prototype.$alert = MessageBox.alert
Vue.prototype.$confirm = MessageBox.confirm
Vue.prototype.$prompt = MessageBox.prompt
Vue.prototype.$notify = Notification
Vue.prototype.$message = Message
Vue.use(Button)

//main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
// 引入组文件
import '@/utils/element'

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

2023.7.9Vue-面经PC端_第17张图片

主题定制

新建src/styles/theme.scss
2023.7.9Vue-面经PC端_第18张图片

//theme.scss
/* 改变主题色变量 */
$--color-primary:rgba(114,124,245,1);

/* 改变 icon 字体路径变量,必需 */
$--font-path: '~element-ui/lib/theme-chalk/fonts';

@import "~element-ui/packages/theme-chalk/src/index";
//初始化body样式
body {
    margin: 0;
    padding: 0;
    background: #fafbfe;
  }
//main.js
// 引入主题文件
import '@/styles/theme.scss'

2023.7.9Vue-面经PC端_第19张图片
2023.7.9Vue-面经PC端_第20张图片

设计路由前准备文件及配置路由

2023.7.9Vue-面经PC端_第21张图片
2023.7.9Vue-面经PC端_第22张图片

路由配置

  1. 路由懒加载
    component:()=>import(‘路径’)’
const router = new VueRouter({
  routes: [
    {
      path: '/login',
      component: () => import('@/views/login/index.vue')
    }
  ]
})
  1. 路由配置router/index.js
//router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const router = new VueRouter({
  routes: [
    {
      path: '/login',
      component: () => import('@/views/login/index.vue')
    },
    {
      path: '/',
      component: () => import('@/views/layout/index.vue'),
      redirect: '/dashboard',
      children: [
        {
          path: '/article',
          component: () => import('@/views/article/index.vue')
        },
        {
          path: '/dashboard',
          component: () => import('@/views/dashboard/index.vue')
        }
      ]
    }
  ]
})

export default router

  1. App.vue
//App.vue配路由出口
  <router-view></router-view>
  1. 给含有二级路由的layout配置路由出口
//layout/index.vue
  <router-view></router-view>

2023.7.9Vue-面经PC端_第23张图片

登录模块

  1. 深度作用选择器:从当前去修改子组件深层穿透样式 less中是/deep/
    ::v-deep .el-card__header

  2. 要实现表单的校验,需要做的事情

  • 要准备好校验规则
  • 要给 el-form绑定 model 和rules
  • 需要给 el-form-item 设置prop属性
  1. element 校验数据的 长度范围 规则
  • { min: 5, max: 11, message: '长度在5到11个字符', trigger: 'blur' }
  • ②trigger 可以支持两种类型: 字符串 / 字符串的数组
  • { pattern: /^\w{5,11}$/, message: '密码长度在5到11个字符', trigger: ['blur', 'change'] }
  • 正则匹配:完全匹配精准匹配,模糊匹配(包含匹配)

2023.7.9Vue-面经PC端_第24张图片

  rules: {
        // trigger 可以支持两种类型: 字符串 / 字符串的数组
        username: [
          { required: true, message: '用户名不能为空', trigger: 'blur' },
          // 校验数据的长度范围
          // 方法1
          { min: 5, max: 11, message: '长度在5到11个字符', trigger: 'blur' }
        ],
        password: [
          { required: true, message: '密码不能为空', trigger: ['blur', 'change'] },
          // 长度校验用法2
          // 正则匹配:完全匹配精准匹配,模糊匹配(包含匹配)
          { pattern: /^\w{5,11}$/, message: '密码长度在5到11个字符', trigger: ['blur', 'change'] }
        ]
      }

login/index.vue

//login/index.vue
<template>
  <div class="login-page">
    <el-card>
      <template #header>黑马面经运营后台</template>
      <!-- 要实现表单的校验,需要做的事情 -->
      <!-- - 要准备好校验规则 -->
      <!-- - 要给 el-form绑定 model 和rules -->
      <!-- - 需要给 el-form-item 设置prop属性 -->
      <el-form autocomplete="off" :model="loginForm" :rules="rules">
        <el-form-item label="用户名" prop="username">
          <el-input placeholder="输入用户名" v-model="loginForm.username"></el-input>
        </el-form-item>

        <el-form-item label="密码" prop="password">
          <el-input type="password" placeholder="输入用户密码" v-model="loginForm.password"></el-input>
        </el-form-item>

        <el-form-item class="tc">
          <el-button type="primary">登 录</el-button>
          <el-button>重 置</el-button>
        </el-form-item>
      </el-form>
    </el-card>
  </div>
</template>

<script>
export default {
  name: 'login-page',
  data () {
    return {
      loginForm: {
        username: '',
        password: ''
      },
      rules: {
        // trigger 可以支持两种类型: 字符串 / 字符串的数组
        username: [
          { required: true, message: '用户名不能为空', trigger: 'blur' },
          // 校验数据的长度范围
          // 方法1
          { min: 5, max: 11, message: '长度在5到11个字符', trigger: 'blur' }
        ],
        password: [
          { required: true, message: '密码不能为空', trigger: ['blur', 'change'] },
          // 长度校验用法2
          // 正则匹配:完全匹配精准匹配,模糊匹配(包含匹配)
          { pattern: /^\w{5,11}$/, message: '密码长度在5到11个字符', trigger: ['blur', 'change'] }
        ]
      }
    }
  },
  methods: {

  }
}
</script>

<style lang="scss" scoped>
.login-page {
  min-height: 100vh;
  background: url(@/assets/login-bg.svg) no-repeat center / cover;
  display: flex;
  align-items: center;
  justify-content: space-around;

  .el-card {
    width: 420px;

    // 深度作用选择器:从当前去修改子组件深层穿透样式less中是/deep/
    ::v-deep .el-card__header {
      height: 80px;
      background: rgba(114, 124, 245, 1);
      text-align: center;
      line-height: 40px;
      color: #fff;
      font-size: 18px;
    }
  }

  .el-form {
    padding: 0 20px;
  }

  .tc {
    text-align: center;
  }
}</style>

2023.7.9Vue-面经PC端_第25张图片

在真正登录之前,手动校验一次

  1. 如何手动校验=>调用el-form组件实例的validate()
  2. 如何得到el-form组件实例 ref+$refs
  3. 1 validate返回的是一个promise, 错误就会进catch
  • .then校验成功
  • .catch校验失败
 this.$refs.form.validate().then(() => {
        console.log('校验成功')
        // 调用接口,发请求,获取token
        // 跳转到管理台首页
      }).catch(err => {
        console.log(err, '校验失败')
      })

2023.7.9Vue-面经PC端_第26张图片

  1. 2 回调函数会返回一个布尔值 true为填写符合规范false反之
 this.$refs.form.validate((isok) => {
        console.log(isok)
        // isok: false ==> 校验失败 ==> 提示用户
        // isok true ==>校验成功 ==> 登录流程
        // 登录流程是什么?
        // 1. 发请求, 换取token
        // 2. 跳转到首页  /dashboard
      })

2023.7.9Vue-面经PC端_第27张图片

validate

对整个表单进行校验的方法,参数为一个回调函数。该回调函数会在校验结束后被调用,并传入两个参数:是否校验成功和未通过校验的字段。若不传入回调函数,则会返回一个 promise

2023.7.9Vue-面经PC端_第28张图片

请求封装

  1. npm i -S axios
    2023.7.9Vue-面经PC端_第29张图片
//utils/request.js

import axios from 'axios'

// 创建一个新的请求实例对象
const instance = axios.create({
  baseURL: 'http://interview-api-t.itheima.net',
  timeout: 1000 * 3
})
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  return config
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error)
})

// 添加响应拦截器
instance.interceptors.response.use(function (response) {
  // 2xx 范围内的状态码都会触发该函数。
  // 对响应数据做点什么
  return response
}, function (error) {
  // 超出 2xx 范围的状态码都会触发该函数。
  // 对响应错误做点什么
  return Promise.reject(error)
})

export default instance

//api/user.js
import request from '@/utils/request'

// 处理用户的登录请求
export function userLogin (data) {
  return request.post('/auth/login', data)
}

获取token

2023.7.9Vue-面经PC端_第30张图片

//store/index.js
import { userLogin } from '@/api/user'
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
    user: {
      // 开启命名空间
      namespaced: true,
      state: {
        token: ''
      },
      mutations: {
        setToken (state, token) {
          state.token = token
        }
      },
      actions: {
        async loginAction (context, payload) {
          // 发请求,换token
          // 提交mutations,更新token
          const res = await userLogin(payload)
          const token = res.data.token
          // 设置token到store中
          context.commit('setToken', token)
        }
      }
    }
  }
})

//utils/request.js

import axios from 'axios'

// 创建一个新的请求实例对象
const instance = axios.create({
  baseURL: 'http://interview-api-t.itheima.net',
  timeout: 1000 * 3
})
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  return config
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error)
})

// 添加响应拦截器
instance.interceptors.response.use(function (response) {
  // 2xx 范围内的状态码都会触发该函数。
  // 对响应数据做点什么
  return response.data
}, function (error) {
  // 超出 2xx 范围的状态码都会触发该函数。
  // 对响应错误做点什么
  return Promise.reject(error)
})

export default instance

//login/index.js
<template>
  <div class="login-page">
    <el-card>
      <template #header>黑马面经运营后台</template>
      <!-- 要实现表单的校验,需要做的事情 -->
      <!-- - 要准备好校验规则 -->
      <!-- - 要给 el-form绑定 model 和rules -->
      <!-- - 需要给 el-form-item 设置prop属性 -->
      <el-form ref="form" autocomplete="off" :model="loginForm" :rules="rules">
        <el-form-item label="用户名" prop="username">
          <el-input placeholder="输入用户名" v-model="loginForm.username"></el-input>
        </el-form-item>

        <el-form-item label="密码" prop="password">
          <el-input type="password" placeholder="输入用户密码" v-model="loginForm.password"></el-input>
        </el-form-item>

        <el-form-item class="tc">
          <el-button type="primary" @click="doLogin">登 录</el-button>
          <el-button>重 置</el-button>
        </el-form-item>
      </el-form>
    </el-card>
  </div>
</template>

<script>
// import { userLogin } from '@/api/user'
import { mapActions } from 'vuex'
export default {
  name: 'login-page',
  data () {
    return {
      loginForm: {
        username: 'admin',
        password: 'admin'
      },
      rules: {
        // trigger 可以支持两种类型: 字符串 / 字符串的数组
        username: [
          { required: true, message: '用户名不能为空', trigger: 'blur' },
          // 校验数据的长度范围
          // 方法1
          { min: 5, max: 11, message: '长度在5到11个字符', trigger: 'blur' }
        ],
        password: [
          { required: true, message: '密码不能为空', trigger: ['blur', 'change'] },
          // 长度校验用法2
          // 正则匹配:完全匹配精准匹配,模糊匹配(包含匹配)
          { pattern: /^\w{5,11}$/, message: '密码长度在5到11个字符', trigger: ['blur', 'change'] }
        ]
      }
    }
  },
  methods: {
    ...mapActions('user', ['loginAction']),
    doLogin () {
      // 在真正登录之前,手动校验一次
      // 如何手动校验=>调用el-form组件实例的validate()
      // 如何得到el-form组件实例 ref+$refs

      // validate返回的是一个promise, 错误就会进catch
      // .then校验成功
      // .catch校验失败
      // this.$refs.form.validate().then(() => {
      //   console.log('校验成功')
      //   // 调用接口,发请求,获取token
      //   // 跳转到管理台首页
      // }).catch(err => {
      //   console.log(err, '校验失败')
      // })

      // 回调函数会返回一个布尔值 true为填写符合规范false反之
      this.$refs.form.validate(async (isok) => {
        console.log(isok)
        // isok: false ==> 校验失败 ==> 提示用户
        // isok true ==>校验成功 ==> 登录流程
        // 登录流程是什么?
        // 1. 发请求, 换取token
        // 2. 跳转到首页  /dashboard
        if (!isok) return
        this.loginAction(this.loginForm)
        // //const res = await userLogin(this.loginForm)
        // // console.log(res)
        // // console.log(res.data.token)
        // //const { token } = res.data
        // //console.log(token)
      })
    }
  }
}
</script>

<style lang="scss" scoped>
.login-page {
  min-height: 100vh;
  background: url(@/assets/login-bg.svg) no-repeat center / cover;
  display: flex;
  align-items: center;
  justify-content: space-around;

  .el-card {
    width: 420px;

    // 深度作用选择器:从当前去修改子组件深层穿透样式less中是/deep/
    ::v-deep .el-card__header {
      height: 80px;
      background: rgba(114, 124, 245, 1);
      text-align: center;
      line-height: 40px;
      color: #fff;
      font-size: 18px;
    }
  }

  .el-form {
    padding: 0 20px;
  }

  .tc {
    text-align: center;
  }
}
</style>

2023.7.9Vue-面经PC端_第31张图片

2023.7.9Vue-面经PC端_第32张图片

token持久化存储

封装的好处

  1. 可以实现模块化
  2. 可维护=>如果未来需要修改本地存储方案的话,直接改当前这个文件即可
//utils/storage.js
// 增删改查
// localstorage
// setTime
// setItem :增 改
// getItem :查
// removeItem:删
// 封装的好处
// 1. 可以实现模块化
// 2. 可维护
// 如果未来需要修改本地存储方案的话,直接改当前这个文件即可

export function setStorage (key, value) {
  return localStorage.setItem(key, value)
}
export function getStorage (key) {
  return localStorage.getItem(key)
}
export function delStorage (key) {
  return localStorage.removeItem(key)
}

//store/index.js
import { userLogin } from '@/api/user'
import Vue from 'vue'
import Vuex from 'vuex'
import { setStorage, getStorage } from '@/utils/storage'
Vue.use(Vuex)

export default new Vuex.Store({
  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
    user: {
      // 开启命名空间
      namespaced: true,
      state: {
        // token: '',
        // 当页面初始化的时候,从本地存储中读回token,如果读出来是 undefined ,就用 空字符串 代替
        token: getStorage('HM-TOKEN') || ''
      },
      mutations: {
        setToken (state, token) {
          state.token = token
          // 将token持久化存储
          setStorage('HM-TOKEN', token)
        }
      },
      actions: {
        async loginAction (context, payload) {
          // 发请求,换token
          // 提交mutations,更新token
          const res = await userLogin(payload)
          const token = res.data.token
          // 设置token到store中
          context.commit('setToken', token)
        }
      }
    }
  }
})

2023.7.9Vue-面经PC端_第33张图片

抽离了 user, store模块

//store/modules/user.js
import { userLogin } from '@/api/user'
import { setStorage, getStorage } from '@/utils/storage'

export default {
  // 开启命名空间
  namespaced: true,
  state: {
    // token: '',
    // 当页面初始化的时候,从本地存储中读回token,如果读出来是 undefined ,就用 空字符串 代替
    token: getStorage('HM-TOKEN') || ''
  },
  mutations: {
    setToken (state, token) {
      state.token = token
      // 将token持久化存储
      setStorage('HM-TOKEN', token)
    }
  },
  actions: {
    async loginAction (context, payload) {
      // 发请求,换token
      // 提交mutations,更新token
      const res = await userLogin(payload)
      const token = res.data.token
      // 设置token到store中
      context.commit('setToken', token)
    }
  }
}

//store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import userStore from '@/store/modules/user'
Vue.use(Vuex)
export default new Vuex.Store({
  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
    user: userStore
  }
})

2023.7.9Vue-面经PC端_第34张图片

实现登录完整流程,登录以后跳转到首页

这里登录操作是异步的,发请求需要等到拿回token结果以后,才能跳转到页面

2023.7.9Vue-面经PC端_第35张图片

2023.7.9Vue-面经PC端_第36张图片

登录访问拦截=>路由前置

2023.7.9Vue-面经PC端_第37张图片

//src/permission.js
// 专门用来处理路由权限拦截
// 路由权限拦截,本质上就是页面的访问拦截
// 再本质上,就是路由前置守卫
import router from './router'
import store from './store'
// to:去哪里
// from:从哪来
// next:是否放行
// 放行:next()
// 不放行: 引导到其他页面next(路由地址)
router.beforeEach((to, from, next) => {
  // 已登录:全部放行
  // 未登录:只能访问登录
  // 登录页面:是一个白名单页面
  // 需要获取用户的token,来判断用户是否已登录
  const token = store.getters.token
  //   if (token) {
  //     // 说明已登录=>放行
  //     next()
  //   } else {
  //     if (to === '/login') {
  //       // 没有登录=>去登录页面=>放行
  //       next()
  //     } else {
  //       // 没有登录,要去非登录页面==>不能放行==>引导到登录页面
  //       next('/login')
  //     }
  if (token || to.path === '/login') {
    // 没有登录=>去登录页面=>放行
    // 说明已登录=>放行,
    return next()
  }
  // 没有登录,要去非登录页面==>不能放行==>引导到登录页面
  next('/login')
})

// // 白名单,定义成登录
// const whiteList = ['/login']

// // 路由导航守卫
// router.beforeEach((to, from, next) => {
//   // 1. 看有没有 token (vuex),如果有,直接放行
//   const token = store.state.user.token
//   if (token) {
//     next()
//     return
//   }

//   // 2. 看是否在 白名单,如果在,直接放行
//   if (whiteList.includes(to.path)) {
//     next()
//     return
//   }

//   // 3. 其他情况,拦截到登录
//   next('/login')
// })

完成了token的注入,渲染用户信息

//api/user.js
// 封装获取用户信息的接口
export function getUser () {
  return request.get('/auth/currentUser')
}
//utils/request.js
// 如果有token就自动完成注入
  if (store.getters.token) {
    config.headers.Authorization = `Bearer ${store.getters.token}`
  }

2023.7.9Vue-面经PC端_第38张图片

退出功能

2023.7.9Vue-面经PC端_第39张图片

原型劫持

原型污染

结论: 一般来讲,我们很少直接去修改原型,尤其是原生对象的原型
2023.7.9Vue-面经PC端_第40张图片
2023.7.9Vue-面经PC端_第41张图片

你可能感兴趣的:(elementui,vue)