Skip to content

面经 H5 端 - Vant(下)

Vue-Router导航守卫

译者注 “导航”表示路由正在发生改变。

js
<div id="app">
    <div>
        <router-link to="/">首页</router-link>
        <router-link to="/goods">商品</router-link>
        <!-- 一级路由出口 -->
        <router-view></router-view>
    </div>
</div>

<script src="./vue-2.7.min.js"></script>
<script src="./vue-router.js"></script>

定义组件:如果使用工程化导入VueRouter,需要使用Vue.use, 直接js导入不需要

js
// 1. 定义组件
const Home = {
    template:'<div>这是首页</div>'
}
const Goods = {
    template:`
    <div>
        <h2>我是商品组件</h2>
        <router-link to="/goods/iphone/1">iPhone111</router-link>
        <router-link to="/goods/iphone/2">iPhone222</router-link>
        <router-link to="/goods/mac">Mac</router-link>
        <router-view></router-view>
    </div>
    `
}

二级路由中的组件

js
const Iphone = {
    data(){
        return {
            msg:'123'
        }
    },
    created() {
        console.log('created')
    },
    template:'<div>我是iphone</div>',
}

const Mac = {
    template:'<div>我是Mac</div>'
}

创建路由实例, 挂载到Vue根实例上

js
const router = new VueRouter({
    routes:[
        {path:'/', component:Home},
        {
            path:'/goods', 
            component:Goods,
            children:[
                {path:'iphone/:id', component:Iphone},
                {path:'mac', component:Mac},
            ],
        },
    ]
})
const vm = new Vue({
    router,
}).$mount('#app')

守卫分类

全局守卫

js
// 1. 全局前置守卫 router.beforeEach
// 2. 全局解析守卫 router.beforeResolve
// 3. 全局后置守卫 router.afterEach   => 没有next

路由独享守卫

js
// router.beforeEnter

组件内守卫 带有route

js
// 1. beforeRouteEnter
// 2. beforeRouteUpdate 
// 3. beforeRouteLeave

导航解析流程(了解)

js
// 1. beforeRouterLeave
// 2. beforeEach 
// 3. beforeRouteUpdate 
// 4. beforeEnter
// 5. beforeRouteEnter
// 6. beforeResolve 
// 7. afterEach 

// 8. DOM更新
// 9. beforeRouteEnter => next回调

页面访问拦截

这个 面经移动端 项目,只对 登录用户 开放,如果未登录,一律拦截到登录

  1. 如果访问的是 首页, 无token, 拦走

  2. 如果访问的是 列表页,无token, 拦走

  3. 如果访问的是 详情页,无token, 拦走

    ....

分析:哪些页面,是不需要登录,就可以访问的! => 注册登录 (白名单 - 游客可以随意访问的)

核心逻辑:

js
1. 判断用户有没有token, 有token, 直接放行  (有身份的人,想去哪就去哪~
2. 没有token(游客),如果是白名单中的页面,直接放行
3. 否则,无token(游客),且在访问需要权限访问的页面,直接拦截到登录

全局前置守卫

js
const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
    console.log(to, from)
    next()
})
  1. 所有的路由一旦被匹配到,在真正渲染解析之前,都会先经过全局前置守卫

  2. 只有全局前置守卫放行,才能看到真正的页面

js
// 导航守卫存在的意义:可以加拦截判断
// 1. 判断用户有没有token,如果有,直接放行
// 2. 没有token,白名单的页面(登录,注册),这些可以访问
// 3. 没有token,要去的还是首页等需要授权的页面,拦截到登录
jsx
// 全局前置守卫:
// 1. 所有的路由一旦被匹配到,在真正渲染解析之前,都会先经过全局前置守卫
// 2. 只有全局前置守卫放行,才能看到真正的页面

// 任何路由,被解析访问前,都会先执行这个回调
// 1. from 你从哪里来, 从哪来的路由信息对象
// 2. to   你往哪里去, 到哪去的路由信息对象
// 3. next() 是否放行,如果next()调用,就是放行 => 放你去想去的页面
//    next(路径) 拦截到某个路径页面

