搜索历史记录:
当发生搜索的时候我们才需要记录历史记录。
1 添加历史记录
// 在`search/index.vue` `data`添加一个数据用来存储历史记录
data () {
return {中
...
searchHistories: []
}
}
// 在触发搜索的时候,记录历史记录
onSearch (val) {
// 更新文本框内容
this.searchText = val
// 存储搜索历史记录
// 要求:不要有重复历史记录、最新的排在最前面
const index = this.searchHistories.indexOf(val)
if (index !== -1) {
this.searchHistories.splice(index, 1)
}
this.searchHistories.unshift(val)// 渲染搜索结果
this.isResultShow = true
},
2 展示历史记录
需要父子通信,将数据传递给`search-history.vue`去渲染使用
// 父组件`search/index.vue`传递数据
<!-- 搜索历史记录 -->
<search-history :search-histories="searchHistories" v-else />
<!-- /搜索历史记录 -->
// 子组件`search-history.vue`接收数据
props:{
// 搜索历史,接收外部传入数据
searchHistories:{
type:Array,
required:true
}
}
// 渲染数据
<!-- 历史记录 -->
<van-cell-group v-else>
<van-cell title="历史记录">
<van-icon name="delete" />
<span>全部删除</span>
<span>完成</span>
</van-cell>
<van-cell
:title="item"
v-for="(item, index) in searchHistories"
:key="index"
>
<van-icon name="close"></van-icon>
</van-cell>
</van-cell-group>
<!-- /历史记录 -->
3 删除历史记录
基本思路:
- 给历史记录中的每一项注册点击事件
- 在处理函数中判断
- 如果是删除状态,则执行删除操作
- 如果是非删除状态,则执行搜索操作
4 数据持久化
// 在`search/index.vue`中利用 `watch` 监视统一存储数据
// 导入本地存储封装方法
import { setItem, getItem } from '@/utils/storage'watch: {
searchHistories (val) {
// 同步到本地存储
setItem('serach-histories', val)
}
}//初始化的时候从本地存储获取数据
data () {
return {
...
searchHistories: getItem('serach-histories') || [],
}
}
文章详情:
1 创建组件并配置路由
// 创建 `views/article/index.vue` 组件
<template>
<div class="article-container">文章详情</div>
</template><script>
export default {
name: 'ArticleIndex',
components: {},
props: {
// 使用props解耦获得了的动态路由数据,这样我们就可以使用this.articleId 获取动态路由数据 而不需要使用 this.$route.params.articleId
articleId: {
type: [Number, String],
required: true
}
},
data () {
return {}
},
computed: {},
watch: {},
created () {},
mounted () {},
methods: {}
}
</script>
<style scoped lang="less"></style>
// 然后将该页面配置到根级路由
{
path: '/article/:articleId',
name: 'article',
component: () => import('@/views/article'),
// 将路由动态参数映射到组件的 props 中,更推荐这种做法
props: true
}
// 找到首页文章列表项组件`src/components/article-item/index.vue`配置路由跳转
<!--
Cell 单元格的 to 属性和 VueRouter 中的 RouterLink 导航组件的 to 属性用法是一样的
用法参考链接:https://router.vuejs.org/zh/api/#to
:to="'/article/' + article.art_id"
:to="`/article/${article.art_id}`"
:to="{ name:'路径名称', params:{ 标识符:数据 } }"
-->
<van-cell
class="article-item"
:to="{ name: 'article', params: { articleId: article.art_id} }"
>
2 页面布局
使用到的 Vant 中的组件:
- [NavBar 导航栏]
- [Loading 加载]
- [Cell 单元格]
- [Button 按钮]
- [Image 图片]
- [Divider 分割线]
- [Icon 图标]
3 实现功能
1、获取文章数据
思路:
- 找到数据接口
- 封装请求方法
- 请求获取数据
- 模板绑定
2、展示文章详情,渲染数据,静态展示
3、处理内容加载状态
需求:
- 加载中,显示 loading
- 加载成功,显示文章详情
- 加载失败,显示错误提示
- 如果 404,提示资源不存在
- 其它的,提示加载失败,用户可以点击重试重新加载
// data定义变量
loading: true, // 加载中的 loading 状态
errStatus: 0 // 失败的状态码
async loadArticle () {
// 展示 loading 加载中
this.loading = true
try {
const { data } = await getArticleById(this.articleId)// if (Math.random() > 0.5) {
// JSON.parse('dsankljdnskaljndlkjsa')
// }// 赋值
this.article = data.data} catch (err) {
if (err.response && err.response.status === 404) {
this.errStatus = 404
}
}
// 无论成功还是失败,都需要关闭 loading
this.loading = false
},
<!-- 加载中 -->
<div v-if="loading" class="loading-wrap"> ... </div>
<!-- /加载中 --><!-- 加载完成-文章详情 -->
<div v-else-if="article.title" class="article-detail"> ... </div>
<!-- /加载完成-文章详情 --><!-- 加载失败:404 -->
<div v-else-if="errStatus === 404" class="error-wrap"> ... </div>
<!-- /加载失败:404 --><!-- 加载失败:其它未知错误(例如网络原因或服务端异常) -->
<div v-else class="error-wrap">...</div>
<!-- /加载失败:其它未知错误(例如网络原因或服务端异常) -->
4、处理正文样式
文章正文包括各种数据:段落、标题、列表、链接、图片、视频等资源。
4-1 将 [github-markdown-css] 样式文件下载到项目中
- 把 样式文件下载放在`views/article/github-markdown.css`
- 在`article/index.vue`里面的`style`标签里面导入
<style scoped lang="less">
@import "./github-markdown.css";
4-2 给内容正文标签增加`markdown-body`样式类
<!-- 文章内容 -->
<div class="article-content markdown-body" v-html="article.content" ></div>
<van-divider>正文结束</van-divider>
4-3 `.postcssrc.js`中配置不要转换样式文件中的字号
// 配置不要转换的样式资源
exclude: 'github-markdown' // 增加这一句!
需要重新启动项目,配置才会生效!
5、图片点击预览
思路:
5-1、从文章内容中获取到所有的 img DOM 节点
5-2、获取文章内容中所有的图片地址
5-3、遍历所有 img 节点,给每个节点注册点击事件
5-4、在 img 点击事件处理函数中,调用 ImagePreview 预览
<!-- 增加ref属性 -->
<div class="article-content markdown-body" ref="article-content" v-html="article.content" ></div>
import { ImagePreview } from 'vant' // 导入预览插件
async loadArticle () {
// 前面省略很多代码...
// 数据驱动视图这件事儿不是立即的
this.article = data.data// 初始化图片点击预览
console.log(this.$refs['article-content']) // 这里没有内容
// 这个时候其实找不到 这个refs引用的,原因是因为v-if的渲染其实需要时间,我们视图还没有立即更新完成。
// 使用定时器,延迟更新( setTimeout 0 会把要做的事情放在异步队列的最后面执行! )
setTimeout(() => {
console.log(this.$refs['article-content']) // 这里有内容
this.previewImage()
}, 0)
// 不使用定时器可以使用nextTick这个api方法
/*
this.loading = false
this.$nextTick(()=>{
this.previewImage()
})
*/
// 后面省略很多代码...
},
// 预览图片处理事件函数
previewImage () {
// 得到所有的 img 节点
const articleContent = this.$refs['article-content'] // 获取到了容器节点
const imgs = articleContent.querySelectorAll('img')// 获取所有 img 地址
const images = []
imgs.forEach((img, index) => {
images.push(img.src)// 给每个 img 注册点击事件,在处理函数中调用预览
img.onclick = () => {
ImagePreview({
// 预览的图片地址数组
images,
// 起始位置,从 0 开始
startPosition: index
})
}
})
}