前端【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 2: 5 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
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')