const whiteList = ['/login', '/register'] // 白名单列表,记录无需权限访问的所有页面

router.beforeEach((to, from, next) => {
  const token = getToken()
  // 如果有token,直接放行
  if (token) {
    next()
  } else {
    // 没有token的人, 看看你要去哪
    // (1) 访问的是无需授权的页面(白名单),也是放行
    //     就是判断,访问的地址,是否在白名单数组中存在 includes
    if (whiteList.includes(to.path)) {
      next()
    } else {
      // (2) 否则拦截到登录
      next('/login')
    }
  }
})

项目定位

核心定位:

  1. 熟悉 项目架子: api模块 (封装请求函数) request请求封装(axios) storage封装 路由练习
  2. 熟悉 组件库 使用: 复制文档内容,粘贴到合适位置,看文档改改

面经列表

静态结构

image-20220614074054380

utils/ vant-ui.js

注册组件:

  • van-cell
  • van-list
jsx
import Vue from 'vue'
import {
  Cell,
  List
} from 'vant'
Vue.use(Cell)
Vue.use(List)

静态结构 article.vue

vue
<template>
  <div class="article-page">
    <nav class="my-nav van-hairline--bottom">
      <a
        href="javascript:;"
        >推荐</a
      >
      <a
        href="javascript:;"
        >最新</a
      >
      <div class="logo"><img src="@/assets/logo.png" alt=""></div>
    </nav>

    <van-cell class="article-item" >
      <template #title>
        <div class="head">
          <img src="http://teachoss.itheima.net/heimaQuestionMiniapp/%E5%AE%98%E6%96%B9%E9%BB%98%E8%AE%A4%E5%A4%B4%E5%83%8F%402x.png" alt="" />
          <div class="con">
            <p class="title van-ellipsis">宇宙头条校招前端面经</p>
            <p class="other">不风流怎样倜傥 | 2022-01-20 00-00-00</p>
          </div>
        </div>
      </template>
      <template #label>
        <div class="body van-multi-ellipsis--l2">
          笔者读大三, 前端小白一枚, 正在准备春招, 人生第一次面试, 投了头条前端, 总共经历了四轮技术面试和一轮hr面, 不多说, 直接上题&nbsp;一面
        </div>
        <div class="foot">点赞 46 | 浏览 332</div>
      </template>
    </van-cell>
  </div>
</template>

<script>
export default {
  name: 'article-page',
  data () {
    return {

    }
  },
  methods: {

  }
}
</script>

<style lang="less" scoped>
.article-page {
  margin-bottom: 50px;
  margin-top: 44px;
  .my-nav {
    height: 44px;
    position: fixed;
    left: 0;
    top: 0;
    width: 100%;
    z-index: 999;
    background: #fff;
    display: flex;
    align-items: center;
    > a {
      color: #999;
      font-size: 14px;
      line-height: 44px;
      margin-left: 20px;
      position: relative;
      transition: all 0.3s;
      &::after {
        content: '';
        position: absolute;
        left: 50%;
        transform: translateX(-50%);
        bottom: 0;
        width: 0;
        height: 2px;
        background: #222;
        transition: all 0.3s;
      }
      &.active {
        color: #222;
        &::after {
          width: 14px;
        }
      }
    }
    .logo {
      flex: 1;
      display: flex;
      justify-content: flex-end;
      > img {
        width: 64px;
        height: 28px;
        display: block;
        margin-right: 10px;
      }
    }
  }
}
.article-item {
  .head {
    display: flex;
    img {
      width: 40px;
      height: 40px;
      border-radius: 50%;
      overflow: hidden;
    }
    .con {
      flex: 1;
      overflow: hidden;
      padding-left: 10px;
      p {
        margin: 0;
        line-height: 1.5;
        &.title {
          width: 280px;
        }
        &.other {
          font-size: 10px;
          color: #999;
        }
      }
    }
  }
  .body {
    font-size: 14px;
    color: #666;
    line-height: 1.6;
    margin-top: 10px;
  }
  .foot {
    font-size: 12px;
    color: #999;
    margin-top: 10px;
  }
}
</style>

