前端【VUE】13-移动端组件库【Vant】【Vant基本使用】【vw、rem适配插件】【定制配置】【vant综合练习项目】【配置publicPath、路由懒加载】

一、基本使用

1、介绍

  Vant 是一个轻量、可靠的移动端组件库,于 2017 年开源。

  目前 Vant 官方提供了 Vue 2 版本Vue 3 版本微信小程序版本,并由社区团队维护 React 版本支付宝小程序版本

2、安装

1 # Vue 3 项目,安装最新版 Vant:
2 npm i vant -S
3 
4 # Vue 2 项目,安装 Vant 25 npm i vant@latest-v2 -S

3、项目引入

  创建vue项目,然后在main.js中引入vant组件

  方式一. 自动按需引入组件 (推荐)

    babel-plugin-import 是一款 babel 插件,它会在编译过程中将 import 的写法自动转换为按需引入的方式。

1 # 安装插件
2 npm i babel-plugin-import -D

  配置

 1 // 在.babelrc 中添加配置
 2 // 注意:webpack 1 无需设置 libraryDirectory
 3 {
 4   "plugins": [
 5     ["import", {
 6       "libraryName": "vant",
 7       "libraryDirectory": "es",
 8       "style": true
 9     }]
10   ]
11 }
12 
13 // 对于使用 babel7 的用户,可以在 babel.config.js 中配置
14 module.exports = {
15   plugins: [
16     ['import', {
17       libraryName: 'vant',
18       libraryDirectory: 'es',
19       style: true
20     }, 'vant']
21   ]
22 };

方式二. 手动按需引入组件

1 import Button from 'vant/lib/button';
2 import 'vant/lib/button/style';

方式三. 导入所有组件

  Vant 支持一次性导入所有组件,引入所有组件会增加代码包体积,因此不推荐这种做法

1 import Vue from 'vue';
2 import Vant from 'vant';
3 import 'vant/lib/index.css';
4 
5 Vue.use(Vant);

4、组件注册

  全局注册

  全局注册后,你可以在 app 下的任意子组件中使用注册的 Vant 组件。

 1 import Vue from 'vue';
 2 import { Button } from 'vant';
 3 
 4 // 方式一. 通过 Vue.use 注册
 5 // 注册完成后,在模板中通过 <van-button> 或 <VanButton> 标签来使用按钮组件
 6 Vue.use(Button);
 7 
 8 // 方式二. 通过 Vue.component 注册
 9 // 注册完成后,在模板中通过 <van-button> 标签来使用按钮组件
10 Vue.component(Button.name, Button);

  局部注册

  局部注册后,你可以在当前组件中使用注册的 Vant 组件。

1 import { Button } from 'vant';
2 
3 export default {
4   components: {
5     [Button.name]: Button,
6   },
7 };

4、使用

main.js内容

 1 import Vue from 'vue'
 2 import App from './App.vue'
 3 
 4 // 导入组件
 5 import Vant from 'vant'
 6 // 导入组件样式库
 7 import 'vant/lib/index.css'
 8 // 全局注册vant
 9 Vue.use(Vant)
10 
11 Vue.config.productionTip = false
12 
13 new Vue({
14   render: h => h(App)
15 }).$mount('#app')

在App.vue中使用相关组件

 1 <template>
 2   <div class="app">
 3     <van-button type="primary">主要按钮</van-button>
 4     <van-button type="info">信息按钮</van-button>
 5     <van-button type="default">默认按钮</van-button>
 6     <van-button type="warning">警告按钮</van-button>
 7     <van-button type="danger">危险按钮</van-button>
 8   </div>
 9 </template>
10 <script>
11 export default {
12 }
13 </script>
14 <style>
15 </style>

启动服务,查看样式加载情况,npm run serve

  

 二、适配的配置

vw适配

  官方说明:https://vant-contrib.gitee.io/vant/v2/#/zh-CN/advanced-usage

1、下载依赖

1 npm i postcss-px-to-viewport@1.1.1 -D
2 安装报错,则:
3 npm i postcss-px-to-viewport@1.1.1 -D --legacy-peer-deps

2、新增配置

  • 项目根目录, 新建postcss的配置文件postcss.config.js
 1 // postcss.config.js
 2 module.exports = {
 3   plugins: {
 4     'postcss-px-to-viewport': {
 5       // 设计稿如果是2倍图,宽是750,则 750/2 = 375,下面就写375
 6       // 设计稿如果是3倍图,宽是 1080,则 1080/3 = 360,下面就写360
 7       viewportWidth: 375
 8     }
 9   }
10 }

3、修改了配置需要重启服务

REM适配

1、下载依赖包

1 npm i postcss-pxtorem -D
2 npm i lib-flexible
3 报错解决,安装时,加 --legacy-peer-deps 选项

