一、 起源
我平时喜欢刷刷知乎,看看每日推送的精选日报。但在手机上必须下载知乎日报app, 在PC端看网页版的知乎日报体验并不怎么好。找了一圈也没有发现移动端的网页版知乎日报。于是乎萌生出模仿“知乎日报”app做一个移动端项目的想法,说干就干。
二、 技术栈
本项目采用Vue3实现,搭配有赞前端团队开源的一套轻量、可靠的移动端组件库Vant3。
vue3+vue-router+axios+vant3
三、 项目演示&数据来源
- 数据来源参考: 日报api
- 项目在线演示: 知乎日报
- 项目仓库: 如果觉得不错求给个Start!
因为目前没有找到登陆相关的api,因此需要登陆后才能进行的互动操作,如点赞、评论、收藏功能暂未实现。
四、 项目实现
项目目录结构:
1. 项目准备
1.1 数据接口准备
安装axios用于发送请求获取数据
npm install axios
在utils文件夹下创建request.js,用于封装axios
import ajax from 'axios'
const axios = ajax.create({
timeout: 10000, // 超时时间
})
export default ({ url, method = 'GET', params = {}, data = {}, headers = {} }) => {
return axios({
url,
method,
params, // 即将与请求一起发送的 URL 参数
data, // 发送给服务端的数据
headers
})
}
在api文件夹下创建index.js,统一管理请求接口
import request from "../utils/request";
// 获取轮播的消息(六条)
export const getLatestNewsAPI = () => request({
url: '/api/news/latest'
})
// 获取回答详情
export const getAnswerDetailAPI =(id)=>request({
url: `/api/news/${id}`
})
// 获取热门回答列表
export const getHotAnswerAPI = ()=>request({
url: '/api/news/hot'
})
// 获取以往的回答
export const getBeforeAnswerAPI= (time)=>request({
url: `/api/news/before/${time}`
})
// 获取回答的额外信息(点赞、评论数)
export const getExtraInfoAPI = (id)=>request({
url: `/api/story-extra/${id}`
})
// 获取长评论
export const getLongCommentAPI = (id)=>request({
url: `/api/story/${id}/long-comments`
})
// 获取短评论
export const getShortCommentAPI = (id)=>request({
url: `/api/story/${id}/short-comments`
})
// 获取所有的合集
export const getAllSectionAPI = ()=>request({
url: '/api/sections'
})
// 获取某个合集的详情列表
export const getSectionList = (id)=>request({
url: `/api/section/${id}`
})
// 获取往前的合集
export const getSectionBefore = (id,timestamp)=>request({
url: `/api/section/${id}/before/${timestamp}`
})
1.2 Vant组件库的使用
在项目中引入Vant可以直接参考 官方文档 , 这里就不再赘述。
1.3 开发环境下的代理配置
在根目录下创建vue.config.js文件,在文件中添加如下的配置用于开启代理:
let path = require('path')
module.exports = {
// 开启代理服务器
publicPath: './',
devServer: {
// 让代理服务器将请求转发到http://news-at.zhihu.com/api/4
proxy: {
'/api': {
target: 'http://news-at.zhihu.com/api/4',
pathRewrite: {
'^/api': ''
}, // 重写请求路径,因为带了前缀,还需要再把前缀去掉再转发到服务器
changeOrigin: true //用于控制请求头中host值
}
}
}
}
2. 首页部分实现
首页实现的效果图:
首页主要分为: 头部区域、轮播区域和回答列表区域。
头部区域用于显示欢迎词以及日期时间, 时间格式处理使用的day.js。在utils文件夹下创建time专门用于处理时间格式:
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'
import relativeTime from 'dayjs/plugin/relativeTime'
dayjs.extend(relativeTime)
dayjs.locale('zh-cn')
const month = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二']
// 获取当前的日期(天)
export const getDay = () => {
return dayjs().date()
}
// 返回当前的月份
export const getMonth = () => {
return `${month[dayjs().month()]}月`
}
// 根据当前的时间返回问候语
export const getTimeNow = () => {
const now = dayjs().hour()
if (now < 5) {
return '夜深人静'
} else if (now < 8) {
return '早上好'
} else if (now < 12) {
return '上午好'
} else if (now < 13) {
return '中午好'
} else if (now < 19) {
return '下午好'
} else {
return '晚上好'
}
}
// 返回当前时间的年月日: YYYYMMDD
export const getTimeFormat = () => {
return dayjs().format('YYYYMMDD')
}
// 返回当前时间的月日: X月X日
export const getMonthAndDay = (date = '') => {
const day =parseInt(date.slice(6))
const month =parseInt(date.slice(4, 6))
return `${month}月${day}日`
}
// 减去一天的时间
export const getSubtractTime = (days = 1) => {
return dayjs().subtract(`${days}`, 'day').format('YYYYMMDD')
}
// 距离现在多久
export const getFromNow = (time)=>{
return dayjs(time*1000).fromNow()
}
2.轮播切换:
轮播使用的是Vant的轮播组件,轮播时间间隔为3s。
// 获取轮播数据
onActivated(async () => {
console.log('home actived')
// 获取首页轮播
const res = await getLatestNewsAPI()
const { date, stories, top_stories } = res.data
state.topAnswer.push(...top_stories) // 6条轮播数据
state.newAnswerList.push({ // 回答列表的前6条回答和轮播是一样的
date,
stories,
})
})
3.回答列表以及滚动加载更多:
首页初始化时会展示当天推荐的回答,这里使用的vant的list列表组件。
例如今天是2022年5月20日,查询 2 月 20 日的消息,请求的时间参数应为 20220521,需要在当前时间的基础上再+1。当滚动条距离底部30px会去请求前一天的回答。
// 在home组件中定义加载更多的回答方法
const getMoreAnswer = async (num) => {
const day = getSubtractTime(num) // 处理时间参数,当前日期 -1
const res = await getBeforeAnswerAPI(day)
const { date, stories } = res.data
state.newAnswerList.push({
date,
stories,
})
// 根据时间先后进行排序,防止乱序
state.newAnswerList.sort((a, b) => {
return b.date - a.date
})
}
// 回答列表组件中通过inject接收父组件getMoreAnswer方法后调用
// 滚动时获取更多的回答
const onLoad = () => {
// 开始加载
state.loading = true
getMoreAnswer(num)
// 结束加载
state.loading = false
// 往后的多少天
num++
}
4.回答详情页
5. 回答评论
总结
调用知乎api的跨域问题,这里是使用vue-cli配置代理
devServer: {
// 让代理服务器将请求转发到http://news-at.zhihu.com/api/4
proxy: {
'/api': {
target: 'http://news-at.zhihu.com/api/4',
pathRewrite: {
'^/api': ''
}, // 重写请求路径,因为带了前缀,还需要再把前缀去掉再转发到服务器
changeOrigin: true //用于控制请求头中host值
}
}
},
本项目主要是本人在学习vue3过程中用于巩固知识点,因此难免会有许多的疏漏和不完善之处。