封装 ArticleItem 组件

新建 components/article-item.vue 组件

jsx
<template>
      <!-- 文字区域 -->
      <van-cell class="article-item" >
      <template #title>
        <div class="head">
          <img src="http://teachoss.itheima.net/heimaQuestionMiniapp/%E5%AE%98%E6%96%B9%E9%BB%98%E8%AE%A4%E5%A4%B4%E5%83%8F%402x.png" alt="" />
          <div class="con">
            <p class="title van-ellipsis">宇宙头条校招前端面经</p>
            <p class="other">不风流怎样倜傥 | 2022-01-20 00-00-00</p>
          </div>
        </div>
      </template>
      <template #label>
        <div class="body van-multi-ellipsis--l2">
          笔者读大三, 前端小白一枚, 正在准备春招, 人生第一次面试, 投了头条前端, 总共经历了四轮技术面试和一轮hr面, 不多说, 直接上题&nbsp;一面
        </div>
        <div class="foot">点赞 46 | 浏览 332</div>
      </template>
    </van-cell>
</template>

<script>
export default {

}
</script>

<style lang="less" scoped>
.article-item {
  .head {
    display: flex;
    img {
      width: 40px;
      height: 40px;
      border-radius: 50%;
      overflow: hidden;
    }
    .con {
      flex: 1;
      overflow: hidden;
      padding-left: 10px;
      p {
        margin: 0;
        line-height: 1.5;
        &.title {
          width: 280px;
        }
        &.other {
          font-size: 10px;
          color: #999;
        }
      }
    }
  }
  .body {
    font-size: 14px;
    color: #666;
    line-height: 1.6;
    margin-top: 10px;
  }
  .foot {
    font-size: 12px;
    color: #999;
    margin-top: 10px;
  }
}
</style>

注册成全局组件使用 => main.js

jsx
import ArticleItem from '@/components/article-item.vue'
Vue.component('ArticleItem', ArticleItem)

Article.vue页面中

vue
<template>
  <div class="article-page">
    ...

    <article-item></article-item>
  </div>
</template>

封装 api 接口

接口:https://www.apifox.cn/apidoc/project-934563/api-20384521

image-20230210190458573

新建 api/article.js 提供接口函数

jsx
import request from '@/utils/request'

export const getArticles = (obj) => {
  return request.get('/interview/query', {
    params: {
      current: obj.current,
      sorter: obj.sorter,
      pageSize: 10
    }
  })
}

页面中调用测试

jsx
import { getArticles } from '@/api/article'
export default {
  name: 'article-page',
  data () {
    return {

    }
  },
  async created () {
    const res = await getArticles({
      current: 1,
      sorter: 'weight_desc'
    })
    console.log(res)
  },
  methods: {

  }
}

发现 401 错误, 通过 headers 携带 token

注意:这个token,需要拼上前缀 Bearer token标识前缀

jsx
// 封装接口,获取文章列表
export const getArticles = (obj) => {
  const token = getToken()

  return request.get('/interview/query', {
    params: {
      current: obj.current, // 当前页
      pageSize: 10, // 每页条数
      sorter: obj.sorter // 排序字段 =>  传"weight_desc" 获取 推荐, "不传" 获取 最新
    },
    headers: {
      // 注意 Bearer 和 后面的空格不能删除,为后台的token辨识
      Authorization: `Bearer ${token}`
    }
  })
}

导入导出语法复习

js
  ---------------------------------------
  按需导出 (适合导出多个值)
    export const fn1 = () => { ... }
    export const fn2 = () => { ... }
    export const num = 20
    export const obj = { ... }

  按需导入
    import { fn1, fn2 }  from './xxx'

  ---------------------------------------
  默认导出 (适合导出一个值)
    export default

  默认导入
    import result from './xxx'
  ---------------------------------------

  结论:默认导出默认导入,按需导出按需导入
  技巧:如果一个模块,只导出一个值,大概率是默认导出
        如果一个模块,导出多个值,一定是按需导出,导入要 import {} from xxx


  直接导入一个文件,说明:你不需要拿到该文件的任何内容,只需要执行一次