2、main.js中导入所需的js

1 import 'lib-flexible'

3、新增配置

  项目根目录,创建 postcss.config.js,把下面的代码加入进去即可

1 module.exports = {
2   plugins: {
3     'postcss-pxtorem': {
4       rootValue: 37.5,
5       propList: ['*'],
6     },
7   },
8 };

4、修改配置重启服务

定制主题

第一步:

  方式1:按需引入样式(推荐)

    在 babel.config.js 中配置按需引入样式源文件,注意 babel6 不支持按需引入样式,请手动引入样式。

 1 module.exports = {
 2   plugins: [
 3     [
 4       'import',
 5       {
 6         libraryName: 'vant',
 7         libraryDirectory: 'es',
 8         // 指定样式路径
 9         style: (name) => `${name}/style/less`,
10       },
11       'vant',
12     ],
13   ],
14 };

  方式2:手动引入

1 // 引入全部样式
2 import 'vant/lib/index.less';
3 
4 // 引入单个组件样式
5 import 'vant/lib/button/style/less';

第二步:

  使用 Less 提供的 modifyVars 即可对变量进行修改,下面是参考的 webpack 配置。

 1 // webpack.config.js
 2 module.exports = {
 3   rules: [
 4     {
 5       test: /\.less$/,
 6       use: [
 7         // ...其他 loader 配置
 8         {
 9           loader: 'less-loader',
10           options: {
11             // 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。
12             lessOptions: {
13               modifyVars: {
14                 // 直接覆盖变量
15                 'text-color': '#111',
16                 'border-color': '#eee',
17                 // 或者可以通过 less 文件覆盖(文件路径为绝对路径)
18                 // hack: `true; @import "your-less-file-path.less";`,
19               },
20             },
21           },
22         },
23       ],
24     },
25   ],
26 };

  如果 vue-cli 搭建的项目,可以在 vue.config.js 中进行配置。

 1 // vue.config.js
 2 module.exports = {
 3   css: {
 4     loaderOptions: {
 5       less: {
 6         // 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。
 7         lessOptions: {
 8           modifyVars: {
 9             // 直接覆盖变量
10             'text-color': '#111',
11             'border-color': '#eee',    
12             // 或者 如果要修改的样式比较多,可以通过 less 文件覆盖(文件路径为绝对路径)
13             // hack: `true; @import "your-less-file-path.less";`,
aaaa: 'red' // 项目中组件的颜色color : @aaa, 此时就会根据这里的变量进行颜色的覆盖
14 }, 15 }, 16 }, 17 }, 18 }, 19 };

 

三、面经项目

基础依赖下载

1 // 1、脚手架
2 npm i  @vue/cli  -g
3 
4 
5 // 2、下载vant
6 npm i vant@latest-v2 -S

1、目录结构

  

2、src/api下的调用接口的js

article.js

 1 import request from '@/utils/request'
 2 
 3 // 获取文章列表接口封装完成
 4 export const getArticles = (obj) => {
 5   return request.get('/interview/query', {
 6     // get请求参数
 7     params: {
 8       current: obj.current || 1, // 当前页 → 做分页
 9       pageSize: obj.pageSize || 10, // 每页条数 → 做分页
10       sorter: obj.sorter // 排序字段 → 推荐(weight_desc) & 最新(不传参数)
11     }
12     // // headers请求头,配置token
13     // headers: {
14     //   Authorization: `Bearer ${token}`
15     // }
16   })
17 }
18 
19 // 获取文章详情数据
20 export const getArticleDetail = (id) => {
21   return request.get('interview/show', {
22     params: {
23       id
24     }
25   })
26 }
27 
28 // 点赞接口
29 export const updateLike = (id) => {
30   return request.post('interview/opt', {
31     id,
32     optType: 1 // 喜欢
33   })
34 }
35 
36 // 收藏接口
37 export const updateCollect = (id) => {
38   return request.post('interview/opt', {
39     id,
40     optType: 2 // 收藏
41   })
42 }
43 
44 // 获取我的收藏
45 export const getArticlesCollect = (obj) => {
46   return request.get('/interview/opt/list', {
47     params: {
48       page: obj.page, // 当前页
49       pageSize: 5, // 可选
50       optType: 2 // 表示收藏
51     }
52   })
53 }
54 
55 // 获取我的喜欢
56 export const getArticlesLike = (obj) => {
57   return request.get('/interview/opt/list', {
58     params: {
59       page: obj.page, // 当前页
60       pageSize: 5, // 可选
61       optType: 1 // 表示喜欢
62     }
63   })
64 }

