一、新建项目
与之前的不同之处在于,本次选择自动创建Router,如下:
注:发现在src目录下新增views文件夹。views文件夹和components文件夹都是存放vue页面文件的。区别在于凡是通过router/index.js路由文件引入的.vue文件放入view文件夹,其余放入components文件夹。
二、开发
2.1 安装Vant组件库
组件库地址:vant-contrib.gitee.io/vant/#/zh-CN/theme
执行npm i vant -S
2.2 组件引入
推荐采用方式一引入组件,但是方式一过于麻烦,一般采用方式三。一次性引入vant的所有组件,导致打包的体积大,后期解决该问题。
将上述代码添加至main.js中
2.3 测试组件库
将下面代码复制至App.vue中,页面效果实现如上,说明组件引入成功。
<van-button type="primary">主要按钮</van-button>
<van-button type="info">信息按钮</van-button>
<van-button type="default">默认按钮</van-button>
<van-button type="warning">警告按钮</van-button>
<van-button type="danger">危险按钮</van-button>
2.4 Tabbar和导航栏的创建
2.4.1创建主页Home和用户页User
分别在views文件夹下新建Home和User文件夹,并建立对应的vue文件。
2.4.2 tabbar标签栏的创建
如上,App.vue中添加占位符和Tabbar。
<template>
<div>
<router-view></router-view>
<van-tabbar route>
<van-tabbar-item to="/" icon="home-o">首页</van-tabbar-item>
<van-tabbar-item to="/user" icon="user-o">我的</van-tabbar-item>
</van-tabbar>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style lang="less" scoped>
</style>
并在index.js中添加路由:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/views/Home/Home.vue'
import User from '@/views/User/User.vue'
Vue.use(VueRouter)
const routes = [
{ path: '/', component: Home },
{ path: '/user', component: User }
]
const router = new VueRouter({
routes
})
export default router
2.4.3 导航栏的创建
主要存在问题如下:
(1)导航栏固定,这里是通过fixed参数设置
(2)底下的h1字体会被顶部的导航栏和底部的Tabbar标签栏挡住,这里需要设置padding。padding的设置为顺时针,上,右,下,左
(3)样式设置采用审查元素查看,若不生效,可加/deep/
<template>
<div class="home-container">
<van-nav-bar title="头条" fixed/>
<h1>aaa</h1>
<h1>bbb</h1>
</div>
</template>
<script>
export default {
name: 'Home'
}
</script>
<style lang="less" scoped>
.home-container {
padding: 46px 0 50px 0;
.van-nav-bar {
background-color: #007bff;
}
/deep/ .van-nav-bar__title {
color: aliceblue;
}
}
</style>
2.5 axios请求数据
npm i axios -S安装
src目录下新建utils文件夹,并在utils文件夹下新建request.js文件,代码如下:
import axios from 'axios'
const request = axios.create({
baseURL: 'https://www.escook.cn'
})
export default request
Home.vue中引入request.js文件,并定义方法发起axios请求,并在created方法中调用axios请求的方法。
<template>
<div class="home-container">
<van-nav-bar title="头条" fixed/>
</div>
</template>
<script>
import request from '@/utils/request.js'
export default {
name: 'Home',
data() {
return {
// 页码值
page: 1,
// 每页展示的数据条数
limit: 10
}
},
created() {
this.initArticleList()
},
methods: {
async initArticleList() {
const { data: res } = await request.get('/articles', {
params: {
_page: this.page,
_limit: this.limit
}
})
console.log(res, res)
}
}
}
</script>
<style lang="less" scoped>
.home-container {
padding: 46px 0 50px 0;
.van-nav-bar {
background-color: #007bff;
}
/deep/ .van-nav-bar__title {
color: aliceblue;
}
}
</style>
如上,若在我的页面也想调用axios请求'/articles'获取相关数据,则又需要在User.vue重新写一次axios.get....。为了提高可用性,创建API接口:
(1)在src下新建api文件夹,并在api文件夹下新建articleAPI.js文件。articleAPI.js代码如下:
// 文章相关的接口,都封装到这个模块中
import request from '@/utils/request.js'
// 向外导出一个API函数
export const getArticleListAPI = function(_page, _limit) {
return request.get('/articles', {
params: {
_page,
_limit
}
})
}
(2)在Home.vue中引入articleAPI.js中的getArticleListAPI方法,并调用,代码如下:
注:方法引入时两侧需要加大括号
2.6 文章列表
(1)在src/components文件夹下新建Article文件夹,并在其下新建ArticleInfo.vue组件。
<template>
<div>
<van-cell>
<!-- 标题区域的插槽 -->
<template #title>
<div class="title-box">
<!-- 标题 -->
<span>{{ title }}</span>
<!-- 单张图片 -->
<img :src="cover.images[0]" alt="" class="thumb" v-if="cover.type === 1">
</div>
<!-- 三张图片 -->
<div class="thumb-box" v-if="cover.type === 3">
<img :src="item" alt="" class="thumb" v-for="(item, index) in cover.images" :key="index">
</div>
</template>
<!-- label区域的插槽 -->
<template #label>
<div class="label-box">
<span>作者 {{ author }} {{ cmbCount }} 评论 发布日期 {{ time }}</span>
<!-- 关闭按钮 -->
<van-icon name="cross"></van-icon>
</div>
</template>
</van-cell>
</div>
</template>
<script>
export default {
name: 'ArticleInfo',
props: {
// 标题
title: {
type: String,
default: ''
},
// 作者
author: {
type: String,
default: ''
},
// 评论数
cmbCount: {
// 通过数组的形式,当前属性定义为多个可能的类型
type: [String, Number],
default: 0
},
// 发布时间
time: {
type: String,
default: ''
},
// 封面的信息对象
cover: {
type: Object,
default: function() {
return { type: 0 }
}
}
}
}
</script>
<style lang="less" scoped>
.label-box {
display: flex;
justify-content: space-between;
align-items: center;
}
.thumb {
width: 113px;
height: 70px;
background-color: #f8f8f8;
object-fit: cover;
}
.title-box {
display: flex;
justify-content: space-between;
align-items: flex-start
}
.thumb-box {
display: flex;
justify-content: space-between;
}
</style>
(2)Home.vue中引入并使用ArticleInfo.vue组件
注:主要涉及的是props值的传递。
tips:(1)props值类型可设置为数组,定义为多个可能的类型
(2)父组件在设置属性值时,若原变量名为小驼峰式,最好修改为全部小写,中间加-。如下:
2.7 上拉加载更多
借助vant中展示组件中的List列表实现上拉加载更多的功能
(1)使用van-list包裹<ArticleInfo>
(2)data中声明:loading(默认true)、finished(默认false)两个属性
(3)onLoad方法定义(page++、请求数据)
(4)修改initArticleList方法(数据拼接、loading属性设置、finished属性设置)
修改后的Home.vue代码如下:
<template>
<div class="home-container">
<van-nav-bar title="头条" fixed/>
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<ArticleInfo
v-for="item in articleList"
:key="item.art_id" :title="item.title"
:author="item.aut_name"
:cmbCount="item.comm_count"
:time="item.pubdate"
:cover="item.cover">
</ArticleInfo>
</van-list>
</div>
</template>
<script>
import { getArticleListAPI } from '@/api/articleAPI.js'
import ArticleInfo from '@/components/Article/ArticleInfo.vue'
export default {
name: 'Home',
components: {
ArticleInfo
},
data() {
return {
// 页码值
page: 1,
// 每页展示的数据条数
limit: 10,
// 文章列表
articleList: [],
// 是否正在加载下一页数据,loadding为true,则不会反复触发load事件
// 当下一页数据请求回来后,要记得把loading改为false
// 初始值设为true,可以防止首次进入页面时调用load事件
loading: true,
// 所有数据是否加载完成
finished: false
}
},
created() {
this.initArticleList()
},
methods: {
async initArticleList() {
const { data: res } = await getArticleListAPI(this.page, this.limit)
// this.articleList=[旧数据,新数据]
this.articleList = [...this.articleList, ...res]
this.loading = false
if (res.length === 0) {
// 无下一页数据,finished设置为true
this.finished = true
}
},
onLoad() {
console.log('ok')
// 页码值加1
this.page++
// 重新请求数据
this.initArticleList()
}
}
}
</script>
<style lang="less" scoped>
.home-container {
padding: 46px 0 50px 0;
.van-nav-bar {
background-color: #007bff;
}
/deep/ .van-nav-bar__title {
color: aliceblue;
}
}
</style>
2.8 下拉刷新
借助vant中展示组件中的List列表实现下拉刷新更多的功能.
(1)使用van-pull-refresh包裹van-list。并将其disabled属性设置为finished。也就是说当加载完成时,设置下拉刷新不可用
(2)data中定义refreshing属性,初始值设置为false
(3)定义onRefresh方法(page++、请求数据)
(4)修改initArticleList方法。(添加isRefresh入参、判断是下拉刷新还是上拉加载更多。前者是新数据在前,后者是新数据在后)
修改后的Home.vue如下:
<template>
<div class="home-container">
<van-nav-bar title="头条" fixed/>
<van-pull-refresh v-model="refreshing" :disabled="finished" @refresh="onRefresh">
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<ArticleInfo
v-for="item in articleList"
:key="item.art_id" :title="item.title"
:author="item.aut_name"
:cmbCount="item.comm_count"
:time="item.pubdate"
:cover="item.cover">
</ArticleInfo>
</van-list>
</van-pull-refresh>
</div>
</template>
<script>
import { getArticleListAPI } from '@/api/articleAPI.js'
import ArticleInfo from '@/components/Article/ArticleInfo.vue'
export default {
name: 'Home',
components: {
ArticleInfo
},
data() {
return {
// 页码值
page: 1,
// 每页展示的数据条数
limit: 10,
// 文章列表
articleList: [],
// 是否正在加载下一页数据,loading为true,则不会反复触发load事件
// 当下一页数据请求回来后,要记得把loading改为false
// 初始值设为true,可以防止首次进入页面时调用load事件
loading: true,
// 所有数据是否加载完成
finished: false,
// 是否正在加载下一页数据,refreshing为true,则不会反复触发onRefresh事件
refreshing: false
}
},
created() {
this.initArticleList()
},
methods: {
async initArticleList(isRefresh) {
console.log('------')
console.log(this.page)
const { data: res } = await getArticleListAPI(this.page, this.limit)
// isRefresh为true,证明是下拉刷新,新数据在前
if (isRefresh) {
// this.articleList=[新数据, 旧数据]
this.articleList = [...res, ...this.articleList]
this.loading = false
} else {
// this.articleList=[旧数据,新数据]
this.articleList = [...this.articleList, ...res]
this.loading = false
}
if (res.length === 0) {
// 无下一页数据,finished设置为true
this.finished = true
}
},
onLoad() {
// 页码值加1
this.page++
// 重新请求数据
this.initArticleList(true)
},
onRefresh() {
// 页码值加1
this.page++
// 重新请求数据
this.initArticleList()
}
}
}
</script>
<style lang="less" scoped>
.home-container {
padding: 46px 0 50px 0;
.van-nav-bar {
background-color: #007bff;
}
/deep/ .van-nav-bar__title {
color: aliceblue;
}
}
</style>
至此,购物车项目首页开发完成。
项目完整链接Headline | 黑马头条 - 移动端
三、 定制主题(参考vue官网)
背景:一个组件在多个页面需要引入,如NavBar 导航栏默认背景颜色为白色,如果要修改颜色,则每个页面都需要重复修改。为了解决该问题,提出定制主题的思路。
步骤一 引入样式源文件
定制主题时,需要引入组件对应的 Less 样式文件,修改的是main.js文件。注释原引入index.css文件的行,修改引入文件为index.less文件。
// import 'vant/lib/index.css'
// 引入全部样式
import 'vant/lib/index.less'
步骤二 修改样式变量
本项目为 vue-cli 搭建的项目,在 vue.config.js
中进行配置。根目录下新建 vue.config.js文件。
// vue.config.js
module.exports = {
css: {
loaderOptions: {
less: {
// 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。
lessOptions: {
modifyVars: {
// 直接覆盖变量
'text-color': '#111',
'border-color': '#eee',
// 或者可以通过 less 文件覆盖(文件路径为绝对路径)
hack: `true; @import "your-less-file-path.less";`,
},
},
},
},
},
};
less版本号查看:package.json搜索less-loader.查看发现小于6.0,移除
lessOptions 这一级
// vue.config.js
module.exports = {
css: {
loaderOptions: {
less: {
modifyVars: {
// 直接覆盖变量
// 'text-color': '#111',
// 'border-color': '#eee',
// 或者可以通过 less 文件覆盖(文件路径为绝对路径)
// hack: 'true; @import "your-less-file-path.less";'
}
}
}
}
}
查看vue官网,发现NavBar样式变量中控制背景颜色的变量如下:
方式一:直接覆盖变量。 修改vue.config.js的样式变量:(不采用该方式)
// vue.config.js
module.exports = {
css: {
loaderOptions: {
less: {
modifyVars: {
// 直接覆盖变量
'nav-bar-background-color': 'red'
}
}
}
}
}
修改后生效,以后NavBar默认颜色为红色。该方法的缺陷在于每次修改完都需要执行npm run serve重启服务器。采用方式二避免该问题。
方式二:新建theme.less文件,并在vue.config.js
中引入。(采用)
(1)在src下新建theme.less文件,并在文件中设置背景颜色和字体颜色
@blue: #007bff;
@nav-bar-background-color: @blue;
@nav-bar-title-text-color: #ecf0f3;
(2)vue.config.js中引入
theme.less文件,如下:
// vue.config.js
const path = require('path')
const themePath = path.join(__dirname, './src/theme.less')
module.exports = {
css: {
loaderOptions: {
less: {
modifyVars: {
// 或者可以通过 less 文件覆盖(文件路径为绝对路径)
hack: `true; @import "${themePath}";`
}
}
}
}
}
则通过修改theme.less文件保存后样式直接生效,不需再重启服务器。
四、vue.config.js
相关配置
(1)publicPath设置为'/'或者''。打包后(npm run build)的dist/index.html支持file协议打开,也就是双击本地文件打开。默认是仅支持http协议的。
浏览器输入:D:/vueworkspace/toutiao/dist/index.html可直接访问。