请求拦截器-携带 token

utils/request.js

每次自己携带token太麻烦,通过请求拦截器统一携带token更方便

jsx
import { getToken } from './storage'

// 添加请求拦截器
request.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  const token = getToken()
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error)
})

响应拦截器-处理token过期

image-20230210215148272

utils/request.js

jsx
import router from '@/router' // 导入router
// 添加响应拦截器
request.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  return response.data
}, function (error) {
  if (error.response) {
    // 有错误响应, 提示错误提示
    if (error.response.status === 401) {
      delToken()
      router.push('/login')
    } else {
      Toast(error.response.data.message)
    }
  }
  // 对响应错误做点什么
  return Promise.reject(error)
})

动态渲染列表

article.vue

存储数据

jsx
data () {
  return {
    list: [],
    current: 1,
    sorter: 'weight_desc'
  }
},
async created () {
  const { data } = await getArticles({
    current: this.current,
    sorter: this.sorter
  })
  this.list = data.rows
},

v-for循环展示

jsx
<template>
  <div class="article-page">
    ...

    <article-item v-for="(item,i) in list" :key="i" :item="item"></article-item>
  </div>
</template>

子组件接收渲染

jsx
<template>
  <van-cell class="article-item" @click="$router.push(`/detail/${item.id}`)">
    <template #title>
      <div class="head">
        <img :src="item.avatar" alt="" />
        <div class="con">
          <p class="title van-ellipsis">{{ item.stem }}</p>
          <p class="other">{{ item.creator }} | {{ item.createdAt }}</p>
        </div>
      </div>
    </template>
    <template #label>
      <div class="body van-multi-ellipsis--l2">{{ clearHtmlTag(item.content) }}</div>
      <div class="foot">点赞 {{ item.likeCount }} | 浏览 {{ item.views }}</div>
    </template>
  </van-cell>
</template>

<script>
export default {
  name: 'article-item',
  props: {
    item: {
      type: Object,
      default: () => ({})
    }
  },
  methods: {
    clearHtmlTag (str) {
      return str.replace(/<[^>]+>/g, '')
    }
  }
}
</script>

<style lang="less" scoped>
.article-item {
  .head {
    display: flex;
    img {
      width: 40px;
      height: 40px;
      border-radius: 50%;
      overflow: hidden;
    }
    .con {
      flex: 1;
      overflow: hidden;
      padding-left: 10px;
      p {
        margin: 0;
        line-height: 1.5;
        &.title {
          width: 280px;
        }
        &.other {
          font-size: 10px;
          color: #999;
        }
      }
    }
  }
  .body {
    font-size: 14px;
    color: #666;
    line-height: 1.6;
    margin-top: 10px;
  }
  .foot {
    font-size: 12px;
    color: #999;
    margin-top: 10px;
  }
}
</style>

分页加载更多

https://vant-contrib.gitee.io/vant/v2/#/zh-CN/list

image-20220614081410184