user.js

 1 // 本文件用于存放所有和用户相关的请求函数
 2 import request from '@/utils/request'
 3 
 4 // 1. 注册用户
 5 export const register = (data) => {
 6   // 进行注册请求
 7   // 注意:这里必须 return,将请求的promise对象返回,将来才能await拿结果
 8   return request.post('/user/register', data)
 9 }
10 
11 // 2. 账户登录
12 export const login = (data) => {
13   // 进行登录请求
14   return request.post('/user/login', data)
15 }
16 
17 // 3. 获取用户信息
18 export const getUserInfo = () => {
19   return request('/user/currentUser')
20 }

3、公共组件components目录下

 1 <template>
 2   <!-- 单元格文章 -->
 3   <van-cell class="article-item" @click="$router.push(`/article/${item.id}`)" >
 4     <template #title>
 5       <div class="head">
 6         <img :src="item.avatar" alt="" />
 7         <div class="con">
 8           <p class="title van-ellipsis">{{ item.stem }}</p>
 9           <p class="other">{{ item.creator }} | {{ item.createdAt }}</p>
10         </div>
11       </div>
12     </template>
13     <template #label>
14       <div class="body van-multi-ellipsis--l2">
15         {{ clearHTMLTag(item.content) }}
16       </div>
17       <div class="foot">点赞 {{ item.likeCount }} | 浏览 {{ item.views }}</div>
18     </template>
19   </van-cell>
20 </template>
21 
22 <script>
23 export default {
24   name: 'ArticleItem',
25   props: {
26     item: {
27       type: Object,
28       default: () => {}
29     }
30   },
31   methods: {
32     // 清除 html 标记符号,将标签符号替换为 ""
33     // /<[^>]+>/g 匹配所有标签符号
34     clearHTMLTag (str) {
35       return str.replace(/<[^>]+>/g, '')
36     }
37   }
38 }
39 </script>
40 
41 <style lang="less" scoped>
42 .article-item {
43   .head {
44     display: flex;
45     img {
46       width: 40px;
47       height: 40px;
48       border-radius: 50%;
49       overflow: hidden;
50     }
51     .con {
52       flex: 1;
53       overflow: hidden;
54       padding-left: 10px;
55       p {
56         margin: 0;
57         line-height: 1.5;
58         &.title {
59           width: 280px;
60         }
61         &.other {
62           font-size: 10px;
63           color: #999;
64         }
65       }
66     }
67   }
68   .body {
69     font-size: 14px;
70     color: #666;
71     line-height: 1.6;
72     margin-top: 10px;
73   }
74   .foot {
75     font-size: 12px;
76     color: #999;
77     margin-top: 10px;
78   }
79 }
80 </style>

4、路由router目录下

index.js

 1 import Vue from 'vue'
 2 import VueRouter from 'vue-router'
 3 import { getToken } from '@/utils/storage'
 4 
 5 // 一级路由页面的导入
 6 const Login = () => import('@/views/Login')
 7 const Register = () => import('@/views/Register')
 8 const Layout = () => import('@/views/Layout')
 9 const Detail = () => import('@/views/Detail')
10 
11 // 二级路由页面的导入
12 const Article = () => import('@/views/Article')
13 const Collect = () => import('@/views/Collect')
14 const Like = () => import('@/views/Like')
15 const User = () => import('@/views/User')
16 
17 Vue.use(VueRouter)
18 
19 const router = new VueRouter({
20   routes: [
21     { path: '/login', component: Login },
22     { path: '/register', component: Register },
23     { path: '/article/:id', component: Detail },
24     {
25       path: '/',
26       component: Layout,
27       redirect: '/article',
28       children: [
29         { path: '/article', component: Article },
30         { path: '/like', component: Like },
31         { path: '/collect', component: Collect },
32         { path: '/user', component: User }
33       ]
34     }
35   ]
36 })
37 
38 // 白名单(就是一个数组,数组收录所有无需登录即可访问的页面)
39 const whiteList = ['/login', '/register']
40 
41 // 路由前置守卫
42 // 1. to 往哪去
43 // 2. from 从哪来
44 // 3. next 是否放行 next() 放行 next(路径) 拦截到某个页面
45 router.beforeEach((to, from, next) => {
46   const token = getToken()
47   if (token) {
48     // 如果有token,直接放行
49     next()
50   } else {
51     // 没有token的人,看看要去哪
52     // 判断访问的路径 to.path, 是否在白名单中
53     if (whiteList.includes(to.path)) {
54       next()
55     } else {
56       next('/login')
57     }
58   }
59 })
60 
61 export default router

5、工具utils目录下

