笔记来源:拉勾教育 - 大前端就业集训营
文章内容:学习过程中的笔记、感悟、和经验
项目搭建和选课功能
项目准备
移动端项目 :使用调试工具使用移动端调试
总体分为三大块:选课(无需登录)、学习和我(需要登录才能查看)
项目构建:
- 创建新项目:
vue create 名字- 手动选择配置预设 - 创建项目 - 删除清空项目:删除不需要的文件、修改文件内容
- 初始化项目:添加接口封装文件夹(视频里添加了utils和services)
- 设置git:我这里不使用课程里的命令行而是使用sourcetree进行项目的管理,如果需要可以查看上一个项目
Vant组件库
和Element类似,Vant组件库是一个适用于移动端的Vue组件库,还在维护中,而且支持vue3和微信小程序
使用Vant
- 使用vue-cli脚手架工具安装Vant -
vue ui进入图形化界面安装依赖 - 这里选择导入所有组件,因为我们需要使用很多Vant组件
// src/main.js 引入并注册Vant
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
// 引入Vant
import Vant from 'vant'
import 'vant/lib/index.css'
// 将Vant注册为vue插件
Vue.use(Vant)
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
浏览器适配
使用rem布局 - 根据不同视口宽度设置rem
需要使用两个插件
- amfe-flexible:动态设置rem基准值 - 安装并引入
- **postcss-pxtorem:**自动将书写的px转换为rem,是vue-cli集成的postCss功能的一个插件
配置使用
- 在项目根目录下书写postcss配置文件 -
.postcssrc.js - 把浏览器配置信息书写在.browserslistrc中
- 注意修改配置文件需要重启serve
// src/main.js 安装amfe-flexible后引入
......
// 引入amfe-flexible自动设置根元素rem
import 'amfe-flexible'
......
// .postcssrc.js 根目录下创建配置文件
// 配置固定写法
module.exports = {
// 插件配置
plugins: {
// 自动添加浏览器前缀
'autoprefixer': {
// 这里vue不建议些在这里,所以放到 .browserslistrc 中去
// browsers: ['Android >= 4.0, 'iOS >= 8']
},
// postcss-pxtorem配置
'postcss-pxtorem': {
// 默认rem
rootValue: 37.5,
// 需要转换为rem的属性,*表示全部
propList: ['*']
}
}
}
.browserslistrc 根目录下添加浏览器配置
> 1%
last 2 versions
not dead
Android >= 4.0
iOS >= 8
src/App.vue 在组件中使用Vant的按钮组件试验一下两个插件是否生效
<template>
<div>
<van-button>按钮</van-button>
<div class="box"></div>
</div>
<!-- <router-view/> -->
</template>
<style lang="scss" scoped>
.box {
width: 100px;
height: 100px;
background: #ccc;
}
</style>
注意
- postcss-pxtorem无法转换行内的px属性
- 这里我遇见了postcss-pxtorem报错,将版本更改为5.1.1就可以了
路由处理
页面分析
- 登陆页为单独页面
- 选课、学习、我三个页面
- 错误页面为单独页面
- 其他页面功能后期添加
创建文件和路由规则设置
- 选课组件为根目录,因为选课即使不登陆也能查看并且默认打开就是这个页面
- 因为选课组件是默认首页,所以可以不使用路由懒加载而使用传统方式
- 因为是移动端就不再书写包含关系了
在views文件夹下创建五个页面文件夹并且创建index,下面是login的index.vue示例,其他页面大致相同
src/views/login/index.vue
<template>
<div>登陆页</div>
</template>
<script>
export default {
name: 'login'
}
</script>
// src/router/index.js 创建五个页面的路由
import Vue from 'vue'
import VueRouter from 'vue-router'
// 选课页面不使用懒加载
import Course from '@/views/course'
Vue.use(VueRouter)
const routes = [
{ // 登陆页
name: 'login',
path: '/login',
// 使用路由懒加载,按需加载,使用魔法命名
component: () => import(/* webpackChunkName: 'login' */'@/views/login')
},
{ // 选课页面,根页面
name: 'course',
path: '/',
component: Course
},
{ // 学习页面
name: 'study',
path: '/study',
component: () => import(/* webpackChunkName: 'study' */'@/views/study')
},
{ // 用户页面
name: 'user',
path: '/user',
component: () => import(/* webpackChunkName: 'user' */'@/views/user')
},
{ // 错误页面
name: 'error',
path: '*',
component: () => import(/* webpackChunkName: 'error' */'@/views/error')
}
]
const router = new VueRouter({
routes
})
export default router
封装请求模块- 接口文档
使用axios进行接口AJAX请求
- 安装axios插件,
npm install axios - 创建js文件,文件中使用axios创建示例,设置url前缀,并导出axios实例
// src/api/axios.js 新建文件封装请求模块,以后所有的接口相关功能都封装在api文件夹中
// 引入axios插件
import axiosPlug from 'axios'
// 创建axios实例
const axios = axiosPlug.create({
// 设置url前缀
baseURL: 'http://edufront.lagou.com'
})
// 导出实例
export default axios
src/App.vue 测试我们封装的接口是否生效,打开浏览器network查看XHR是否成功
<template>
<router-view/>
</template>
下面用完就可以删掉了
<script>
// 引入axios
import axios from '@/api/axios'
// 发起请求
axios({
method: 'get',
// 这个接口不需要登陆也可以请求
url: '/front/ad/getAdList'
})
</script>
公共组件 - 底栏
使用Vant中的标签栏组件,组件提供了路由模式可进行路由跳转
公共组件要单独封装,在需要使用的三个组件中引入三个组件
图标可以使用Vant中的图标组件替换
直接修改标签栏组件中的to属性即可实现点击跳转到指定组件
src/common/foot-bar.vue 单独封装底部标签栏组件作为公共组件,公共组件全部放在common目录
<template>
<!-- 底部标签栏,开启路由功能 -->
<van-tabbar route>
<!-- to设置跳转路由,icon图标 -->
<van-tabbar-item replace to="/" icon="cart-o">选课</van-tabbar-item>
<van-tabbar-item replace to="/study" icon="records">学习</van-tabbar-item>
<van-tabbar-item replace to="/user" icon="user-o">我</van-tabbar-item>
</van-tabbar>
</template>
<script>
export default {
name: 'footBar'
}
</script>
src/views/course/index.vue 选课页面引入并使用标签栏公共组件
<template>
<div class="course">
<!-- 创建标签栏子组件实例 -->
<footBar/>
</div>
</template>
<script>
// 引入标签栏子组件
import footBar from '@/common/foot-bar'
export default {
name: 'Course',
// 注册组件
components: {
footBar
}
}
</script>
选课功能
选课功能区分为上下两部分
- 顶部置顶展示区
- 底部全部课程列表展示区
新建两部分组件,将两部分组件分别引入到index.vue中使用
头部置顶组件
头部只有一个logo展示区,可以使用Vant图片组件
图片可以使用静态资源,使用:src = require(图片路径)引入
可能需要进行一些样式调整,Vant可以直接使用组件名作为类名进行属性设置
注意:我这里直接把顶部组件写在了index内部,就不单独写子组件了
src/views/course/index.vue 使用图片组件设置顶部logo
<template>
<div class="course">
<!-- 顶部 logo,使用require引入静态图片资源 -->
<van-image
width="180"
:src="require('@/assets/logo.png')"
/>
<!-- 创建标签栏子组件实例 -->
<footBar/>
</div>
</template>
.............
<style lang="scss" scoped>
// 顶部logo
.van-image {
margin-left: -20px;
}
</style>
广告轮播图
使用Vant中的轮播组件
轮播图需要使用的数据使用广告接口
- 获取所有广告位,这个就不封装了,直接网页打开查找一下首页轮播的id即可 - spaceKey
- 封装获取广告位及其对应的广告接口,传递spaceKey参数即可获取广告列表
在组件创建后获取广告列表,将广告列表存储起来,使用v-for进行轮播结构创建即可
注意:广告有status状态,如果状态为0则代表该条广告下架状态,不进行展示,所以保存列表的时候需要判断是否需要展示
视频里使用的是计算属性筛选,我认为最简单的应该是获取数据的时候直接进行筛选
最后需要进行一些样式调整
src/views/course/index.vue 添加轮播图
<template>
<div class="course">
<!-- 顶部 logo,使用require引入静态图片资源 -->
<van-image
width="180"
:src="require('@/assets/logo.png')"
/>
<!-- 轮播图 -->
<van-swipe class="my-swipe" :autoplay="3000" indicator-color="white">
<!-- 使用 v-for 遍历计算属性 -->
<van-swipe-item v-for="item in swipeListDate" :key="item.id">
<img :src="item.img" :alt="item.name">
</van-swipe-item>
</van-swipe>
<!-- 创建标签栏子组件实例 -->
<footBar/>
</div>
</template>
<script>
// 引入接口:获取广告位及对应广告
import { getAllAds } from '@/api/course'
// 引入标签栏子组件
import footBar from '@/common/foot-bar'
export default {
name: 'Course',
data () {
return {
// 广告列表
swipeList: []
}
},
// 注册组件
components: {
footBar
},
// 钩子函数
created () {
this.getAds()
},
methods: {
// 获取轮播广告列表
async getAds () {
// 调用接口
const { data } = await getAllAds(999)
// 将获取到的数据传给data
this.swipeList = data.content[0].adDTOList
}
},
// 计算属性
computed: {
// 过滤真正需要展示的轮播广告
swipeListDate () {
return this.swipeList.filter(item => item.status === 1)
}
}
}
</script>
<style lang="scss" scoped>
// 顶部logo
.van-image {
margin-left: -20px;
}
// 轮播图图片
.van-swipe .van-swipe-item img {
width: 100%;
height: 200px;
}
</style>
// src/api/course.js 新建js文件封装课程相关接口
// 引入封装的 axios
import axios from './axios'
// 根据 id 获取广告位及其对应广告
export const getAllAds = spaceKeys => {
return axios({
method: 'get',
url: `/front/ad/getAllAds?spaceKeys=${spaceKeys}`
})
}
课程列表基础布局
课程列表需要单独封装组件
使用Vant中的列表组件
src/views/course/index.vue - 中引入注册并使用列表组件
<script>
// 引入课程列表组件
import courseList from './son/course-list'
// 注册组件
components: {
footBar,
courseList
}</script>
<!-- 创建课程列表组件实例 -->
<courseList></courseList>
src/views/course/son/course-list.vue 新建文件书写课程列表组件
<template>
<!-- 列表组件 -->
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<!-- 列表项 -->
<van-cell v-for="item in listData" :key="item" :title="item" />
</van-list>
</template>
<script>
export default {
name: 'courseList',
data () {
return {
// 信号值 - 标记是否列表加载完毕
finished: false,
// 信号值 - 标记列表是否处于刷新状态
loading: false,
// 列表数据
listData: [1, 2, 3, 3, 3, 3, 1, 2, 3, 3, 3, 3]
}
},
methods: {
// 列表触底刷新事件
onLoad () {
}
}
}
</script>
固定列表处理
列表滚动不应让其他部分跟着滚动:设置列表固定定位,垂直方向允许滚动
因为列表可能要复用,所有有些样式属性可能就需要在引入的组件中设置
src/views/course/son/course-list.vue 列表组件设置公共样式
<style lang="scss" scoped>
// 列表定位
.van-list {
position: fixed;
left: 0;
right: 0;
// 开启Y轴滚动条
overflow-y: auto;
}
</style>
src/views/course/index.vue 使用组件的父组件设置私有样式,这样就不会影响组件在其他父组件中的样式
<style lang="scss" scoped>
................
// 课程列表顶部和底部定位
.van-list {
top: 260px;
bottom: 50px;
}
</style>
接口封装
使用分页查询课程信息接口吧(前台接口有点问题,使用后台接口)
直接在列表组件onload中发请求,并且处理相应属性
//src/api/course.js 封装课程分页查询接口
// 分页查询课程信息
export const getQueryCourses = data => {
return axios({
method: 'post',
url: '/boss/course/getQueryCourses',
data
})
}
单个课程结构布局与数据绑定
将获取到的数据添加到列表的数据中
单个列表设置结构和样式以及数据绑定
src/views/course/son/course-list.vue 书写结构并绑定数据、调整样式
<template>
<!-- 列表组件 -->
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<!-- 列表项,使用for循环 -->
<van-cell v-for="item in listData" :key="item.id">
<!-- 左侧图片 -->
<img :src="item.courseImgUrl" alt="">
<!-- 右侧信息 -->
<div class="r">
<h3>{{item.courseName}}</h3>
<p class="FirstField">{{item.previewFirstField}}</p>
<p>
<span class="discounts">¥{{item.discounts}}</span>
<!-- 直接使用删除线 -->
<s>¥{{item.price}}</s>
</p>
</div>
</van-cell>
</van-list>
</template>
<script>
// 引入接口:分页查询课程
import { getQueryCourses } from '@/api/course'
export default {
name: 'courseList',
data () {
return {
// 信号值 - 标记是否列表加载完毕
finished: false,
// 信号值 - 标记列表是否处于刷新状态
loading: false,
// 列表数据
listData: [],
// 课程查询页码
currentPage: 1
}
},
methods: {
// 列表触底刷新事件,第一次加载的时候就会默认执行一次
async onLoad () {
// 调用接口
const { data } = await getQueryCourses({
currentPage: this.currentPage,
pageSize: 10,
status: 1
})
console.log(data)
if (data.code === '000000') {
// 如果获取成功,将获取到的数据添加进列表数据中
this.listData.push(...data.data.records)
// 页数 +1
this.currentPage++
}
// 加载状态结束
this.loading = false
if (this.listData.length >= data.data.total) {
this.finished = true
}
}
}
}
</script>
<style lang="scss" scoped>
// 列表整体定位
.van-list {
position: fixed;
left: 0;
right: 0;
overflow-y: auto; // 开启Y轴滚动条
// 单个课程
.van-cell .van-cell__value {
display: flex;
// 清除默认效果
h3, p {
margin: 0;
}
img {
height: 100px;
width: 75px;
border-radius: 5px;
}
.r {
display: flex;
flex-direction: column;
margin-left: 10px;
.FirstField {
flex-grow: 1;
}
p .discounts {
margin-right: 10px;
color: #ff7452;
}
p s {
color: #ccc;
}
}
}
}
</style>
下拉刷新
列表组件提供了下拉刷新案例
刷新的时候重置参数,替换数据
添加提示信息
添加判断信息,判断是否获取到了数据并且数据不为空再设置数据
src/views/course/son/course-list.vue 添加下拉刷新功能
<template>
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<!-- 列表组件 -->
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<!-- 列表项,使用for循环 -->
<van-cell v-for="item in listData" :key="item.id">
<!-- 左侧图片 -->
<img :src="item.courseImgUrl" alt="">
<!-- 右侧信息 -->
<div class="r">
<h3>{{item.courseName}}</h3>
<p class="FirstField">{{item.previewFirstField}}</p>
<p>
<span class="discounts">¥{{item.discounts}}</span>
<!-- 直接使用删除线 -->
<s>¥{{item.price}}</s>
</p>
</div>
</van-cell>
</van-list>
</van-pull-refresh>
</template>
<script>
// 引入接口:分页查询课程
import { getQueryCourses } from '@/api/course'
export default {
name: 'courseList',
data () {
return {
// 信号值 - 标记是否列表加载完毕
finished: false,
// 信号值 - 标记列表是否处于刷新状态
loading: false,
// 列表数据
listData: [],
// 课程查询页码
currentPage: 1,
// 信号值 - 下拉刷新
refreshing: false
}
},
methods: {
// 下拉刷新函数
async onRefresh () {
// 重置页码为 1
this.currentPage = 1
// 修改触底为 false
this.finished = false
// 调用接口获取数据
const { data } = await getQueryCourses({
currentPage: this.currentPage,
pageSize: 10,
status: 1
})
// 添加条件,如果获取成功并且数据部位空使用数据
if (data.code === '000000' && data.data.records !== 0) {
// 直接替换数据
this.listData = data.data.records
// 页数 +1
this.currentPage++
// 修改下拉刷新信号值
this.refreshing = false
// 弹出提示
this.$toast('刷新成功')
}
},
// 列表触底刷新事件,第一次加载的时候就会默认执行一次
async onLoad () {
// 调用接口
const { data } = await getQueryCourses({
currentPage: this.currentPage,
pageSize: 10,
status: 1
})
if (data.code === '000000' && data.data.records !== 0) {
// 如果获取成功,将获取到的数据添加进列表数据中
this.listData.push(...data.data.records)
// 页数 +1
this.currentPage++
}
// 加载状态结束
this.loading = false
if (this.listData.length >= data.data.total) {
this.finished = true
}
}
}
}
</script>
<style lang="scss" scoped>
// 列表整体定位
.van-pull-refresh {
position: fixed;
left: 0;
right: 0;
overflow-y: auto; // 开启Y轴滚动条
// 单个课程
.van-cell .van-cell__value {
display: flex;
// 清除默认效果
h3, p {
margin: 0;
}
img {
height: 100px;
width: 75px;
border-radius: 5px;
}
.r {
display: flex;
flex-direction: column;
margin-left: 10px;
.FirstField {
flex-grow: 1;
}
p .discounts {
margin-right: 10px;
color: #ff7452;
}
p s {
color: #ccc;
}
}
}
}
</style>