js
:finished =>  loading / finished (True/false

loading:数据是否在加载中
    true在加载中, 此时不会重复触发加载
    false不在加载中,又可以加载更多数据了

finished:是否加载已完成
    true数据已经加载完成,此时不会再次触发加载
    false数据还没加载完成,此时表示还有数据可以加载

1. List 组件通过 loading 和 finished 两个变量控制加载状态
2. 当组件滚动到底部时 或 进入页面数据没有撑满整个屏幕,会触发 load 事件,并将 loading 设置成 true
此时可以发起异步操作并更新数据,数据更新完毕后,将 loading 设置成 false 即可。
3. 若数据已全部加载完毕,则直接将 finished 设置成 true 即可。
jsx
<van-list
  v-model="loading"
  :finished="finished"
  finished-text="没有更多了"
  @load="onLoad"
>
  <article-item v-for="(item,i) in list" :key="i" :item="item"></article-item>
</van-list>

data () {
  return {
    list: [],
    current: 1,
    sorter: 'weight_desc',
    loading: false,
    finished: false
  }
},
    
methods: {
    // 触发时机(onLoad会被触发多次!):
    // 1. 一进入页面,如果数据没有撑满整个屏幕  list: [] 需要加载更多,触发load事件
    // 2. 当用户往下滑动,触底时,需要加载更多,触发load事件
  async onLoad () {
    const { data } = await getArticles({
      current: this.current,
      sorter: this.sorter
    })
    this.list = data.rows
  }
}

加载完成,重置 loading, 累加数据,处理 finished

jsx
async onLoad () {
  const { data } = await getArticles({
    current: this.current,
    sorter: this.sorter
  })
  this.list.push(...data.rows)
  this.loading = false
  this.current++

  if (this.current > data.pageTotal) {
    this.finished = true
  }
}

修改筛选条件

image-20230210202854079

article.vue

jsx
<a
  @click="changeSorter('weight_desc')"
  :class="{ active: sorter === 'weight_desc' }"
  href="javascript:;"
  >推荐</a
>
<a
  @click="changeSorter(null)"
  :class="{ active: sorter === null }"
  href="javascript:;"
  >最新</a
>

提供methods

jsx
changeSorter (value) {
  this.sorter = value

  // 重置所有条件
  this.current = 1 // 排序条件变化,重新从第一页开始加载
  this.list = []
  this.finished = false // finished重置,重新有数据可以加载了
  // this.loading = false

  // 手动加载更多
  // 手动调用了加载更多,也需要手动将loading改成true,表示正在加载中(避免重复触发)
  this.loading = true
  this.onLoad()
}

快速实现

我的收藏

接口文档:https://www.apifox.cn/apidoc/project-934563/api-20384527

出于项目的完整性,这里会快速实现收藏,喜欢,详情~

提供api方法

  • page: 表示当前页
  • optType:2 表示获取我的收藏数据 、 (1 点赞)

api/article.js

jsx
// 获取我的收藏
export const getArticlesCollect = (obj) => {
  return request.get('/interview/opt/list', {
    params: {
      page: obj.page, // 当前页
      pageSize: 10, // 可选
      optType: 2 // 表示收藏
    }
  })
}

collect.vue准备结构

jsx
<template>
  <div class="collect-page">
    <van-nav-bar fixed title="我的收藏" />
    <van-list
      v-model="loading"
      :finished="finished"
      finished-text="没有更多了"
      @load="onLoad"
    >
      <article-item v-for="(item, i) in list" :key="i" :item="item" />
    </van-list>
  </div>
</template>

<script>
import { getArticlesCollect } from '@/api/article'
export default {
  name: 'collect-page',
  data () {
    return {
      list: [],
      loading: false,
      finished: false,
      page: 1
    }
  },
  methods: {
    async onLoad () {
      // 异步更新数据
      const { data } = await getArticlesCollect({ page: this.page })
      this.list.push(...data.rows)
      this.loading = false
      if (this.page === data.pageTotal || !data.rows.length) {
        this.finished = true
      } else {
        this.page++
      }
    }
  }
}
</script>

<style lang="less" scoped>
.collect-page {
  margin-bottom: 50px;
  margin-top: 44px;
}
</style>

我的喜欢

准备api函数

  • page: 表示当前页
  • optType:1 表示获取我的喜欢数据

api/article.js

jsx
// 获取我的喜欢
export const getArticlesLike = (obj) => {
  return request.get('/interview/opt/list', {
    params: {
      page: obj.page, // 当前页
      pageSize: 10, // 可选
      optType: 1 // 表示喜欢
    }
  })
}

like.vue请求渲染

jsx
<template>
  <div class="like-page">
    <van-nav-bar fixed title="我的点赞" />
    <van-list
      v-model="loading"
      :finished="finished"
      finished-text="没有更多了"
      @load="onLoad"
    >
      <article-item v-for="(item,i) in list" :key="i" :item="item" />
    </van-list>
  </div>
</template>

<script>
import { getArticlesLike } from '@/api/article'
export default {
  name: 'like-page',
  data () {
    return {
      list: [],
      loading: false,
      finished: false,
      page: 1
    }
  },
  methods: {
    async onLoad () {
      // 异步更新数据
      // setTimeout 仅做示例,真实场景中一般为 ajax 请求
      const { data } = await getArticlesLike({ page: this.page })
      this.list.push(...data.rows)
      this.loading = false
      if (this.page === data.pageTotal || !data.rows.length) {
        this.finished = true
      } else {
        this.page++
      }
    }
  }
}
</script>

<style lang="less" scoped>
.like-page {
  margin-bottom: 50px;
  margin-top: 44px;
}
</style>

面经详情

核心知识点:跳转路由传参

准备动态路由

image-20220702082707782

页面中获取参数

jsx
this.$route.params.id

点击跳转 article.vue => article-item组件中添加!有了

jsx
<template>
  <!-- 文章区域 -->
  <van-cell class="article-item" @click="$router.push(`/detail/${item.id}`)">
    <template #title>
      ...
    </template>
    <template #label>
      ...
    </template>
  </van-cell>
</template>

其他准备代码:

api/article.js

jsx
export const getArticleDetail = (id) => {
  return request.get('interview/show', {
    params: {
      id
    }
  })
}

export const updateLike = (id) => {
  return request.post('interview/opt', {
    id,
    optType: 1 // 喜欢
  })
}

export const updateCollect = (id) => {
  return request.post('interview/opt', {
    id,
    optType: 2 // 收藏
  })
}

detail.vue

jsx
<template>
  <div class="detail-page">
    <van-nav-bar
      left-text="返回"
      @click-left="$router.back()"
      fixed
      title="面经详细"
    />
    <header class="header">
      <h1>{{ article.stem }}</h1>
      <p>
        {{ article.createdAt }} | {{ article.views }} 浏览量 |
        {{ article.likeCount }} 点赞数
      </p>
      <p>
        <img :src="article.avatar" alt="" />
        <span>{{ article.creator }}</span>
      </p>
    </header>
    <main class="body" v-html="article.content"></main>
    <div class="opt">
      <van-icon @click="toggleLike" :class="{active:article.likeFlag}" name="like-o"/>
      <van-icon @click="toggleCollect" :class="{active:article.collectFlag}" name="star-o"/>
    </div>
  </div>
</template>

<script>
import { getArticleDetail, updateCollect, updateLike } from '@/api/article';

export default {
  name: 'detail-page',
  data() {
    return {
      article: {}
    };
  },
  async created() {
    this.article = {}
    const { data } = await getArticleDetail(this.$route.params.id)
    this.article = data;
  },
  methods: {
    async toggleLike () {
      await updateLike(this.article.id)
      this.article.likeFlag = !this.article.likeFlag
      if ( this.article.likeFlag ) {
        this.article.likeCount ++
        this.$toast.success('点赞成功')
      } else {
        this.article.likeCount --
        this.$toast.success('取消点赞')
      }
    },
    async toggleCollect () {
      await updateCollect(this.article.id)
      this.article.collectFlag = !this.article.collectFlag
      if ( this.article.collectFlag ) {
        this.$toast.success('收藏成功')
      } else {
        this.$toast.success('取消收藏')
      }
    }
  }
};
</script>

<style lang="less" scoped>
.detail-page {
  margin-top: 44px;
  overflow: hidden;
  padding: 0 15px;
  .header {
    h1 {
      font-size: 24px;
    }
    p {
      color: #999;
      font-size: 12px;
      display: flex;
      align-items: center;
    }
    img {
      width: 40px;
      height: 40px;
      border-radius: 50%;
      overflow: hidden;
    }
  }
  .opt {
    position: fixed;
    bottom: 100px;
    right: 0;
    > .van-icon {
      margin-right: 20px;
      background: #fff;
      width: 40px;
      height: 40px;
      line-height: 40px;
      text-align: center;
      border-radius: 50%;
      box-shadow: 2px 2px 10px #ccc;
      font-size: 18px;
      &.active {
        background: #FEC635;
        color: #fff;
      }
    }
  }
}
</style>

我的(个人中心)

准备代码:

1 注册组件

vant-ui.js

jsx
import {
  Grid,
  GridItem,
  CellGroup
} from 'vant'

Vue.use(Grid).use(GridItem).use(CellGroup)

2 准备api

api/user.js

jsx
// 获取用户信息
export const getUserInfo = () => {
  return request('/user/currentUser')
}

3 页面调用渲染

user.vue

jsx
<template>
  <div class="user-page">
    <div class="user">
      <img :src="avatar" alt="" />
      <h3>{{ username }}</h3>
    </div>
    <van-grid clickable :column-num="3" :border="false">
      <van-grid-item icon="clock-o" text="历史记录" to="/" />
      <van-grid-item icon="bookmark-o" text="我的收藏" to="/collect" />
      <van-grid-item icon="thumb-circle-o" text="我的点赞" to="/like" />
    </van-grid>

    <van-cell-group class="mt20">
      <van-cell title="推荐分享" is-link />
      <van-cell title="意见反馈" is-link />
      <van-cell title="关于我们" is-link />
      <van-cell @click="logout" title="退出登录" is-link />
    </van-cell-group>
  </div>
</template>

<script>
import { getUserInfo } from '@/api/user'
import { delToken } from '@/utils/storage'
export default {
  name: 'user-page',
  data () {
    return {
      username: '',
      avatar: ''
    }
  },
  async created () {
    const { data } = await getUserInfo()
    this.username = data.username
    this.avatar = data.avatar
  },
  methods: {
    logout () {
      delToken()
      this.$router.push('/login')
    }
  }
}
</script>

<style lang="less" scoped>
.user-page {
  padding: 0 10px;
  background: #f5f5f5;
  height: 100vh;
  .mt20 {
    margin-top: 20px;
  }
  .user {
    display: flex;
    padding: 20px 0;
    align-items: center;
    img {
      width: 80px;
      height: 80px;
      border-radius: 50%;
      overflow: hidden;
    }
    h3 {
      margin: 0;
      padding-left: 20px;
      font-size: 18px;
    }
  }
}
</style>

打包发布

vue脚手架只是开发过程中,协助开发的工具,当真正开发完了 => 脚手架不参与上线

参与上线的是 => 打包后的源代码

打包:

  • 将多个文件压缩合并成一个文件
  • 语法降级
  • less sass ts 语法解析, 解析成css
  • ....

打包后,可以生成,浏览器能够直接运行的网页 => 就是需要上线的源码!

打包命令

vue脚手架工具已经提供了打包命令,直接使用即可。

bash
yarn build

在项目的根目录会自动创建一个文件夹dist,dist中的文件就是打包后的文件,只需要放到服务器中即可。

配置publicPath

Vue CLI 会假设你的应用是被部署在一个域名的根路径上,例如 https://www.my-app.com/。 如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如, 如果你的应用被部署在 https://www.my-app.com/my-app/,则设置 publicPath 为 /my-app/

js
module.exports = {
  // https://cli.vuejs.org/zh/config/#publicpath
  publicPath: '/'
}
js
module.exports = {
  // 设置获取.js,.css文件时,是以相对地址为基准的。
  // https://cli.vuejs.org/zh/config/#publicpath
  publicPath: './'
  //  => file:///C:/Users/xxxx/Desktop/vue-2-9/hm-vant-h5/dist/img/logo.dbd40807.png
}

路由懒加载 & 异步组件

image-20230210214008437

路由懒加载 & 异步组件

不会一上来就将所有的组件都加载,而是访问到对应的路由了,才加载解析这个路由对应的所有组件

官网链接:路由懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

js
const Detail = () => import('@/views/detail')
const Register = () => import('@/views/register')
const Login = () => import('@/views/login')
const Article = () => import('@/views/article')
const Collect = () => import('@/views/collect')
const Like = () => import('@/views/like')
const User = () => import('@/views/user')

# 如果多了二级目录
const Detail = () => import('@/views/detail')
const Register = () => import('@/views/register')
const Login = () => import('@/views/login')
const Article = () => import('@/views/second/article')
const Collect = () => import('@/views/second/collect')
const Like = () => import('@/views/second/like')
const User = () => import('@/views/second/user')

PS: 如果想要手机上看到效果,可以将打包后的代码,上传到 gitee,利用 git pages 进行展示