request.js

 1 import axios from 'axios'
 2 import { Toast } from 'vant'
 3 import { getToken, delToken } from './storage'
 4 import router from '@/router/index'
 5 
 6 // 创建自定义的实例
 7 const instance = axios.create({
 8   baseURL: 'http://interview-api-t.itheima.net/h5/',
 9   timeout: 5000
10 })
11 
12 // 自定义配置 (请求和响应拦截器)
13 // 添加请求拦截器
14 instance.interceptors.request.use(function (config) {
15   // 在发送请求之前做些什么
16   const token = getToken()
17   // 统一携带token
18   if (token) {
19     config.headers.Authorization = `Bearer ${token}`
20   }
21   return config
22 }, function (error) {
23   // 对请求错误做些什么
24   return Promise.reject(error)
25 })
26 
27 // 添加响应拦截器
28 instance.interceptors.response.use(function (response) {
29   // 对响应数据做点什么
30   return response.data
31 }, function (error) {
32   // console.log(error)
33   // 有错误响应,后台正常返回了错误信息
34   if (error.response) {
35     if (error.response.status === 401) {
36       // 清除掉无效的token
37       delToken()
38       // 拦截到登录
39       router.push('/login')
40     } else {
41       // 有错误响应,提示错误消息
42       // this.$toast(error.response.data.message)
43       Toast(error.response.data.message)
44     }
45   }
46   // 对响应错误做点什么
47   return Promise.reject(error)
48 })
49 
50 // 导出实例
51 export default instance

storage.js  公共存储

 1 const KEY = 'vant-mobile-exp-token'
 2 
 3 // 1. 获取token (需要返回token)
 4 export const getToken = () => {
 5   return localStorage.getItem(KEY)
 6 }
 7 // 2. 设置token
 8 export const setToken = (newToken) => {
 9   localStorage.setItem(KEY, newToken)
10 }
11 // 3. 删除token
12 export const delToken = () => {
13   localStorage.removeItem(KEY)
14 }

vant按需导入的vant-ui.js

 1 // 按需导入:将需要的vant组件,逐一导入
 2 import {
 3   Grid,
 4   GridItem,
 5   CellGroup, Icon, List, Cell, Toast, NavBar, Form, Field, Button, Rate, Tabbar, TabbarItem
 6 } from 'vant'
 7 import Vue from 'vue'
 8 Vue.use(Grid)
 9 Vue.use(GridItem)
10 Vue.use(CellGroup)
11 Vue.use(Icon)
12 Vue.use(List)
13 Vue.use(Cell)
14 Vue.use(Toast)
15 Vue.use(NavBar)
16 Vue.use(Form)
17 Vue.use(Field)
18 Vue.use(Tabbar)
19 Vue.use(TabbarItem)
20 Vue.use(Button)
21 Vue.use(Rate)

6、views目录下的视图

