• 第一章:项目起步
  • #第二章:项目架构">#第二章:项目架构
    • #01-引入vant">#01-引入vant
    • #02-适配单位">#02-适配单位
    • #04-频道-渲染可选频道">#04-频道-渲染可选频道
    • #05-频道-点击进入频道">#05-频道-点击进入频道
    • #06-频道-支持本地操作">#06-频道-支持本地操作
    • #07-频道-添加">#07-频道-添加
    • #08-频道-切换编辑">#08-频道-切换编辑
    • #09-频道-删除">#09-频道-删除
  • #第六章:文章详情">#第六章:文章详情
    • #01-文章详情-顶部导航">#01-文章详情-顶部导航
    • #04-文章评论-传送功能">#04-文章评论-传送功能
    • #05-文章评论-点赞">#05-文章评论-点赞
    • #06-文章评论-收藏">#06-文章评论-收藏
    • #07-评论回复-回复弹层">#07-评论回复-回复弹层
    • #08-评论回复-列表渲染">#08-评论回复-列表渲染
    • #09-填写文章评论">#09-填写文章评论
    • #10-填写评论回复">#10-填写评论回复
  • #第八章:个人中心">#第八章:个人中心
    • #01-个人中心-基础布局">#01-个人中心-基础布局
    • #02-个人中心-渲染页面">#02-个人中心-渲染页面
    • #03-编辑资料-页面布局">#03-编辑资料-页面布局
    • #04-编辑资料-回显数据">#04-编辑资料-回显数据
    • #05-编辑资料-退出登录">#05-编辑资料-退出登录
    • #06-编辑资料-修改头像">#06-编辑资料-修改头像
    • #07-编辑资料-修改昵称">#07-编辑资料-修改昵称
    • #08-编辑资料-修改性别">#08-编辑资料-修改性别
    • #09-编辑资料-修改生日">#09-编辑资料-修改生日
    • #10-小智同学-基础布局">#10-小智同学-基础布局
    • #11-小智同学-认识websocket">#11-小智同学-认识websocket
    • #12-小智同学-完成聊天">#12-小智同学-完成聊天
  • #第九章:项目收尾">#第九章:项目收尾
    • #01-刷新token">#01-刷新token
    • #02-打包部署">#02-打包部署
    • #03-项目总结">#03-项目总结

    第一章:项目起步

    01-项目介绍

    目标:了解项目背景,了解项目功能。
    项目背景:

    • 它对标 CSDN 博客园 等竞品,致力成为全球知名的IT技术交流平台,它包含 技术文章,问答内容,视频解答 的专业IT资讯平台,它提供原创,优质,完整内容的专业IT社区。它是 极客园 IT资讯社区。

    项目功能:

    • 极客园-个人端M 是一款移动web应用。
    • 主要功能有:
      • 首页-文章频道,文章列表,更多操作
      • 详情-文章详情,文章评论,评论回复,点赞,收藏,关注
      • 登录-短信登录
      • 个人-信息展示,信息编辑

    项目物料:http://geek.itheima.net
    总结: 我们知道项目的大致功能即可。

    02-使用技术

    目标:了解使用技术
    开发依赖大致如下:

    • 基础环境:nodejs12+ vscode vuecli4.x
    • 配套工具:eslint babel less
    • 使用技术:vue2.6.12 vue-router vue-vuex vant iconfont dayjs socket.io-client postcss-px-to-viewport

    项目中的解决方案:

    • 使用vue-cli创建vue单页应用解决方案
    • 使用vue-router实现前端路由解决方案
    • 使用vue-vuex实现状态管理解决方案
    • 使用vant快速搭建移动界面解决方案
    • 使用json-bigint处理最大安全整数解决方案
    • 使用iconfont实现前端多色字体图标解决方案
    • 使用dayjs处理相对时间计算解决方案
    • 使用soket.io实现即时通讯解决方案
    • 使用postcss-px-to-viewport 实现移动端适配解决方

    总结: 我们大概知道用了那些东西即可。

    #03-创建项目

    目标:知道如何使用vue-cli创建项目
    大致步骤:

    • 在某个目录打开命令行工具输入创建项目的命令
    • 安装项目需求选择具体的工具,然后等待创建吧
    • 最后进入创建好的项目,启动项目即可

    具体如下:

    • 执行创建命令

    vue create geek-client-mobile

    • 选中自定义创建

    image.png

    • 选择Vue版本,依赖Babel降级ES6语法,依赖vue-router,依赖vuex,使用css预处理器,使用代码风格校验。

    image.png

    • 选择vue2.0版本

    image.png

    • 是否使用历史模式API,输入 n

    image.png

    • 选择less这种css预处理器

    image.png

    • 选择 通用语法风格配置

    image.png

    • 语法风格校验的时机,保存代码校验,提交代码校验且自动修复。

    image.png

    • 选择使用不同的配置文件对于所依赖工具

    image.png

    • 是否记录此次操作记录,输入 n

    image.png

    • 最后等待安装即可,安装完毕进入项目目录,执行 npm run serve 即可启动项目。

    image.png
    总结: 我们可以使用vuecli根据自己项目需求创建合适的项目。

    #04-调整目录

    目标:根据项目功能调整下目录结构
    大致步骤:

    • 配置文件解释说明
    • 调整src下目录结构

    落地内容:

    • 根目录和配置文件。都是自动生成的,了解作用即可

    ├─node_modules ├─public ├─src ├─.browserslistrc # 适配浏览器列表 ├─.editorconfig # 提供给编辑器的配置 ├─.eslintrc.js # eslint代码风格配置 ├─.gitignore # git忽略文件配置 ├─.babel.config.js # babelES降级配置 ├─package-lock.json # 包下载版本说明文件 ├─package.json # 项目包说明文件 ├─postcss.config.js # postcss,css预处理器后处理器配置 ├─README.md # 说明MD文件 └─vue.config.js # vue-cli的配置文件

    • src 目录结构如下,仅供参考 (分模块的思维才重要)

    ├─api # 接口函数 ├─assets # 项目资源 │ ├─images # 图片 │ └─styles # less代码 ├─components # 全局组件,通用组件 ├─router # 路由 ├─store # 状态 ├─utils # 工具 └─views # 路由组件(页面) ├─article # 文章详情 ├─home # 首页 ├─question # 问答 ├─user # 用户模块 └─video # 视频
    总结: 做好开发前的准备,调整下项目结构。

    #第二章:项目架构

    #01-引入vant

    目的:在项目中引入Vant组件库
    大致步骤:

    • 从官方了解引入Vant的几种方式 引入vant方式(opens new window)
      • 自动按需(推荐)- 打包体积小
      • 手动按需
      • 全部引入
    • 我们采用 全部引入 方式
      • 开发过程中使用方便,一次引入全局使用。
      • 后续做打包优化可以降低打包体积。

    落地代码:

    • 安装

    npm i vant

    • 引入 main.js

    import Vant from ‘vant’ import ‘vant/lib/index.css’ Vue.use(Vant)

    • 测试 App.vue

    按钮
    image.png
    总结: 如果后续不做打包优化 自动按需引入 推荐,但是我们为了开发方便后续也会做打包优化使用 全部引入 方式。

    #02-适配单位

    目标:了解移动端适配方案,实现这些适配方案用到的尺寸单位。
    大致步骤:

    • 回忆下移动端等比例适配的单位,rem 或者 vw+vh
    • 在项目中使用 vw 来演示下适配的过程
    • 总结 vw 单位在做适配的方法

    落地过程:

    • rem适配,vw适配

    // 目标:等比例适配 // 1. iphone6 375px 盒子 100100 // 2. iphone6 plus 414px 盒子 110.4110.4 // rem适配 // 1. iphone6 html===>font-size:37.5px // 2. iphone6 plus html===>font-size:41.4px // 3. height:2.666rem; width:2.666rem; // vw适配 // 1. height: 26.667vw; width: 26.667vw;

    • 演示 vw 效果

    .box { height: 26.66667vw; width: 26.66667vw; } - 总结下方案,设备宽度是 375px 那么 1vw === 3.75px ,需要将px单位换成vw即可。 总结: 知道如果使用vw换算px单位,实现适配效果。但是手动换算效率太低,下一节来讲如果进行自动适配。 ### #03-进行适配 目标:在项目中加入vw的适配方案
    大致步骤: - 安装 postcss-px-to-viewport 插件 - 新建一个 postcss.config.js 的配置文件 - 添加插件配置 参考 浏览器适配(opens new window) 落地代码: - 安装 npm install postcss-px-to-viewport —save-dev - 配置 postcss.config.js module.exports = { plugins: { ‘postcss-px-to-viewport’: { viewportWidth: 375, } } } - 注意,该插件对行内样式无效,建议样式通过类来定义。 - postcss 可以认为是后处理器,对css代码做后续的处理 (转单位,加私有前缀..) 总结: 通过postcss-px-to-viewport插件解决移动端适配。 ### #04-约定路由 目标:约定好项目的路由设计
    大致步骤: - 先了解各个页面布局的基本构成 - 再约定好路径和组件的映射关系 落地规则: | 路径 | 组件 | 功能 | | —- | —- | —- | | / | Home+Tabbar | 首页 | | /question | Question+Tabbar | 问答 | | /video | Video+Tabbar | 视频 | | /user | User+Tabbar | 用户 | | /user/profile | UserProfile | 用户资料 | | /user/chat | UserChat | 小智同学 | | /article | Article | 文章详情 | 总结: 全部采用一级路由来实现,不使用嵌套路由。(方便后续做组件缓存) ### #05-命名视图-概念 目标:在不使用嵌套路由情况下,如何实现共同布局的复用。
    官方话术: 有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。参考地址(opens new window)
    学习步骤: - 什么是命名视图? - router-view组件有name属性,默认是default,可以指定名称。 - 使用场景在哪里? - 一个路由规则,可以通过命名视图,指定多个组件,组件是同级的,而不是嵌套关系。 落地分析: - 嵌套视图: image.png - 命名视图: image.png
    总结: 在不使用嵌套视图情况下,可以使用命名视图来复用公用的布局内容。 ### #06-命名视图-应用 目标:使用命名视图完成项目路由的基本实现。
    大致步骤: - 定义一个tabbar组件 - 定义 首页 问答 视频 用户 文章详情 等组件 - 使用定义路由规则,使用命名视图,组织页面。 落的代码:
    src/components/app-tabbar.vue 底部tab切换组件

    src/views 中 首页 问答 视频 用户 文章详情 等组件





    src/router/index.js 路由规则
    import Vue from ‘vue’ import VueRouter from ‘vue-router’ Vue.use(VueRouter) // 路由懒加载方式 const Tabbar = () => import(‘@/components/app-tabbar.vue’) const Home = () => import(‘@/views/home’) const Question = () => import(‘@/views/question’) const Video = () => import(‘@/views/video’) const User = () => import(‘@/views/user’) const Artcile = () => import(‘@/views/article’) const routes = [ // 路由规则 { path: ‘/‘, components: { default: Home, tabbar: Tabbar } }, { path: ‘/question’, components: { default: Question, tabbar: Tabbar } }, { path: ‘/video’, components: { default: Video, tabbar: Tabbar } }, { path: ‘/user’, components: { default: User, tabbar: Tabbar } }, { path: ‘/article’, component: Artcile } ] const router = new VueRouter({ routes }) export default router
    App.vue 组织视图

    总结: 可以使用命名视图再一层路由视图情况下,复用tabbar组件。 ### #07-实现tab栏 目标:使用vant的tabbar组件完成底部tab栏
    大致步骤: - 先去vant阅读下tabbar组件的模板代码 - 再在app-tabbar.vue组件使用基础用法 - 开启路由功能,指定跳转地址,更改文字。 落的代码:
    src/components/app-tabbar.vue

    总结: van-tabbar的route是开启路由,van-tabbar-item的to属性是指定跳转地址。图标稍后处理。 ### #08-字体图标 目标:掌握如何使用svg的字体图标库iconfont
    大致步骤: - 在iconfont.com上生成字体图标的js文件 - 在public下index.html头部引入该文件 - 使用固定的svg语法来使用图标 落地代码: - 第一步:在public下index.html头部引入该文件 //at.alicdn.com/t/font_2496877_pghtj7rdgrh.js - 第二步:组件中需要加入通用css代码 - 第三步:挑选相应图标并获取类名,应用于页面
    image.png
    总结: 把字体图标库的js文件引入在index.html,使用svg标签通过类名来指定图标。但是每次这样使用较为麻烦,结构比较多,还有样式,封装成组件。 ### #09-图标组件 目的:封装geek-icon组件来使用字体图标
    大致步骤: - 在components下定义geek-icon组件 - 使用div.geek-icon包裹svg格式代码 - 暴露props属性,name来指定图标 落地代码: - 定义src/components/geek-icon.vue - 使用
    总结: 使用geek-icon组件name是图标名称,不需要加上icon。geek-icon的颜色和字体大小可控制图标颜色。
    注意: weixin图标才可以测试改颜色,其他图标没有放开颜色的设置,需要去iconfont上进行设置。 ### #10-Vue插件 目标:掌握定义一个vue插件模块
    大致步骤: - 背景:项目开发过程中会有大量的一些自己写的全局组件,指令,过滤器,原型函数,如果都在main中书写,main的代码就很混乱,不利于维护。 - 插件:可以扩展vue的原有功能在一个独立的js模块中。 - 步骤: - 定义一个js模块 - 导出一个对象 - 对象中有一个install属性指向的是一个函数 - 函数的默认参数是 Vue - 你可以基于Vue做扩展功能 落地代码: - 定义插件 src/components/index.js import GeekIcon from ‘@/components/geek-icon’ export default { install (Vue) { // 在这里扩展Vue功能 Vue.component(GeekIcon.name, GeekIcon) } } - 使用插件 src/main.js // 导入插件 import Geek from ‘@/components’ // 使用插件 Vue.use(Geek)
    总结: 我们在项目中一般会把 全局组件,过滤器,指令,定义在一个插件模块中。js模块的格式就是一个对象中有install函数即可。 ### #11-改造底部Tab 目标:自定义使用底部tab的图标
    大致步骤: - 需要使用icon插槽自定义图标 - 需要使用icon插槽的props作用域数据切换图标状态 - 需要使用/deep/来覆盖组件内部样式 落地代码:src/components/app-tabbar.vue

    总结: 使用van-tabbar-item的作用域插槽icon可以完成图标自定义和状态切换。
    样式中用到了几个颜色,这几个颜色是将来其他组件也会大量使用的,建议定义为全局变量。 ### #12-Less全局变量 目标:定项目的Less全局变量
    大致步骤: - 注意,如果你将less变量定义在一个less文件中,在使用变量的地方就需要引入这个文件,麻烦。 - 所以,vue-cli考虑到这一点,提供了配置。 参考配置(opens new window) 落地代码: - 添加配置 vue.config.js 修改配置文件需要重启项目 module.exports = { css: { loaderOptions: { less: { // 这里定义不需要加@,使用的时候需要加@ globalVars: { ‘geek-color’: ‘#FC6627’, ‘geek-gray-color’: ‘#F7F8FA’ } } } } } - 使用变量 src/components/app-tabbar.vue .van-tabbar { + background: @geek-gray-color; position: static; } /deep/ .van-tabbar-item—active { + color: @geek-color; + background-color: @geek-gray-color }
    总结: 在vue.config.js中添加配置就可以定义全局需要使用的less变量。 ### #13-Vuex管理状态 目标:使用vuex来管理项目中需要共享的数据模块
    大致步骤: - 定义 user 用户模块,维护用户 token 等信息,且需要同步本地存储。 - 在 store 中使用 user 模块 落地代码: - user模块 src/store/modules/user.js // 用户模块 export default { namespaced: true, state () { return { token: localStorage.getItem(‘geek-store-token’) } }, getters: {}, mutations: { setToken (state, token) { state.token = token localStorage.setItem(‘geek-store-token’, token) } }, actions: {} } - 使用模块 src/store/index.js import Vue from ‘vue’ import Vuex from ‘vuex’ import user from ‘./modules/user’ Vue.use(Vuex) export default new Vuex.Store({ modules: { user } })
    总结: 今后组件间共享的数据由vuex来管理。 ### #14-请求工具-基础 目的:为了能单独维护axios的配置,提取一个请求工具模块。
    大致步骤: - 安装 axios 且导入到文件 src/utils/request.js - 创建一个新的axios实例,配置 baseURL timeout - 导出一个通过新axios实例调用接口的函数,返回值promise 落地代码: src/utils/request.js
    import axios from ‘axios’ // 新axios实例 const instance = axios.create({ baseURL: ‘http://geek.itheima.net/‘, timeout: 5000 }) // 导出一个新axios实例调用接口的函数,返回值promise export default ({ url, method = ‘get’, params, data, headers }) => { const promise = instance({ url, method, params, data, headers }) return promise }
    总结: 将来通过导出的函数调接口即可。配置可以统一在这个文件进行维护。 ### #15-请求工具-赋能 目的:让请求工具能够处理,携带token问题,token失效问题。
    大致步骤: - 请求头携带token保持登录状态 - 处理token失效问题,跳转登录页面,需要把在哪个页面失效的页面地址传递给登录页面,登录后回跳。 落地步骤: - 请求头携带token保持登录状态 导入 vuex 仓库,然后在 请求拦截器 获取token信息,有就修改 config 的配置信息即可。
    import store from ‘@/store’
    // 请求拦截器 instance.interceptors.request.use(config => { const token = store.state.user.token if (token) config.headers.Authorization = Bearer ${token} return config }, err => Promise.reject(err)) - 处理token失效问题,跳转登录页面,需要把在哪个页面失效的页面地址传递给登录页面,登录后回跳。 import router from ‘@/router’
    // 响应拦截器 instance.interceptors.response.use(res => res, err => { if (err.response && err.response.status === 401) { // token失效 store.commit(‘user/setToken’, ‘’) router.push(‘/login?returnUrl=’ + encodeURIComponent(router.currentRoute.fullPath)) } return Promise.reject(err) })
    总结: 我们可以在request.js 配置扩展 axios 功能,处理项目需要的业务。 ### #16-await异常处理 目的:解决 async await 发请求,处理异常时候多出 try catch 代码块问题。
    大致步骤: - 使用封装好的请求函数调用接口处理成功和异常,总结 try catch 处理异常的缺点:需要额外的代码。 - 使用 await-to-js 处理promise请求,处理成功和异常情况。 - 优化 request.js 的请求函数,让返回的promise支持 await-to-js 写法。 落地代码: 1. 演示 request.js 调用接口处理成功和异常 import request from ‘@/utils/request’ export default { name: ‘HomePage’, async created () { try { const res = await request({ url: ‘v1_0/channels2’ }) console.log(‘成功’, res.data) } catch (e) { console.log(‘失败’, e.message) } } }
    我们发现:async await 调用接口,处理异常需要额外加 try catch 的代码块,代码层次增多,不好阅读。 1. 使用 await-to-js 配合 request.js 调用接口处理成功和异常 import request from ‘@/utils/request’ import to from ‘await-to-js’ export default { name: ‘HomePage’, async created () { const [err, res] = await to(request({ url: ‘v1_0/channels’ })) if (err) console.log(‘失败’, err.message) else console.log(‘成功’, res.data) } }
    我们可以将 to 在request.js 工具中就使用,以后就不用每次引入 await-to-js,调用to函数。 - export default ({ url, method = ‘get’, params, data, headers }) => { const promise = instance({ url, method, params, data, headers }) + return to(promise) } import request from ‘@/utils/request’ export default { name: ‘HomePage’, async created () { const [err, res] = await request({ url: ‘v1_0/channels’ }) if (err) console.log(‘失败’, err.message) else console.log(‘成功’, res.data) } } 1. 优化 request.js 代码 - 安装 npm i await-to-js - 导入 import to from ‘await-to-js’ - 使用 总结: 使用await-to-js之后,可以通过是否存在 err 判断是否出现异常。不用使用try catch 增加代码。 ### #17-接口API函数 目的:知道提取API函数的目的,掌握这种套路。
    发现问题: - 问题:调用一个接口,需要传入:请求地址,请求方式,请求参数,等等。如果这个接口需要在多个组件调用,那么相同的代码需要写多次。 - 解决:将接口的调用再次封装成为一个函数,只暴露请求参数。这样可以提高代码复用。 大致步骤: - 根据接口文档封装一个接口函数 - 再需要数据的组件导入调用函数 落的代码:(在首页获取频道信息) - 定义API函数 src/api/channel.js import request from ‘@/utils/request’ / 获取所有频道 / export const getAllChannels = () => { return request({ url: ‘v1_0/channels’ }) } - 调用API函数 src/views/home/index.vue import { getAllChannels } from ‘@/api/channel’ export default { name: ‘HomePage’, async created () { // 不使用err不写err即可,但是,号需要写 const [, res] = await getAllChannels() console.log(res.data) } }
    总结: 以后调用接口,先写API函数,然后再调用API函数获取数据。 ## #第三章:登录模块 ### #01-登录-导航守卫 目的:访问用户相关页面都需要进行登录,做一个导航守卫进行登录拦截。
    大致步骤: - 约定好将来用户相关的页面 路由地址以 /user 开头 - 在 src/router/index.js 添加前置导航守卫 - 通过vuex中是否有 token 数据来判断是否登录,进行登录拦截。 落的代码: src/router/index.js
    import store from ‘@/store’
    // 导航守卫 router.beforeEach((to, from, next) => { const token = store.state.user.token // 没登录却访问user下的路由 if (!token && to.path.startsWith(‘/user’)) { return next(‘/login?returnUrl=’ + encodeURIComponent(to.fullPath)) } // 其他情况放行 next() })
    总结: 使用前置导航守卫拦截未登录却访问用户页面的情况。 ### #02-登录-组件布局 目的:完成登录页面基础布局,路由规则配置。
    大致步骤: - 定义登录组件 - 配置路由规则 - 完成基础布局 落的代码: - 登录组件 src/views/login/index.vue - 路由规则 src/router/index.js +const Login = () => import(‘@/views/login’) const routes = [ // … + { path: ‘/login’, component: Login } ]
    总结: 注意van-form和van-field是表单结构中的form和input他们需要结合使用。 ### #03-登录-表单校验 目标:完成表单项校验和提交时整体校验。
    大致步骤: - 通过文档了解vant校验的套路 - 完成单个表单项的校验 - 完成提交时整体校验 落地代码: - 根据约定vant文档总结如下: 1. 通过van-field组件的rules属性指定校验规则,校验单个表单项 2. 校验规则具体参考:https://vant-contrib.gitee.io/vant/#/zh-CN/form#rule-shu-ju-jie-gou 3. 通过van-form组件提供的validate函数校验全部表单项 - 完成单个表单项校验 data () { return { form: { mobile: ‘’, code: ‘’ }, rules: { mobile: [ { required: true, message: ‘请输入手机号’ }, { pattern: /^1[3-9]\d{9}$/, message: ‘手机号格式不对’ } ], code: [ { required: true, message: ‘请输入手机号’ }, { pattern: /^\d{6}$/, message: ‘验证码是6个数字’ } ] } } },
    - 完成提交时整体校验
    methods: { login () { this.$refs.form.validate().then(() => { console.log(‘校验成功’) }) } }
    总结: 掌握单个表单项验证和整体验证可以完成大部分业务。 ### #04-登录-默认登录 目的:能够通过默认246810短信验证码完成登录
    大致步骤: - 由于短信业务需要接入第三方运营商需要买短信包,所以提供了246810默认的短信验证码。 - 编写登录 API 接口函数 - 使用 手机号 和 默认的短信验证码246810 进行登录 - 成功后存储 token 信息 落地代码: - 编写登录 API 接口函数 src/api/index.js import request from ‘@/utils/request’ /
    登录 @param {String} mobile - 手机号 @param {String} code - 验证码 @returns Promise / export const userLogin = ({ mobile, code }) => { return request({ url: ‘v1_0/authorizations’, method: ‘post’, data: { mobile, code } }) } - 使用 手机号 和 默认的短信验证码 进行登录 src/views/login/index.vue import { userLogin } from ‘@/api/user’
    methods: { async login () { // 校验 await this.$refs.form.validate() // 登录 const [err, res] = await userLogin(this.form) // 失败 if (err) return this.$toast.fail(‘登录失败’) // 成功 console.log(res) } } - 成功后存储 token src/views/login/index.vue
    methods: { async login () { // 校验 await this.$refs.form.validate() // 登录 const [err, res] = await userLogin(this.form) // 失败 if (err) return this.$toast.fail(‘登录失败’) // 成功 + this.$store.commit(‘user/setToken’, res.data.data.token) + this.$router.push(this.$route.query.returnUrl || ‘/‘) } }
    总结: 定义API——>调用API——->得到数据,会是以后的常态。我们存储token是后续会使用的。 ### #05-登录-短信登录 目的:完成发送短信验证码登录功能。
    大致步骤: - 准备 发送验证码 按钮 - 编写发送短信API接口 - 点击 发送验证码 按钮, - 判断是否已经发送, - 校验手机号, - 调用发送短信API, - 成功后开启60秒倒计时,不可再次发送 落地代码: 1. 准备 发送验证码 按钮 src/views/login/index.vue
    .send { font-size: 12px; color: #A5A6AB; } 1. 编写发送短信API接口 src/api/index.js /**
    发送短信验证码 @param {String} mobile - 手机号 @returns Promise / export const sendMessage = (mobile) => { return request({ url: /v1_0/sms/codes/${mobile} }) } 1. 点击 发送验证码 按钮,校验手机号,调用发送短信API,成功后开启60秒倒计时,不可再次发送 src/views/login/index.vue
    // 倒计时秒数 second: 0, // 定时器ID timer: null

    methods: { // 省略…. async send () { // 已发送,不做事 if (this.second > 0) return // 校验 await this.$refs.form.validate(‘mobile’) // 发短信 const [err] = await sendMessage(this.form.mobile) // 失败 if (err) return this.$toast.fail(‘发送失败’) // 成功 倒计时 this.second = 60 if (this.timer) clearInterval(this.timer) this.timer = setInterval(() => { this.second— if (this.second <= 0) clearInterval(this.timer) }, 1000) } }, beforeDestroy () { if (this.timer) clearInterval(this.timer) }
    +总结: 对应定时器,最好在销毁组件前清除定时器。 ## #第四章:首页模块 ### #01-首页-频道展示 目的:完成首页频道TAB展示
    大致步骤: - 使用van-tabs组件完成基础结构 - 放置搜索按钮和频道按钮再tab组件右侧 - 获取频道数据,渲染van-tabs组件 落地代码: - 使用van-tabs组件完成基础结构 src/views/home/index.vue 内容 {{ index }}
    ::v-deep .van-tabs { height: 100%; display: flex; flex-direction: column; .van-tabsline { background: @geek-color; height: 2px; width: 32px; } .van-tab { color: #9EA1AE; } .van-tab—active { font-size: 18px; color: #333; } .van-tabswrap { padding-right: 86px; box-shadow: 0 0 10px rgba(0,0,0,0.1); } .van-tabscontent { flex: 1; overflow: hidden; } .van-tabpane { height: 100%; } } - 放置搜索按钮和频道按钮再van-tabs组件下面,定位到右上角 src/views/home/index.vue

    .home-page { .btn-wrapper { position: absolute; right: 0; top: 0; width: 86px; height: 44px; background: #fff; display: flex; align-items: center; .geek-icon { flex: 1; text-align: center; font-size: 18px; } &::before { content: “”; width: 20px; height: 44px; position: absolute; left: -20px; top: 0; background: linear-gradient(to right, rgba(255,255,255,0), #fff); } } } - 获取频道数据,渲染van-tabs组件 src/api/channel.js
    /**
    获取我的频道(未登录会返回默认的一些频道) / export const getMyChannels = () => { return request({ url: ‘v1_0/user/channels’ }) }
    src/views/home/index.vue
    data () { return { myChannels: [] } }, async created () { // 不使用err不写err即可,但是,号需要写 const [, res] = await getMyChannels() this.myChannels = res.data.data.channels }
    内容 {{ item.id }}
    总结: 完成tab使用,改造样式布局,获取频道数据,渲染即可。 ### #02-首页-文章组件 目的:完成文章列表组件与文章单项组件
    大概步骤: - 准备文章单项组件 - 准备文章列表组件 - 首页使用文章列表组件 - 首页van-tabs样式修改 落的步骤: - 准备文章单项组件 src/views/home/components/article-item.vue 1)无图

    美国强行关闭33家伊朗网站 伊总统办公室警告:不利于伊核谈判

    小兵张嘎 17评论 1天前

    2)三图

    美国强行关闭33家伊朗网站 伊总统办公室警告:不利于伊核谈判

    vue-极客园M端 - 图15 vue-极客园M端 - 图16 vue-极客园M端 - 图17
    小兵张嘎 17评论 1天前

    3)单图,title处加了w66的类名需要注意

    美国强行关闭33家伊朗网站 伊总统办公室警告:不利于伊核谈判

    vue-极客园M端 - 图18
    小兵张嘎 17评论 1天前

    其他代码
    - 准备文章列表组件 src/views/home/components/article-list.vue - 首页使用文章列表组件 src/views/home/index.vue import { getMyChannels } from ‘@/api/channel’ +import ArticleList from ‘./components/article-list.vue’ export default { name: ‘HomePage’, + components: { ArticleList },
    + - 首页van-tabs样式修改 src/views/home/index.vue ::v-deep .van-tabs { + height: 100%; + display: flex; + flex-direction: column; .van-tabsline { background: @geek-color; height: 2px; width: 32px; } .van-tab { color: #9EA1AE; } .van-tab—active { font-size: 18px; color: #333; } .van-tabswrap { padding-right: 86px; } + .van-tabscontent { + flex: 1; + overflow: hidden; + } + .van-tabpane { + height: 100%; + } }
    总结: 注意下单图无图三图结构情况,注意下flex布局占满剩余高度产生滚动条。 ### #03-首页-上拉加载 目的:实现上拉加载效果
    大致步骤: - 使用van-list组件,知道需要使用属性和事件作用 - 模拟上拉加载效果 落地代码:src/views/home/components/article-list.vue - 使用van-list组件,知道需要使用属性和事件作用
    v-model=”loading” 是加载中状态,控制显示加载中效果,:finished=”finished” 是显示是否完全加载完毕数据,
    @load=”onLoad()” 是上拉加载后事件,当列表最底部进入可视区(能看见列表末尾)就会触发上拉加载事件。 - 模拟上拉加载效果
    data () { return { // 正在加载 loading: false, // 数据全部加载完毕 finished: false, // 文章列表 + articles: [] } },
    methods: { onLoad () { // 模拟请求 setTimeout(() => { // 加载完毕 this.loading = false // 判断还有没有数据 if (this.articles.length < 40) { // 设置数据,追加 const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] this.articles.push(…data) } else { // 设置数据全部加载完毕 this.finished = true } }, 1000) } }
    总结: 知道van-list的属性和事件,知道加载数据的套路。 ### #04-首页-下拉刷新 目的:实现下拉刷新效果
    大致步骤:src/views/home/components/article-list.vue - 使用 van-pull-refresh 组件,知道需要使用的属性和事件作用 - 模拟下拉刷新效果 落地代码: - 使用 van-pull-refresh 组件,知道需要使用的属性和事件作用 + +
    data () { return { // 正在加载 loading: false, // 数据全部加载完毕 finished: false, // 文章列表 articles: [], // 正在刷新 + refreshing: false } },
    methods: { + onRefresh () { + console.log(‘下拉刷新’) + },
    v-model=”refreshing” 是刷新中状态,控制显示刷新中效果,loading-text” 是设置刷新中的提示文字,
    @refresh=”onRefresh()” 是下拉刷新后事件,当用户向下拖动到一定距离后会触发下拉刷新事件。 - 模拟下拉刷新效果 onRefresh () { // 模拟请求 setTimeout(() => { // 刷新完毕 this.refreshing = false // 设置数据全部加载完毕为:未加载完 this.finished = false // 设置数据,替换 const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] this.articles = data }, 1000) },
    需要注意下的是,刷新完毕后需要 将 finished 改成 false 代表还可以继续上拉加载数据,因为重置了列表数据。
    总结: 知道 van-pull-refresh 组件 v-model 的作用是控制刷新中状态,@refresh 是监听下拉刷新事件。然后由于重置了数据,需要将列表改成还有更多数据状态。 ### #05-首页-接入数据 目的:调用接口获取文章列表真实的数据,完成数据渲染。
    大致步骤: - 每个频道对应自己的列表,需要传入频道ID给文章列表组件。 - 编写获取文章列表的API接口函数 - 再上拉加载和下拉加载的位置接入真实的接口 - 渲染单项文章组件 落地代码: - 每个频道对应自己的列表,需要传入频道ID给文章列表组件。 src/views/home/index.vue
    +
    src/views/home/components/article-list.vue
    props: { channelId: { type: Number, default: 0 } }, - 编写获取文章列表的API接口函数 src/api/article.js
    import request from ‘@/utils/request’ /**
    根据频道获取频道 @param {Number} channelId - 频道ID @param {Number} timestamp - 时间戳 @returns / export const getArticlesByChannel = (channelId, timestamp) => { return request({ url: ‘/v1_0/articles’, method: ‘get’, params: { channel_id: channelId, timestamp } }) } - 在上拉加载和下拉加载的位置接入真实的接口 src/views/home/components/article-list.vue
    import { getArticlesByChannel } from ‘@/api/article’
    data () { return { // 正在加载 loading: false, // 数据全部加载完毕 finished: false, // 文章列表 articles: [], // 正在刷新 refreshing: false, // 时间戳 + timestamp: Date.now() } },
    async onRefresh () { // 下拉刷新 // 1. 重置时间戳:回到第一页 // 2. 获取数据 // 3. 重置全部数据加载完成:可以再次加载更多 // 4. 替换当前列表数据戳 // 5. 记录下一次请求的时间 // 6. 结束加刷新操作 this.timestamp = Date.now() const [, res] = await getArticlesByChannel(this.channelId, this.timestamp) this.finished = false this.articles = res.data.data.results this.timestamp = res.data.data.pre_timestamp this.refreshing = false }, async onLoad () { // 上拉加载 // 1. 获取数据 // 2. 判断下一页是否还有数据:当前时间戳未空,也就是没有更多了 // 2.1 如果有:记录当前的数据时间戳,下一次请求使用 // 2.2 如没有:设置没有更多数据 // 3. 当前列表追加数据 // 4. 结束上拉加载操作 const [err, res] = await getArticlesByChannel(this.channelId, this.timestamp) if (err) return this.$toast.fail(‘加载失败’) // 加载失败 if (res.data.data.pre_timestamp) { this.timestamp = res.data.data.pre_timestamp } else { this.finished = true } this.articles.push(…res.data.data.results) this.loading = false } } - 渲染单项文章组件 src/views/home/components/article-list.vue 传入文章信息

    src/views/home/components/article-item.vue 使用文章信息
    props: { article: { type: Object, default: () => ({}) } }

    总结: 使用传入的频道ID获取数据,在上拉和下拉后等所有的逻辑完成后,结束加载或刷新状态。 ### #06-首页-处理时间 目的:把文章发布时间转换为相对时间,例如:2个月内,1分钟内。
    大致步骤: - 定义一个过滤器,使用过滤器证明可用。 - 然后在过滤中实现转换逻辑,采用 dayjs 时间库处理。 - 安装导入 dayjs - 使用 relativeTime 模块转化相对时间 - 使用 locale 默认语言本地化 落地代码: - 定义一个过滤器,使用过滤器证明可用。 src/components/index.js
    export default { install (Vue) { // 在这里扩展Vue功能 Vue.component(GeekIcon.name, GeekIcon) + // 全局注册过滤器 + Vue.filter(‘relativeTime’, (value) => { + return ‘1周内’ + }) } }
    src/views/home/components/article-item.vue
    {{article.pubdate|relativeTime}} - 然后在过滤中实现转换逻辑,采用 dayjs 时间库处理。 src/components/index.js
    1)安装
    npm i dayjs
    2)导入
    import dayjs from ‘dayjs’
    3)使用 相对时间 插件
    import relativeTime from ‘dayjs/plugin/relativeTime’ dayjs.extend(relativeTime)
    4)进行时间转换
    // 全局注册过滤器 Vue.filter(‘relativeTime’, (value) => { + return dayjs(value).toNow() })
    5)本地化,语言
    import ‘dayjs/locale/zh-cn’ dayjs.locale(‘zh-cn’)
    总结: 知道过滤器的注册使用,知道dayjs的基本使用。 ### #07-首页-组件缓存 目的:首页的状态(当前浏览的频道,频道里头文章)需要保持,当你切换到其他页面时。
    大致步骤: - 知道如何通过组件缓存来保存组件状态 - 知道如何指定某些组件进行缓存 - 知道如何判断组件确实缓存了 落地代码:
    src/App.vue - 使用 keep-alive 来缓存组件
    + +

    那么将来 RouterView 动态展示的组件都会被缓存 - 使用 include 来指定需要被缓存的组件
    属性的值是需要缓存组件的name名称,如果多个组件可以 逗号 分隔 a,b,c - 我们可以通过 devtools 观察组件是否缓存 image.png
    看见 inactive 代表当前组件被缓存,或者切换下组件看看组件数据状态是否保存住,还有组件被缓存了再次进入组件是不会触发 created 钩子的这也可以判断。
    总结: 知道使用keep-alive组件缓存组件,知道使用include来制定被缓存组件。 ### #08-首页-阅读位置 目的:组件缓存只会缓存状态数据,而滚动的位置(阅读位置)没有保持,我们需要完成阅读位置保持。
    大致步骤: - 我们需要知道如何监听:进入组件(激活组件) - 我们需要在阅读列表的时候记录当前滚动位置 - 我们需要在进入组件的时候还原之前滚动位置 落地代码:
    src/views/home/components/article-list.vue - activated 和 deactivated 将会在 keep-alive 树内的所有嵌套组件中触发 参考文档(opens new window) - 我们需要在阅读列表的时候记录当前滚动位置 // 阅读位置 (data中申明) scrollTop: 0

    methods: { // 滚动监听 rememberScroll () { this.scrollTop = this.$refs.ArticleList.scrollTop }, - 我们需要在进入组件的时候还原之前滚动位置 // 激活组件 activated () { this.$refs.ArticleList.scrollTop = this.scrollTop },
    注意: src/views/home/index.vue
    如果当前列表被隐藏,滚动失效,需要设置 animated 有动画的时候,所有列表不会隐藏,激活的时候滚动生效。

    总结: 监听 article-list 的 scroll 事件记录 scrollTop , 然后在组件触发 activated 事件设置 article-list 的 scrollTop 最后注意 van-tabs 加上 animated 属性。
    先定义API函数,组件中调用即可。 - van-tabs 组件实现频道 - van-list 组件实现上拉加载 - van-pull-refresh 组件实现下拉刷新 - dayjs relativeTime模块 定义过滤器 处理时间 - keep-alive vue内置组件做了缓存 - 记录位置,组件激活 activated 还原位置 回顾 promise 知识 - 异步操作有回调函数,如果有嵌套,形成逻辑不是特别清楚的代码,回调地狱 - promise 可以解决回调地狱问题,可以让异步操作使用then串联执行 // promise ajax(‘url1’).then(data=>{ return ajax(‘url2’) }).then(data=>{ }) - async await 可以让promise的代码更加简洁 const getData = async () = >{ await ajax(‘url1’) await ajax(‘url2’) } ## #第五章:频道管理 ### #01-频道-组件准备 目的:定义频道管理组件,准备一个弹出层组件,在首页组件使用频道管理组件。
    大致步骤: - 了解 van-popup 组件基本用法 - 定义频道管理组件,使用van-popup组件,修改样式 - 在首页组件使用频道管理组件 - 实现:value来控制van-popup的显示 - 实现@input来控制van-popup的隐藏 - 简写成v-model 落地代码: - 了解 van-popup 组件基本用法 参考文档(opens new window) 1. v-model 或者 value 控制 弹出层显示和隐藏,值类型 true false 2. position 弹出的位置 left top bottom right 3. closeable 是否显示关闭图标 4. 事件 click-close-icon 点击关闭按钮的事件 5. 弹层大小,图标大小,使用样式覆盖控制 - 定义频道管理组件,使用van-popup组件,修改样式 定义组件 src/views/home/components/article-channel.vue

    使用组件 src/views/home/index.vue
    +import ArticleChannel from ‘./components/article-channel.vue’ export default { name: ‘HomePage’, + components: { ArticleList, ArticleChannel },
    +
    +
    data () { return { myChannels: [], + // 控制频道组件显示隐藏 + showChannel: false } },
    总结: 封装频道管理组件,实现v-model指令,控制van-popup显示隐藏。 ### #02-频道-组件布局 目的:完成频道组件的基础布局,了解需要动态交互的点。
    大致步骤: - 准备HTML结构 - 准备CSS样式 - 了解动态修改的类名 落地代码:src/views/home/components/article-channel.vue - 准备HTML结构 - 准备CSS样式 .article-channel { margin-top: 44px; .head { padding: 0 16px; display: flex; justify-content: space-between; justify-items: center; padding-bottom: 12px; h3 { font-size: 16px; color: #333; margin: 0; small { font-size: 12px; color: #999; margin-left: 10px; } } .edit { float: right; height: 22px; width: 52px; line-height: 22px; text-align: center; color: #DE644B; border-radius: 11px; border: 1px solid #DE644B; font-size: 12px; &.active { color: #fff; background: #DE644B; } } } .body { padding: 0 6px 0 16px; a { display: inline-block; padding: 0 8px; font-size:14px; color: #3A3948; background: #F7F8FA; height: 36px; line-height: 36px; min-width: 78px; margin-right: 10px; margin-bottom: 12px; border-radius: 18px; text-align: center; &.active { color: @geek-color; } } } } - 了解动态修改的类名 1. 编辑按钮——点击后——>完成按钮,需要加上active类名 2. 我的频道按钮,如果是当前浏览频道,需要加上active类名
    总结: 准备好布局,知道 active 类名的作用。 ### #03-频道-渲染我的频道 目的:完成我的频道渲染与激活当前浏览频道。
    大致步骤: - 完成我的频道渲染,数据从首页组件传入。 - 完成当前浏览频道激活,首页组件准备当前浏览频道索引,传人数据给频道组件 落地代码: - 完成我的频道渲染,数据从首页组件传入。 src/views/home/index.vue 传入我的频道数据

    src/views/home/components/article-channel.vue 使用我的频道数据
    props: { value: { type: Boolean, default: false }, + myChannels: { + type: Array, + default: () => [] + }

    我的频道点击进入频道

    编辑
    • 完成当前浏览频道激活,首页组件准备当前浏览频道索引,传人数据给频道组件。

    src/views/home/index.vue 浏览频道索引,传给频道组件
    data () { return { myChannels: [], showChannel: false, + activeIndex: 0 } },
    +

    src/views/home/components/article-channel.vue 使用浏览频道索引
    props: { value: { type: Boolean, default: false }, myChannels: { type: Array, default: () => [] }, + activeIndex: { + type: Number, + default: 0 + } }

    我的频道点击进入频道

    编辑

    总结: 我的频道和当前浏览频道索引均在首页组件,准备好之后传递给频道组件,使用即可。

    #04-频道-渲染可选频道

    目的:完成可选频道的渲染
    大致步骤:

    • 知道可选频道数据:就是所有频道除去我的频道剩余频道。
    • 获取所有频道数据
    • 使用计算属性得到可选频道数据
    • 进行数据渲染即可

    落地代码:src/views/home/components/article-channel.vue

    • 获取所有频道数据

      data () { return { allChannels: [] } }, created () { this.getAllChannels() }, methods: { async getAllChannels () { const [, res] = await getAllChannels() this.allChannels = res.data.data.results } }

    • 使用计算属性得到可选频道数据

      computed: { // 可选频道 optionalChannels () { // 在myChannels中找不到的就是可选的 return this.allChannels.filter(item => !this.myChannels.find(c => c.id === item.id)) } },

    • 进行数据渲染即可

      1. <div class="head" style="margin-top:12px"> <h3>频道推荐</h3> </div> <div class="body"> <a href="javascript:;" v-for="item in optionalChannels" :key="item.id">+ {{item.name}}</a> </div> <br />**总结:** 使用计算属性,全部频道-我的频道=可选频道。

      #05-频道-点击进入频道

      目的:频道组件点击频道,关闭频道对话框,首页切换到对应频道。技术点掌握sync的使用。
      大致步骤:

    • 点击频道将当前的点击按钮索引传递给父组件,父组件接收后修改tabs的值即可完成切换

    • 如果传给父组件的事件时 update:属性名称,父组件传值 :属性名称。可以简写 属性名称.sync

    落地代码:

    • 完成点击进入频道功能

    src/views/home/components/article-channel.vue

    // 进入频道 enterChannel (index) { // 关闭对话框 this.$emit(‘input’, false) // 传递频道索引 this.$emit(‘update:activeIndex’, index) }
    src/views/home/index.vue

    落地代码:

    • 按照流程图在 原来的API函数实现

    src/api/channel.js
    /* 获取我的频道(未登录会返回默认的一些频道) */ export const getMyChannels = async () => { if (!store.state.user.token) { const localData = JSON.parse(localStorage.getItem(KEY) || ‘[]’) if (localData.length) { // 保持和res一样的结构,使用这个API的地方就无需修改 return localData } else { // 获取数据本地缓存 const [, res] = await request({ url: ‘v1_0/user/channels’ }) // 只存数组 localStorage.setItem(KEY, JSON.stringify(res.data.data.channels)) return res.data.data.channels } } else { const [, res] = await request({ url: ‘v1_0/user/channels’ }) return res.data.data.channels } }

    • 修改home组件中调用API函数的地方

    src/views/home/index.vue
    async created () { // 不使用err不写err即可,但是,号需要写 const channels = await getMyChannels() this.myChannels = channels }
    总结: 在API函数完成获取我的频道逻辑业务,降低组件的业务复杂的,提高可复用性。
    补充: 登录状态的切换,需要更新频道数据。
    watch: { ‘$store.state.user.token’: async function () { const channels = await getMyChannels() this.myChannels = channels this.activeIndex = 0 } }

    #07-频道-添加

    目的:完成我的频道的添加操作,支持本地和线上。
    image.png
    大致步骤:

    • 按照流程图 定义API函数实现
    • 绑定点击事件,调用API函数添加

    落地代码:
    api/channel.js API函数
    / 添加频道 @param {Array} myChannels - 我的频道集合 @param {Number} myChannels.id - 频道ID @param {String} myChannels.name - 频道名称 @param {Number} myChannels.seq - 频道名称 / export const addChannel = async (myChannels) => { if (!store.state.user.token) { // 1. 获取本地 const localData = JSON.parse(localStorage.getItem(KEY) || ‘[]’) // 2. 最后一项就是需要更新的 const { id, name } = myChannels[myChannels.length - 1] // 3. 追加新频道 localData.push({ id, name }) // 4. 存储本地 localStorage.setItem(KEY, JSON.stringify(localData)) } else { await request({ url: ‘/v1_0/user/channels’, method: ‘put’, data: { channels: myChannels } }) } }
    views/home/components/article-channel.vue 添加频道

    频道推荐


    // 添加频道 async addChannel (item) { // 1. 使用重置式添加频道数据,准备重置式的数据 const newMyChannels = [] this.myChannels.forEach((c, i) => { if (i !== 0) { newMyChannels.push({ id: c.id, name: c.name, seq: i }) } }) newMyChannels.push({ …item, seq: newMyChannels.length + 1 }) // 2. 去做添加频道操作 await addChannel(newMyChannels) // 3. 成功:更新我的频道 this.myChannels.push(item) }
    总结:** 再添加频道到API函数处理登录和未登录的添加操作,使用重置的方式更新我的频道,需要再点击添加频道按钮后自己组织数据。

    #08-频道-切换编辑

    目的:显示删除按钮,完成编辑状态切换
    大致步骤:

    • 准备删除按钮
    • 切换编辑状态

    落地代码:

    • 切换编辑 src/views/home/article-channel.vue

    准备删除按钮(编辑状态,不是推荐,不是当前激活频道,才可以显示)
    .body { padding: 0 6px 0 16px; a { + position: relative; + .geek-icon { + position: absolute; + top: -5px; + right: -5px; + line-height: 1; + }
    {{item.name}} +
    切换编辑状态(关闭弹出层后,需要改成不编辑状态)

    我的频道点击进入频道

    + + {{isEdit?’完成’:’编辑’}} +

    data () { return { // 全部频道 allChannels: [], + // 是否编辑 + isEdit: false } },

    #09-频道-删除

    目的:完成我的频道的删除操作,支持本地和线上。
    image.png
    大致步骤:

    • 按照流程图 定义API函数实现
    • 绑定点击事件,调用API函数删除

    落地代码:

    • API函数 api/channel.js

    /* 删除频道 @param {Number} id - 频道ID / export const delChannel = async (id) => { if (!store.state.user.token) { // 1. 获取本地 const localData = JSON.parse(localStorage.getItem(KEY) || ‘[]’) // 2. 删除频道 const index = localData.findIndex(item => item.id === id) localData.splice(index, 1) // 3. 存储本地 localStorage.setItem(KEY, JSON.stringify(localData)) } else { await request({ url: ‘/v1_0/user/channels/‘ + id, method: ‘delete’ }) } }

    • 删除频道 views/home/components/article.vue

    // 删除频道 async delChannel (id) { // 删除操作 await delChannel(id) // 成功:更新我的频道 const index = this.myChannels.findIndex(item => item.id === id) this.myChannels.splice(index, 1) }

    #第六章:文章详情

    #01-文章详情-顶部导航

    目的:完成文章详情布局,顶部导航
    大致步骤:

    • 完成跳转
    • 顶部导航

    落地代码:

    • 完成跳转 src/views/home/components/article-item.vue
    - 顶部导航 src/views/article/index.vue 点击返回按钮回退历史,固定定位,右侧按钮通过right插槽使用,全局样式按钮黑色

    src/assets/styles/index.less
    // 全局生效的样式 .van-nav-bar .van-icon{ color: #333; }
    src/main.js
    import ‘@/assets/styles/index.less’ ### #02-文章详情-主体内容 目的:完成文章详情布局,标题,时间,作者,内容
    大致步骤: - 使用基础结构 - 了解结构划分 落地代码:
    src/views/article/index.vue

    第二十二节:Java语言基础-详细讲解位运算符与流程控制语句

    2019年03月11日 | 1186 阅读 | 61 评论
    黑马先锋 +关注

    我会把书籍分成两类,一类是全面型,一类是犀利型.前面介绍了一本全面型的书籍,接下来介绍的这本的特点是非常犀利,这类书籍的特点是作者能找对重点(2/8原则掌握的很好),在重点位置深入挖掘.这本书的作者John Resig也是JQuery的作者,他显然是个足够犀利的人儿.JQuery从未承诺解决所有问题,但再一些重点部位的突破,让这个类库如此流行.这本书并没有着重介绍JQuery,还是基于原生的JavaScript和DOM API.


    .article-wrapper { height: 100%; overflow-y: auto; padding: 44px 0 50px; // 头部 .header { padding: 0 16px; .title { font-size: 20px; font-weight: normal; padding: 10px 0; margin: 0; } .time { font-size: 12px; color: #999; span:nth-child(2n) { margin: 0 5px; color: #ccc; position: relative; top: -1px; } } .author { display: flex; align-items: center; padding: 10px 0 ; .name { flex: 1; padding-left: 10px; font-size: 16px; } } } // 内容 .main { .space { height: 16px; background: @geek-gray-color; } .html { word-break: break-all; width: 100%; overflow: hidden; padding: 20px 16px; /deep/ img { max-width:100%; background: #f9f9f9; } /deep/ pre { white-space: pre-wrap; code { white-space: pre; } } } } }
    src/assets/styles/index.less
    { box-sizing: border-box; }
    总结: article-wrapper容器装:header main comment组件 ### #03-文章详情-渲染页面 目的:获取文章详情数据进行渲染 7974 这个文章数据不错哦
    大致步骤: - 定义API接口函数 - 定义数据,获取数据 - 渲染页面 落地代码: - 定义API接口函数 src/api/article.js /**
    获取文章详情 @param {String} id - 文章ID @returns / export const getArticle = (id) => { return request({ url: ‘/v1_0/articles/‘ + id }) } - 定义数据,获取数据 src/views/article/index.vue import { getArticle } from ‘@/api/article’ export default { name: ‘ArticlePage’, data () { return { article: {} } }, created () { this.getArticle() }, methods: { async getArticle () { const [, res] = await getArticle(this.$route.query.id) this.article = res.data.data } } } - 渲染页面 src/views/article/index.vue

    {{article.title}}

    {{article.pubdate}} | {{article.read_count}} 阅读 | {{article.comm_count}} 评论
    {{article.aut_name}} + 关注

    总结: 富文本内容使用v-html渲染 ### #04-文章详情-导航交互 目的:完成在滚动页面时候,头部被卷起后,在顶部导航显示作者信息。
    大致步骤: - 准备顶部导航作者信息 - 监听滚动,显示隐藏作者信息 落地代码: - 准备顶部导航作者信息 src/views/article/index.vue +
    /deep/ .van-nav-bar__title { max-width: 270px; width: 270px; } .nav-author { display: flex; justify-content: flex-start; align-items: center; > span { font-size: 14px; padding-left: 5px; } .line { color: #ccc; position: relative; top: -1px; } .follow { color: @geek-color; } } - 监听滚动,显示隐藏作者信息 src/views/article/index.vue

    data () { return { article: {}, showNavAuthor: false } }, methods: { // 监听滚动 onScroll () { const scrollTop = this.$refs.wrapper.scrollTop const headerHeight = this.$refs.header.offsetHeight this.showNavAuthor = scrollTop > headerHeight },