项目架构
基于BS
架构项目。 使用http
作为网络协议,数据存储在mysql
数据库。前后端分离。
客户端:VueCLI
、MintUI
服务端:nodejs
、mysql
、express
搭建学子问答项目的服务端
-
下载
server.zip
,解压。 -
下载
xzqa.sql
,导入mysql
数据库。打开
xampp
,启动mysql
服务。 点击shell
, 进入命令行,执行命令:mysql -u root < [把xzqa.sql文件拖拽到此处生成路径即可]
导入成功后,可以进入
mysql
,查看一下数据:root> mysql -u root MariaDB> show databases; MariaDB> use xzqa; MariaDB> show tables; MariaDB> select * from xzqa_category; MariaDB> desc xzqa_article; MariaDB> select id, subject from xzqa_article limit 0,5; MariaDB> select xa.id, xa.subject, xc.category_name from xzqa_article xa join xzqa_category xc on xa.category_id=xc.id limit 0,5;
-
启动
server
,提供http
访问接口。node app.js
访问地址:
http://localhost:3000/category
项目实现
加载首页中的类别列表信息
实现步骤
-
安装配置
axios
。安装
axios
:# 进入项目目录下,执行安装命令 npm install --save axios
配置
main.js
:import axios from 'axios' axios.defaults.baseURL = 'http://localhost:3000/' Vue.prototype.axios = axios // 任何Vue对象都将会携带一个属性:axios
-
在
Index.vue
的mounted
中发送http
请求,获取响应,渲染页面。/** 发请求,加载文章类别列表 */ loadCats(){ this.axios.get('/category').then(res=>{ console.log('加载类别列表', res) this.cats = res.data.results // 将数组存入data.cats }) }
加载首页UI
类别下的文章列表数据(第一页数据)
http://localhost:3000/articles?cid=1&page=1
http://localhost:3000/articles?cid=1&page=2
http://localhost:3000/articles?cid=1&page=5
http://localhost:3000/articles?cid=2&page=1
.......
实现步骤:
-
在
Index.vue
的mounted
生命周期方法中,发送http
请求,访问ui
类别下的首页文章列表。// 加载UI类别下的首页文章列表 this.axios.get(`/articles?cid=1&page=1`).then(res=>{ console.log('加载UI类别下首页数据', res) this.articles = res.data.results // 将文章数组存入data.articles })
-
获取数据,存入
data
,在页面完成遍历显示。<ArticleItem v-for="(item, i) in articles" :key="i" :article="item" />
遍历输出20个
ArticleItem
组件,需要给每一个组件传递参数(当前文章信息)。在
ArticleItem
组件中接收,并且显示在页面中。<template> .... {{article.subject}} .... </template>
export default { props:['article'], // article接受了当前文章信息对象{id,desc,img,sub} .... };
实现切换顶部导航时更新文章列表
由于每个选项卡的布局都一样,切换顶部选项卡时仅仅需要更新列表就行了。所以没必要搞那么多的面板。干掉仨,留下一个即可。
当点击某一个顶部选项卡时,获取当前激活项的id
,发送http
请求,获取响应数据,重新渲染列表。
列表的触底分页加载
需求:当列表滚到底部,触发事件,在事件处理函数中,发送请求,加载下一页数据,追加到当前文章列表的末尾即可。
Infinite Scroll
指令 (无限滚动指令)
Infinite Scroll
用于监听元素的触底事件。 基本使用方法:
<div v-infinite-scroll="loadMore" 当div滚动到底自动执行loadMore方法
infinite-scroll-distance="定义无限滚动触发的距离阈值">
<p>.....</p>
<p>.....</p>
<p>.....</p>
......
</div>
methods:{
loadMore(){ console.log('到底了~') }
}
防抖:触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。
节流:高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率
实现思路
-
为列表容器添加无限滚动指令。
<mt-tab-container v-infinite-scroll="loadMore" infinite-scroll-distance="50"> .... </mt-tab-container>
-
触底后加载下一页。就需要在
data
中声明一个变量page
用于保存当前页码。每次下一页时,page++
。然后向当前类别的下一页发送请求即可。/articles?cid=2&page=1 /articles?cid=2&page=2 /articles?cid=2&page=3 /articles?cid=2&page=4
this.page++ let cid = this.active let page = this.page http://localhost:3000/articles?cid=${cid}&page=${page}
-
将得到新文章列表追加到当前列表的末尾。
-
处理一下节流问题。通过
isLoading
变量防止触底频繁发送请求。 -
处理刷新页面时立即执行一次
loadMore
的bug
。问题的原因在于
infinite-scroll
指令。 它将会在首页数据不满一屏幕的情况下,自动执行loadMore
。 -
切换顶部导航时,需要将滚动条复位,还有需要重置
this.page
为1.// 将滚动条滚动到顶部 window.scrollTo(0, 0) // 重新重置page变量为1 this.page = 1
异步方法的封装
至此我们发现,有3个地方都发送了/articles
请求,希望获取到文章列表:
mounted
/articles?cid=1&page=1
watch active
/articles?cid=xxxxxx&page=1
loadMore
/articles?cid=xxxx&page=xxxx
我们可以将访问文章列表的请求,封装到一个loadArticles
方法中,这样,这三个地方都可以直接调用这个loadArticlies
方法,提高代码的重用性与可维护性。
该方法的功能是,接收cid与page,发送请求, 返回articleList
loadArticles(cid, page, callback){
this.axios.get('/articles?cid=${cid}&page=${page}').then(res=>{
let articleList = res.data.results
callback(articleList) // 执行callback方法
})
}
mounted(){
this.loadArticles(1, 1, (articleList)=>{
this.articles = articleList // 替换列表
})
}
watch Active(){
this.loadArticles(this.active, 1, (articleList)=>{
this.articles = articleList
})
}
loadMore(){
this.loadArticles(this.active, ++this.page, (articleList)=>{
this.articles.push(...articleList)
})
}
自定义封装的方法内部执行异步任务,异步任务的结果是无法直接通过return
返回的。如果需要返回异步任务的结果给自定义方法的调用者,两种方法:
callback
promise
实现文章详情页的内容展示
业务流程:点击首页列表项中某一项时,跳转到详情页,显示选中项文章的详情信息。
实现步骤
-
点击列表项,跳转到详情页。 跳转的同时,需要将选中项文章的
ID
传给详情页。传参的第一种方式:
Index.vue
<router-link to="/article?id=123"></router-link>
Article.vue
mounted(){ let id = this.$route.query.id }
传参的第二种方式:
Index.vue
<router-link to="/article/123"></router-link> <router-link to="/article/237"></router-link> <router-link to="/article/238"></router-link>
router/index.js
{ path: '/article/:articleId', component: Article组件 }
Article.vue
mounted(){ this.$route.params.articleId }
-
在详情页中获取选中文章的
ID
,发送http
请求,获取详情数据。/detail?id=xx
-
获取到详情数据后,将信息渲染到页面上。
处理刷新首页时滚动位置的更新问题
基于Vue
的滚动行为解决刷新滚动位置的修改。
基于keepAlive
实现首页的保活
在router/index.js
合适的路由中,添加路由meta信息,指定哪些路由组件需要保活:
{
path: 'index',
component: () => import('../views/Index.vue'),
meta: {
keepAlive: true
}
},
在App.vue
中,使用keepAlive
组件包裹router-view
。被包裹的路由组件将会保活。
<keepAlive>
<router-view v-if="$route.meta.keepAlive"/>
</keepAlive>
<router-view v-if="!$route.meta.keepAlive"/>
如上配置即可实现首页的保活,但是,当跳转到详情页后,在滚动时会触发mintui
的无限滚动指令监听方法。解决方法如下:
当首页开启了keepAlive
,将会解锁两个生命周期方法,activated
deactivated
。
activated() {
this.isLoading = false;
},
deactivated() {
this.isLoading = true;
},
实现注册业务
业务流程:
在注册页面,填写表单;
点击快速注册,验证表单;
验证成功后,发送注册请求,提交用户信息,执行注册业务;
- 注册成功,跳转到登录;
- 注册失败,弹窗提示。
实现登录业务
业务流程:
在登录页面,填写表单;
点击登录,验证表单;
验证成功后,发送登录请求,提交用户信息,执行登录业务;
- 登录成功,跳转到首页;
- 登录失败,弹窗提示。
基于Vuex
登录成功后在首页提示用户信息
Vuex
state
用于定义存在vuex
中的状态信息
mutations
用于定义修改state
所需要的方法
actions
用于定义异步方法,执行完异步任务后,可以调用mutations
修改state
。
Vuex
的核心功能就是在合适的时间点,存数据、取数据。
实现步骤:
-
在
store/index.js
中,向state对象中存入一些信息,用来全局保存用户登录状态。state:{ isLogin: true, name: 'zs' }
在页面中引用:
<div slot="right" v-if="$store.state.isLogin"> 欢迎:{{$store.state.name}} </div>
-
在vuex的
mutations
声明一个方法,用于登录成功后修改state
:mutations: { /** 定义一个方法 当登录成功后修改state * state: vuex将会自动传入state对象,方便操作变量 * newname: 该参数是调用者携带的自定义参数 */ loginOK(state, newname){ state.isLogin = true state.name = newname } },
如何调用
mutations
中声明的方法:// commit()方法用于请求vuex,执行mutations中定义的loginOK this.$store.commit('loginOK', 'zhangsan')
Actions
在actions
中定义函数,异步完成任务得到结果后,将结果更新到state
。
actions:{
login(store, userObj){
// actions中执行异步任务
执行登录().then(res=>{
// 异步任务得到结果后,不能直接操作state,应该调用store.commit方法
// 让mutations直接修改state。
store.commit('loginOK', userObj.name)
})
}
}
vuex
可以在不刷新页面的前提下保存多页面的共享数据。但是一旦刷新,意味着页面将重新加载,整个Vue
容器也将会重新加载,Vuex
中存储的数据也将不复存在,全部初始化。也就无法保存登录状态信息。
如果希望持久化保存用户数据(刷新不销毁,甚至客户端关机重启依然存在),推荐使用HTML5
新特性中的WebStorage
来解决。
WebStorage
WebStorage
是HTML5
提供的可以让前端持久化缓存数据的存储空间。包含两种对象:
sessionStorage
: 这一块存储空间存储的数据单浏览器会话生效。重启浏览器即销毁。localStorage
: 这一块存储空间存储的数据永久有效。
WebStorage
相关API
存数据:
sessionStorage.setItem('name', '张三')
localStorage.setItem('age', '15')
let user = {"id":1,"name":"zs","age":15}
sessionStorage.setItem('user', JSON.stringify(user))
读数据:
sessionStorage.getItem('name') --> '张三'
localStorage.getItem('age') --> '15'
sessionStorage.getItem('user') --> JSON字符串
JSON.parse(sessionStorage.getItem('user')) --> JS对象
删除数据:
sessionStorage.removeItem('name')
localStorage.removeItem('name')
清空数据:
sessionStorage.clear()
localStorage.clear()