Article.vue

  1 <template>
  2   <div class="article-page">
  3     <!-- 头部导航 -->
  4     <nav class="my-nav van-hairline--bottom">
  5       <a
  6         :class="{ active: sorter === 'weight_desc' }"
  7         @click="changeSorter('weight_desc')"
  8         href="javascript:;"
  9         >推荐</a
 10       >
 11       <!-- 对于axios,如果 传递的 属性值,为null或undefined,则此参数会被忽略 -->
 12       <a
 13         :class="{ active: sorter === null }"
 14         @click="changeSorter(null)"
 15         href="javascript:;"
 16         >最新</a
 17       >
 18       <div class="logo"><img src="@/assets/logo.png" alt=""></div>
 19     </nav>
 20 
 21     <van-list
 22       v-model="loading"
 23       :finished="finished"
 24       finished-text="没有更多数据了"
 25       @load="onLoad"
 26     >
 27       <ArticleItem v-for="item in list" :key="item.id" :item="item"></ArticleItem>
 28     </van-list>
 29 
 30   </div>
 31 </template>
 32 
 33 <script>
 34 import { getArticles } from '@/api/article'
 35 export default {
 36   name: 'article-page',
 37   data () {
 38     return {
 39       current: 1, // 当前页
 40       sorter: 'weight_desc', // 推荐
 41       list: [], // 文章列表
 42       loading: false, // 是否在加载中
 43       finished: false // 是否加载完了全部的数据
 44     }
 45   },
 46   async created () {
 47     // 获取推荐的,第1页的10条数据
 48     // const res = await getArticles({
 49     //   current: this.current,
 50     //   sorter: this.sorter
 51     // })
 52     // this.list = res.data.rows
 53     // console.log(res.data.rows)
 54   },
 55   methods: {
 56     async onLoad () {
 57       const res = await getArticles({
 58         current: this.current,
 59         sorter: this.sorter
 60       })
 61       console.log(res)
 62       // 需要在 this.list 基础上,累加 res.data.rows
 63       this.list.push(...res.data.rows)
 64       // 如果数据已经请求完毕,需要将loading改成false,才能加载下一页的数据
 65       // 一旦 loading 改为 false,load事件可以再次触发
 66       this.loading = false
 67       this.current++ // 当前页+1
 68 
 69       // 对于没有更多数据的数据
 70       if (this.current > res.data.pageTotal) {
 71         this.finished = true
 72       }
 73     },
 74     changeSorter (value) {
 75       // 修改排序规则 (推荐/最新)
 76       this.sorter = value
 77 
 78       // 重置数据
 79       this.current = 1 // 排序条件变化,重新从第一页开始加载
 80       this.list = [] // 数据重置为空
 81       this.finished = false // finished 重置,重新有数据可以加载了
 82       // this.loading = false
 83 
 84       // 标记需要开始加载了,由于我们是手动调用加载更多 onLoad 方法
 85       // 所以loading 需要自己改成 true,避免重复触发
 86       this.loading = true
 87       // 根据最新的条件重新渲染
 88       this.onLoad()
 89     }
 90   }
 91 }
 92 </script>
 93 
 94 <style lang="less" scoped>
 95 .article-page {
 96   margin-bottom: 50px;
 97   margin-top: 44px;
 98   .my-nav {
 99     height: 44px;
100     position: fixed;
101     left: 0;
102     top: 0;
103     width: 100%;
104     z-index: 999;
105     background: #fff;
106     display: flex;
107     align-items: center;
108     > a {
109       color: #999;
110       font-size: 14px;
111       line-height: 44px;
112       margin-left: 20px;
113       position: relative;
114       transition: all 0.3s;
115       &::after {
116         content: '';
117         position: absolute;
118         left: 50%;
119         transform: translateX(-50%);
120         bottom: 0;
121         width: 0;
122         height: 2px;
123         background: #222;
124         transition: all 0.3s;
125       }
126       &.active {
127         color: #222;
128         &::after {
129           width: 14px;
130         }
131       }
132     }
133     .logo {
134       flex: 1;
135       display: flex;
136       justify-content: flex-end;
137       > img {
138         width: 64px;
139         height: 28px;
140         display: block;
141         margin-right: 10px;
142       }
143     }
144   }
145 }
146 </style>

Collect.vue

 1 <template>
 2   <div class="collect-page">
 3     <van-nav-bar fixed title="我的收藏" />
 4     <van-list
 5       v-model="loading"
 6       :finished="finished"
 7       finished-text="没有更多了"
 8       @load="onLoad"
 9     >
10       <ArticleItem v-for="(item, i) in list" :key="i" :item="item" />
11     </van-list>
12   </div>
13 </template>
14 
15 <script>
16 import { getArticlesCollect } from '@/api/article'
17 export default {
18   name: 'collect-page',
19   data () {
20     return {
21       list: [],
22       loading: false,
23       finished: false,
24       page: 1
25     }
26   },
27   methods: {
28     async onLoad () {
29       // 异步更新数据
30       const { data } = await getArticlesCollect({ page: this.page })
31       this.list.push(...data.rows)
32       this.loading = false
33       this.page++
34 
35       if (this.page > data.pageTotal) {
36         this.finished = true
37       }
38     }
39   }
40 }
41 </script>
42 
43 <style lang="less" scoped>
44 .collect-page {
45   margin-bottom: 50px;
46   margin-top: 44px;
47 }
48 </style>

