# vant_test
**Repository Path**: yanjiushen/vant_test
## Basic Information
- **Project Name**: vant_test
- **Description**: 传智博学谷-Vue3-Vant移动开发的新特性
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 4
- **Forks**: 1
- **Created**: 2021-12-19
- **Last Updated**: 2024-06-06
## Categories & Tags
**Categories**: Uncategorized
**Tags**: Vue, Vant, 项目实战
## README
# 项目搭建步骤
## 1. 封装请求模块
1. `npm install axios`
2. 新建`utils/request.js`
1. ```js
//导入axios
import axios from 'axios'
```
2. ```js
//创建axios实例对象
const service = axios.create({
baseURL: 'http://localhost:3000',
timeout: 3000
})
```
3. ```js
//封装并暴露post方法
export function post (url, params) {
return service.request({
method: 'post',
url,
params
})
}
```
4. ```js
export default service
```
## 2. 创建登录路由
1. `npm install vue-router`
2. 新建`router/index.js`
1. ```js
//创建路由集合
const routes = [
{
path: '/login',
name: 'Login',
component: () => import('../views/login/index')
}
]
```
2. ```js
//创建路由器
const router = new VueRouter({
routes
})
```
3. ```js
//暴露路由器
export default router
```
3. `main.js`中引入
1. ```js
import VueRouter from 'vue-router'
import router from './router/index'
```
2. ```Vue.use(VueRouter)```
3. ```js
new Vue({
render: h => h(App),
router
}).$mount('#app')
```
## 3. 处理用户Token
1. 创建`store/modules/user.js`
```js
const TOKEN_KEY = "TOUTIAO_USER"
const state = {
user: JSON.parse(localStorage.getItem(TOKEN_KEY)) //初始化时取值,防止刷新浏览器时清空数据
}
//在操作数据之前处理一些逻辑
const actions = {
}
//直接操作数据
const mutations = {
setUser(state, data) {
state.user = data
localStorage.setItem(TOKEN_KEY, JSON.stringify(state.user))
}
}
export default {
namespaced: true, //开启命名空间,模块化引入
state,
actions,
mutations
}
```
2. 创建`store/index.js`
```js
import Vue from 'vue'
import Vuex from 'vuex'
import userStore from './modules/user'
/**
* 不能在main.js中注册vuex
* 因为import userStore 在use之前,而userStore中使用了vuex
* 相当于在注册之前使用vuex
*/
Vue.use(Vuex)
export default new Vuex.Store({
modules:{
userStore
}
})
```
3. `main.js`中引入
```js
import store from '@/store/index'
new Vue({
render: h => h(App),
router,
store
}).$mount('#app')
```
4. 保存数据
```js
//模块化引入:userStore/setUser
this.$store.commit('userStore/setUser', data)
```
5. 读取数据
```js
//模块化引入:state.userStore
this.$store.state.userStore.user
```
## 4. 封装本地存储操作
1. 创建`utils/storage.js`
```js
//存储数据
export const setItem = (key, value) => {
if(typeof value === "object") {
value = JSON.stringify(value)
}
localStorage.setItem(key, value)
}
//读取数据
export const getItem = (key) => {
const data = localStorage.getItem(key)
try {
return JSON.parse(data)
} catch(e) {
return data
}
}
//删除数据
export const removeItem = (key) => {
localStorage.removeItem(key)
}
```
2. 使用
```js
import { setItem, getItem } from '@/utils/storage'
setItem(TOKEN_KEY, state.user)
getItem(TOKEN_KEY)
```
## 5. TabBar处理
1. 新建`layout/index.js`
```html
首页
问答
视频
我的
```
2. 新建`home/index.vue、qa/index.vue、video./index.vue、me/index.vue`
3. `router/index.js`中注册路由
```js
{
path: '/',
name: 'Layout',
component: () => import('@/views/layout/index'),
children: [
{
path: '', //默认路由
name: 'Home',
component: () => import('@/views/home/index')
},
{
path: '/qa',
name: 'Qa',
component: () => import('@/views/qa/index')
},
{
path: '/video',
name: 'Video',
component: () => import('@/views/video/index')
},
{
path: '/me',
name: 'Me',
component: () => import('@/views/me/index')
}
]
}
```
## 6. 未登录布局实现
1. 添加方法`@click="$router.push('/login')"`跳转登录
2. `NavBar`使用插槽添加左侧返回图标
```html
```
`left`:左侧
`name`:图标名称
`size`:图标大小
3. 添加方法`@click="$router.back()"`退回未登录页面
## 7. 已登录布局实现
1. `image`标签
```html
```
`src`:路径
`round`:是否显示为圆形
`fit`:图片填充模式
`cover`:保持宽高缩放图片,使图片的短边能完全显示出来,裁剪长边
## 8. 宫格导航布局
1. 宫格组件
```html
```
`column-num`:列数
`clickable`:点击反馈
## 9. 消息通知与退出登录布局
1. Cell单元格
```html
```
`is-link`:右侧箭头图标
## 10. 处理页面显示状态
1. 获取vuex中的用户信息
```js
computed: {
user() {
return this.$store.state.userStore.user
}
}
```
2. 登录成功跳转
```js
this.$router.push('/my')
```
## 11. 退出登录
1. 清空用户信息
`this.$store.commit('userStore/setUser', null)`
2. `Dialog`组件
```js
Dialog.confirm({
title: '是否确认退出'
}).then(() => {
this.$store.commit('userStore/setUser', null)
}).catch(()=> {
console.log('取消操作')
});
```
`confirm`:选择框
`then`:确认
`catch`:取消
## 12. 展示当前用户信息
1. 封装请求方法
```js
export async function getUserInfo(id) {
const res = await post('/userInfo', {userId: id, token: store.state.userStore.user.data.token})
return res
}
```
2. 获取用户详细信息
```js
async getUserInfo() {
if(this.user) {
const userId = this.user.data.userId
const { data } = await getUserInfo(userId)
this.userInfo = data.data[0]
}
}
```
## 13. 用户头像处理
1. `:src="userInfo.photo"`
2. 配置环境变量
1. 新建文件`.env.development`
2. ```
NODE_DEV=development
VUE_APP_URL='http://localhost:5000'
```
`development`:开发环境
`production`:生产环境
必须以`VUE_APP_`开头
3. 使用方式:`process.env.VUE_APP_URL`
## 14. 优化设置Token
1. 设置请求拦截器
```js
//请求拦截器
service.interceptors.request.use(
function(config) {
const user = store.state.userStore.user
if(user && user.data) {
config.headers.accessToken = user.data.token
}
return config
},
function(err) {
return Promise.reject(err)
}
)
```
## 15. 头部布局实现
1. `NavBar`使用插槽实现
```html
搜索
```
`type`;类型,可选值为 primary info warning danger
`size`:尺寸,可选值为 large small mini
`round`:是否为圆形按钮
`icon`:左侧图标名称或图片链接
## 16. 文章频道列表构建
1. `van-tabs`组件
```html
内容 1
内容 2
内容 3
内容 4
```
`active`:默认标签
## 17. 样式调整
1. 调整`Tabs`样式
```html
内容 1
内容 2
内容 3
内容 4
内容 5
内容 6
```
`v-model`:默认标签
`title-active-color`:标题选中态颜色
`line-width`:底部条宽度
`line-height`:底部条高度
## 18. 频道列表按钮处理
1. `Tabs`使用插槽实现
```html
```
2. 样式
```css
.channels-tabs .hamburger-btn {
/* 固定定位 */
position: fixed;
right: 0;
display: flex;
justify-content: center;
align-items: center;
width: 66px;
height: 32px;
background-color: #fff;
/* 设置透明度 */
opacity: 0.902;
/* 设置图标大小 */
font-size: 23px;
}
.channels-tabs .placeholder {
flex-shrink: 0;
width: 66px;
height: 32px;
}
```
## 19. 获取频道列表数据
1. 封装请求方法
```js
// 获取用户频道列表数据
export async function getUserChannels() {
const res = await post('/channels', {})
return res
}
```
2. 获取用户频道列表信息
```js
async getUserChannels() {
const { data } = await getUserChannels()
this.channels = data.data
}
```
## 20. 创建文章列表组件
1. 创建`components/article-list.vue`
2. 传递参数
``
3. 接收参数
```
props: {
channel: {
type: Object,
required: true
}
}
```
## 21. List组件基本使用
1. `List`组件
```html
```
`v-model`:是否处于加载状态
`finished`:是否已加载完成
`finished-text`:加载完成后的提示文案
`load`:滚动条与底部距离小于 offset 时触发
2. `data`默认数据
```js
data() {
return {
list: [],
loading: false,
finished: false
}
}
```
`loading`:是否处于加载状态
`finished`:是否已加载完成
3. `methods`示例
```js
onLoad() {
// 异步更新数据
// setTimeout 仅做示例,真实场景中一般为 ajax 请求
// 1. 发送异步请求获取数据
setTimeout(() => {
// 2. 获取到服务端返回的数据,将其填充到list数据
for (let i = 0; i < 10; i++) {
this.list.push(this.list.length + 1);
}
// 3. 本次数据加载完成后,要把加载状态设置为结束,loading设置为false以后,才能够触发下一次的加载更多的操作
// 加载状态结束
this.loading = false;
// 数据全部加载完成
// 4. 当数据全部加载完成后,把finished设置为true
if (this.list.length >= 40) {
this.finished = true;
}
}, 1000);
}
```
`onLoad`:滚动条与底部距离小于 offset 时触发
`loading`:是否处于加载状态
`finished`:是否已加载完成
## 22. 获取频道列表数据
1. 封装请求方法
```js
// 获取文章列表
export async function getArticles(data) {
const res = await post('/articles', data)
return res
}
```
2. `methods`
```js
//请求参数
const params = {
channel_id: id,
pageSize: this.pageSize,
pageNum: this.pageNum
}
// 1. 发送异步请求获取数据
const { data } = await getArticles(params)
if(data.status) {
// 2. 获取到服务端返回的数据,将其填充到channels
this.channels.push(...data.data)
// 3. 本次数据加载完成后,要把加载状态设置为结束,loading设置为false以后,才能够触发下一次的加载更多的操作
this.loading = false
this.pageNum++
} else {
// 4. 当数据全部加载完成后,把finished设置为true
this.finished = true
}
```
## 23. 请求失败的处理
1. `List`组件
```js
:error.sync="error"
error-text="请求失败,点击重新加载"
data:
error: false
methods:
} catch (error) {
this.error = true // 展示错误信息
this.loading = false // 结束本次加载
}
```
`error`:是否加载失败,加载失败后点击错误提示可以重新触发load事件,必须使用sync修饰符
`error-text`:加载失败后的提示文案
## 24. 下拉刷新效果
1. `PullRefresh`组件
```html
```
`v-model`:是否处于加载中状态
`refresh`:下拉刷新时触发
`success-text`:刷新成功提示文案
`success-duration`:刷新成功提示展示时长(ms)
数据加载完成或失败将`isLoading`设置为`false`结束下拉状态
## 25. 固定头部区域
1. 固定`NavBar`组件
```html
```
`fixed`:是否固定在顶部
2. 固定`Tabs`组件
```css
.channels-tabs .van-tabs__wrap {
position: fixed;
top: 46px;
left: 0;
right: 0;
z-index: 1;
}
```
`position`:固定定位
`top`:距顶部距离
`left、right`:不设置无法左右滑动
`z-index`:设置层级,不设置会盖在list下面
3. 修改`List`组件样式
```css
.article-list {
margin-top: 80px;
}
```
## 26. 记住列表滚动位置
1. 添加样式
```css
.article-list { //最外层div样式
height: 530px;
overflow-y: auto;
}
```
`height`:设置页面高度值
`overflow-y`:设置溢出滚动
## 27. 文章列表组件创建
1. 创建文件`src/components/article-item/index.vue`
2. 使用`Cell`组件
```html
```
## 28. 展示列表项内容
1. 使用`Cell`组件
```html
{{ article.title }}
{{ article.aut_name }}
{{ article.comm_count }}
{{ article.pubdate }}
```
`title`:自定义左侧 title 的内容
`van-multi-ellipsis--l2`:多显示两行的文字,多余的内容会被省略
`label`:自定义标题下方 label 的内容
`right-icon`:自定义右侧按钮,默认为arrow
2. 案例
```js
mounted() {
this.isLoading = true //默认展示下拉动作,实际加载数据由onload执行
},
methods: {
onLoad() { // list默认执行
this.error = false
this.getListAll(this.channel.id)
},
onRefresh() { //下拉页面时执行
this.error = false //清除加载失败提示
this.channels = []
this.pageNum = 1
this.getListAll(this.channel.id)
},
async getListAll(id){
try {
//请求参数
const params = {
channel_id: id,
pageSize: this.pageSize,
pageNum: this.pageNum
}
// 1. 发送异步请求获取数据
const { data } = await getArticles(params)
if(data.status) {
// 2. 获取到服务端返回的数据,将其填充到channels
this.channels.push(...data.data)
// 3. 本次数据加载完成后,要把加载状态设置为结束,loading设置为false以后,才能够触发下一次的加载更多的操作
this.pageNum++
this.loading = false // 重置加载状态,才能够触发下一次加载
this.isLoading = false // 重置下拉刷新状态
this.finished = false // 第一次通过上划将列表加载完毕后finished为true,此时通过下拉刷新无法加载第二页
this.refreshText = '刷新成功'
} else {
// 4. 当数据全部加载完成后,把finished设置为true
this.loading = false // 重置上划加载状态,否则会一直显示加载中
this.finished = true // 数据全部加载后,把finished设置为true
this.isLoading = false // 重置下拉刷新状态,否则会一直显示加载中
}
} catch (error) {
this.refreshText = "请求失败"
this.isLoading = false // 重置上划加载状态,否则会一直显示加载中
this.error = true //显示失败提示
this.loading = false // 重置上划加载状态,否则会一直显示加载中
}
}
}
```
## 29. 日期时间的处理
1. 安装`dayjs`
```npm install dayjs```
2. 新建文件`utils/day.js`
```js
import dayjs from 'dayjs' // 导入dayjs库
import 'dayjs/locale/zh-cn' // 导入中文语言包
import relativeTime from 'dayjs/plugin/relativeTime' //导入相对时间插件
dayjs.extend(relativeTime) // 使用相对时间插件
dayjs.locale('zh-cn') // 使用中文语言包
export default dayjs // 默认暴露
```
3. 导入文件
```import dayjs from '@/utils/day.js'```
4. 添加计算属性使用dayjs
```js
computed: {
pubdate() {
return dayjs().to(dayjs(this.article.pubdate))
}
}
```
`dayjs()`:获取当前时间
`to`:进行比较获取相对时间
`dayjs(this.article.pubdate)`:解析给定字符串
## 30. 弹出层组件应用
1. `Popup`组件
```html
```
`v-model`:是否显示弹出层
`closeable`:是否显示关闭图标
`close-icon-positio`:关闭图标位置,可选值为top-left bottom-left bottom-right 默认top-right
`position`:弹出位置,可选值为 top bottom right lef,默认center
`height`:弹出层高度
## 31. 频道编辑组件
1. 创建组件`channel-exit.vue`
2. 使用`Cell`组件插槽实现布局
```html
我的频道
编辑
```
`cell`:单元格
`#title`:自定义左侧 title 的内容
`button`:按钮
`type`:类型,可选值为 primary info warning danger
`size`:尺寸,可选值为 large small mini
`plain`:是否为朴素按钮
`round`:是否为圆形按钮
`grid`:宫格
`gutter`:格子之间的间距,默认单位为px
3. 频道编辑样式调整
```html
```
```css
.channel-edit .my-grid .van-icon-clear {
position: absolute; /* 绝对定位 */
right: -5px;
top: -5px;
font-size: 17px;
color: #cacaca;
z-index: 2;
}
```
4. 展示我的频道数据
```
props: {
myChannels: {
type: Array,
required: true,
},
},
```
5. 处理激活频道高亮
```
{{
channel.name
}}
.active {
color: red;
}
```
6. 展示推荐频道
```js
channels () {
const channels = []
// 遍历所有频道
this.allChannels.forEach(channel => {
// 查询所有频道中的子项在我的频道中是否出现
const result = this.myChannels.find(myChannel => {
// 返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。
return channel.id === myChannel.id
})
if (!result) {
channels.push(channel)
}
})
return channels
}
```
7. 切换频道
```js
this.$emit('updata-active', channel.id, false)
```
删除频道
```js
this.myChannels.splice(index, 1)
```
## 32.搜索功能
1. 创建组件`SearchHistory`,`SearchSuggestion`,`SearchResult`
- `SearchHistory`搜索历史:
```html
全部删除
取消
```
默认插槽:右侧内容
- `SearchSuggestion`联想建议:
```html
```
`icon`:左侧图标
- `SearchResult`搜索结果:
```html
```
2. 显示判断逻辑
```html
```
## 33.联想建议
1. 添加监视
```js
watch: {
searchText: {
immediate: true, //加载后立即执行监视
async handler (newValue) {
this.suggestion = []
const params = {
title: newValue
}
// 查询联想建议
const { data } = await getSuggestion(params)
this.suggestion.push(...data.data)
}
}
}
```
2. 防抖处理
```js
watch: {
searchText: {
immediate: true, //加载后立即执行监视
handler (newValue) {
this.loadSuccestion(newValue)
}
}
},
loadSuccestion: debounce(async function (newValue) {
this.suggestion = []
const params = {
title: newValue
}
// 查询联想建议
const { data } = await getSuggestion(params)
this.suggestion.push(...data.data)
}, 3000)
```
`debounce`:间隔一段时间后再执行后面的函数
3. 关键字高亮
```html
```
```js
highLight (title) {
const hightStr = `${this.searchText}`
const reg = new RegExp(this.searchText, "gi") // 匹配this.searchText
return title.replace(reg, hightStr)
}
```
## 34.实现图片预览效果
1. 图片预览
```html
```
```js
setTimeout(() => {
// 获取所有img标签
const imgs = this.$refs.articleContent.querySelectorAll('img')
const images = []
imgs.forEach((img, index) => {
images.push(img.src)
// 添加点击事件
img.onclick = () => {
ImagePreview({
images, // 图片列表
startPosition: index, // 初始位置
loseable: true, // 展示关闭按钮
showIndicators: true // 是否显示轮播指示器
})
}
})
}, 0)
```