Detail.vue

  1 <template>
  2   <div class="detail-page">
  3     <van-nav-bar
  4       left-text="返回"
  5       @click-left="$router.back()"
  6       fixed
  7       title="面经详细"
  8     />
  9     <header class="header">
 10       <h1>{{ article.stem }}</h1>
 11       <p>
 12         {{ article.createdAt }} | {{ article.views }} 浏览量 |
 13         {{ article.likeCount }} 点赞数
 14       </p>
 15       <p>
 16         <img :src="article.avatar" alt="" />
 17         <span>{{ article.creator }}</span>
 18       </p>
 19     </header>
 20     <main class="body" v-html="article.content"></main>
 21     <div class="opt">
 22       <van-icon @click="toggleLike" :class="{active:article.likeFlag}" name="like-o"/>
 23       <van-icon @click="toggleCollect" :class="{active:article.collectFlag}" name="star-o"/>
 24     </div>
 25   </div>
 26 </template>
 27 
 28 <script>
 29 import { getArticleDetail, updateCollect, updateLike } from '@/api/article'
 30 
 31 export default {
 32   name: 'detail-page',
 33   data () {
 34     return {
 35       article: {}
 36     }
 37   },
 38   async created () {
 39     this.article = {}
 40     const { data } = await getArticleDetail(this.$route.params.id)
 41     this.article = data
 42   },
 43   methods: {
 44     async toggleLike () {
 45       await updateLike(this.article.id)
 46       this.article.likeFlag = !this.article.likeFlag
 47       if (this.article.likeFlag) {
 48         this.article.likeCount++
 49         this.$toast.success('点赞成功')
 50       } else {
 51         this.article.likeCount--
 52         this.$toast.success('取消点赞')
 53       }
 54     },
 55     async toggleCollect () {
 56       await updateCollect(this.article.id)
 57       this.article.collectFlag = !this.article.collectFlag
 58       if (this.article.collectFlag) {
 59         this.$toast.success('收藏成功')
 60       } else {
 61         this.$toast.success('取消收藏')
 62       }
 63     }
 64   }
 65 }
 66 </script>
 67 
 68 <style lang="less" scoped>
 69 .detail-page {
 70   margin-top: 44px;
 71   overflow: hidden;
 72   padding: 0 15px;
 73   .header {
 74     h1 {
 75       font-size: 24px;
 76     }
 77     p {
 78       color: #999;
 79       font-size: 12px;
 80       display: flex;
 81       align-items: center;
 82     }
 83     img {
 84       width: 40px;
 85       height: 40px;
 86       border-radius: 50%;
 87       overflow: hidden;
 88     }
 89   }
 90   .opt {
 91     position: fixed;
 92     bottom: 100px;
 93     right: 0;
 94     > .van-icon {
 95       margin-right: 20px;
 96       background: #fff;
 97       width: 40px;
 98       height: 40px;
 99       line-height: 40px;
100       text-align: center;
101       border-radius: 50%;
102       box-shadow: 2px 2px 10px #ccc;
103       font-size: 18px;
104       &.active {
105         background: #FEC635;
106         color: #fff;
107       }
108     }
109   }
110 }
111 </style>

Layout.vue

 1 <template>
 2   <div class="layout-page">
 3     <!-- 二级路由出口:匹配的二级路由的组件展示的位置 -->
 4     <router-view></router-view>
 5 
 6     <!-- route 开启路由模式 -->
 7     <van-tabbar route>
 8       <van-tabbar-item to="/article" icon="fire-o">面经</van-tabbar-item>
 9       <van-tabbar-item to="/collect" icon="star-o">收藏</van-tabbar-item>
10       <van-tabbar-item to="/like" icon="like-o">喜欢</van-tabbar-item>
11       <van-tabbar-item to="/user" icon="user-o">我的</van-tabbar-item>
12     </van-tabbar>
13   </div>
14 </template>
15 
16 <script>
17 export default {
18   name: 'LayoutPage'
19 }
20 </script>
21 
22 <style lang="less" scoped></style>

Like.vue

 1 <template>
 2   <div class="like-page">
 3     <van-nav-bar fixed title="我的点赞" />
 4     <van-list
 5       v-model="loading"
 6       :finished="finished"
 7       finished-text="没有更多了"
 8       @load="onLoad"
 9     >
10       <ArticleItem v-for="(item,i) in list" :key="i" :item="item" />
11     </van-list>
12   </div>
13 </template>
14 
15 <script>
16 import { getArticlesLike } from '@/api/article'
17 export default {
18   name: 'like-page',
19   data () {
20     return {
21       list: [],
22       loading: false,
23       finished: false,
24       page: 1
25     }
26   },
27   methods: {
28     async onLoad () {
29       // 异步更新数据
30       const { data } = await getArticlesLike({ page: this.page })
31       this.list.push(...data.rows)
32       this.loading = false
33       this.page++
34 
35       if (this.page > data.pageTotal) {
36         this.finished = true
37       }
38     }
39   }
40 }
41 </script>
42 
43 <style lang="less" scoped>
44 .like-page {
45   margin-bottom: 50px;
46   margin-top: 44px;
47 }
48 </style>

Login.vue

 1 <template>
 2   <div class="login-page">
 3     <!-- 登录头部 -->
 4     <van-nav-bar title="登录"/>
 5 
 6     <!-- 表单内容 -->
 7     <van-form @submit="onSubmit">
 8       <van-field
 9         v-model="username"
10         name="username"
11         label="用户名"
12         placeholder="请输入用户名"
13         :rules="[
14           { required: true, message: '请填写用户名' },
15           { pattern: /^\w{5,}$/, message: '用户名至少包含5个字符'}
16         ]"
17       />
18       <van-field
19         v-model="password"
20         type="password"
21         name="password"
22         label="密码"
23         placeholder="请输入密码"
24         :rules="[
25           { required: true, message: '请填写密码' },
26           { pattern: /^\w{6,}$/, message: '密码至少包含6个字符' }
27         ]"
28       />
29       <div style="margin: 16px;">
30         <van-button block type="info" native-type="submit">提交</van-button>
31       </div>
32     </van-form>
33 
34     <router-link class="link" to="/register">注册账号</router-link>
35   </div>
36 </template>
37 
38 <script>
39 import { login } from '@/api/user'
40 import { setToken } from '@/utils/storage'
41 export default {
42   name: 'LoginPage',
43   data () {
44     return {
45       username: '',
46       password: ''
47     }
48   },
49   methods: {
50     // 监听表单的提交,形参中:可以获取到输入框的值
51     async onSubmit (values) {
52       const { data } = await login(values)
53       // 1. 成功的提示
54       this.$toast('登录成功')
55       // 2. 将token存入本地
56       setToken(data.token)
57       // 3. 跳转首页
58       this.$router.push('/')
59     }
60   }
61 }
62 </script>
63 
64 <style lang="less" scoped>
65 .link {
66   color: #069;
67   font-size: 12px;
68   padding-right: 20px;
69   float: right;
70 }
71 </style>

Register.vue

 1 <template>
 2   <div class="login-page">
 3     <!-- 注册头部 -->
 4     <van-nav-bar title="注册"/>
 5 
 6     <!-- 表单内容 -->
 7     <van-form @submit="onSubmit">
 8       <van-field
 9         v-model="username"
10         name="username"
11         label="用户名"
12         placeholder="请输入用户名"
13         :rules="[
14           { required: true, message: '请填写用户名' },
15           { pattern: /^\w{5,}$/, message: '用户名至少包含5个字符'}
16         ]"
17       />
18       <van-field
19         v-model="password"
20         type="password"
21         name="password"
22         label="密码"
23         placeholder="请输入密码"
24         :rules="[
25           { required: true, message: '请填写密码' },
26           { pattern: /^\w{6,}$/, message: '密码至少包含6个字符' }
27         ]"
28       />
29       <div style="margin: 16px;">
30         <van-button block type="primary" native-type="submit">提交</van-button>
31       </div>
32     </van-form>
33 
34     <router-link class="link" to="/login">有账号,去登陆</router-link>
35   </div>
36 </template>
37 
38 <script>
39 // import { Toast } from 'vant'
40 import { register } from '@/api/user'
41 export default {
42   name: 'LoginPage',
43   data () {
44     return {
45       username: '',
46       password: ''
47     }
48   },
49   methods: {
50     // 监听表单的提交,形参中:可以获取到输入框的值
51     async onSubmit (values) {
52       // const res = await register(values)
53       // console.log(res)
54 
55       // Toast('注册成功')
56       // Toast.loading({
57       //   message: '拼命加载中...',
58       //   forbidClick: true
59       // })
60       // Toast.success('成功文案')
61       // Toast.fail('失败文案')
62 
63       // 其实Toast方法已经被挂到了原型上,通过 this.$toast 直接调用
64       this.$toast.loading({
65         message: '请求中...',
66         forbidClick: true
67       })
68 
69       await register(values)
70       this.$toast.success('注册成功')
71       this.$router.push('/login')
72     }
73   }
74 }
75 </script>
76 
77 <style lang="less" scoped>
78 .link {
79   color: #069;
80   font-size: 12px;
81   padding-right: 20px;
82   float: right;
83 }
84 </style>

User.vue

 1 <template>
 2   <div class="user-page">
 3     <div class="user">
 4       <img :src="avatar" alt="" />
 5       <h3>{{ username }}</h3>
 6     </div>
 7     <van-grid clickable :column-num="3" :border="false">
 8       <van-grid-item icon="clock-o" text="历史记录" to="/" />
 9       <van-grid-item icon="bookmark-o" text="我的收藏" to="/collect" />
10       <van-grid-item icon="thumb-circle-o" text="我的点赞" to="/like" />
11     </van-grid>
12 
13     <van-cell-group class="mt20">
14       <van-cell title="推荐分享" is-link />
15       <van-cell title="意见反馈" is-link />
16       <van-cell title="关于我们" is-link />
17       <van-cell @click="logout" title="退出登录" is-link />
18     </van-cell-group>
19   </div>
20 </template>
21 
22 <script>
23 import { getUserInfo } from '@/api/user'
24 import { delToken } from '@/utils/storage'
25 export default {
26   name: 'user-page',
27   data () {
28     return {
29       username: '',
30       avatar: ''
31     }
32   },
33   async created () {
34     const { data } = await getUserInfo()
35     this.username = data.username
36     this.avatar = data.avatar
37   },
38   methods: {
39     logout () {
40       delToken()
41       this.$router.push('/login')
42     }
43   }
44 }
45 </script>
46 
47 <style lang="less" scoped>
48 .user-page {
49   padding: 0 10px;
50   background: #f5f5f5;
51   height: 100vh;
52   .mt20 {
53     margin-top: 20px;
54   }
55   .user {
56     display: flex;
57     padding: 20px 0;
58     align-items: center;
59     img {
60       width: 80px;
61       height: 80px;
62       border-radius: 50%;
63       overflow: hidden;
64     }
65     h3 {
66       margin: 0;
67       padding-left: 20px;
68       font-size: 18px;
69     }
70   }
71 }
72 </style>

7、根组件App.vue

 1 <template>
 2   <div id="app">
 3     <!-- 路由出口 -->
 4     <router-view/>
 5   </div>
 6 </template>
 7 
 8 <script>
 9 export default {
10   data () {
11     return {
12 
13     }
14   }
15 }
16 </script>
17 
18 <style lang="less">
19 
20 </style>

8、main.js

 1 import Vue from 'vue'
 2 import App from './App.vue'
 3 import router from './router'
 4 
 5 // 全部导入:将所有的vant组件,一次性导入到项目中
 6 // import Vant from 'vant'
 7 // import 'vant/lib/index.css'
 8 // Vue.use(Vant)
 9 
10 // 导入按需导入的配置文件
11 import '@/utils/vant-ui'
12 
13 // 组件的全局注册
14 import ArticleItem from '@/components/ArticleItem'
15 Vue.component('ArticleItem', ArticleItem)
16 
17 Vue.config.productionTip = false
18 
19 new Vue({
20   router,
21   render: h => h(App)
22 }).$mount('#app')

9、package.json

 1 {
 2   "name": "hm-exp-mobile",
 3   "version": "0.1.0",
 4   "private": true,
 5   "scripts": {
 6     "serve": "vue-cli-service serve",
 7     "build": "vue-cli-service build",
 8     "lint": "vue-cli-service lint"
 9   },
10   "dependencies": {
11     "axios": "^1.3.5",
12     "core-js": "^3.8.3",
13     "vant": "^2.12.54",
14     "vue": "^2.6.14",
15     "vue-router": "^3.5.1"
16   },
17   "devDependencies": {
18     "@babel/core": "^7.12.16",
19     "@babel/eslint-parser": "^7.12.16",
20     "@vue/cli-plugin-babel": "~5.0.0",
21     "@vue/cli-plugin-eslint": "~5.0.0",
22     "@vue/cli-plugin-router": "~5.0.0",
23     "@vue/cli-service": "~5.0.0",
24     "@vue/eslint-config-standard": "^6.1.0",
25     "babel-plugin-import": "^1.13.6",
26     "eslint": "^7.32.0",
27     "eslint-plugin-import": "^2.25.3",
28     "eslint-plugin-node": "^11.1.0",
29     "eslint-plugin-promise": "^5.1.0",
30     "eslint-plugin-vue": "^8.0.3",
31     "less": "^4.0.0",
32     "less-loader": "^8.0.0",
33     "postcss-px-to-viewport": "1.1.1",
34     "vue-template-compiler": "^2.6.14"
35   }
36 }

10、postcss.config.js   

1 // postcss.config.js
2 module.exports = {
3   plugins: {
4     'postcss-px-to-viewport': {
5       // 标准屏的宽度,设计图 750-2倍图,标准屏 375
6       viewportWidth: 375
7     }
8   }
9 }

11、vue.config.js

 1 const { defineConfig } = require('@vue/cli-service')
 2 module.exports = defineConfig({
 3   transpileDependencies: true,
 4   // 将资源访问路径从 / 配置成 ./ 相对路径
 5   publicPath: './',
 6   css: {
 7     loaderOptions: {
 8       less: {
 9         // 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。
10         lessOptions: {
11           modifyVars: {
12             // 直接覆盖变量
13             blue: '#FA6D1D'
14           }
15         }
16       }
17     }
18   }
19 })

 

打包发布

1 // yarn管理
2 yarn build
3 
4 // npm 管理
5 npm run build

配置publicPath

1 module.exports = {
2   // 设置获取.js,.css文件时,是以相对地址为基准的。
3   // https://cli.vuejs.org/zh/config/#publicpath
4   publicPath: './'
5 }

路由懒加载

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

  官网链接:https://router.vuejs.org/zh/guide/advanced/lazy-loading.html#%E4%BD%BF%E7%94%A8-webpack

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

1 const Detail = () => import('@/views/detail')
2 const Register = () => import('@/views/register')
3 const Login = () => import('@/views/login')
4 const Article = () => import('@/views/article')
5 const Collect = () => import('@/views/collect')
6 const Like = () => import('@/views/like')
7 const User = () => import('@/views/user')

 

posted @ 2024-04-08 17:19  为你编程  阅读(1500)  评论(0编辑  收藏  举报