vue3项目实战---知乎日报----首页功能

2023-11-19

目录

网络请求封装

header

swiper

items新闻列表、

home

IntersectionObserver API 使用教程

性能优化


网络请求封装

GET传参格式 www.baidu.com/info?t=0&age=18

传递当天日期返回以往日期信息

params` 是即将与请求一起发送的 URL 参数 ,必须是一个无格式对象(plain object)或 URLSearchParams 对象

import axios from './http'

//获取最新新闻 && 轮播图信息
export const NewsLatest = () => {
    return axios.get('/api/news_latest')
}
//获取以往信息
export const NewsBefore = time => {
    return axios.get('/api/news_before', {
        params: {
            time
        }
    })
}

用类库处理时间格式(不传为当前时间,传递字符串返回数组格式)

通过计算属性 处理数组对应月份(计算属性有缓存)

// 日期格式化
export const formatTime = function formatTime(time, template) {
    if (typeof time !== "string") {
        time = new Date().toLocaleString('zh-CN', { hour12: false });
    }
    if (typeof template !== "string") {
        template = "{0}年{1}月{2}日 {3}:{4}:{5}";
    }
    let arr = [];
    if (/^\d{8}$/.test(time)) {
        let [, $1, $2, $3] = /^(\d{4})(\d{2})(\d{2})$/.exec(time);
        arr.push($1, $2, $3);
    } else {
        arr = time.match(/\d+/g);
    }
    return template.replace(/\{(\d+)\}/g, (_, $1) => {
        let item = arr[$1] || "00";
        if (item.length < 2) item = "0" + item;
        return item;
    });
};
<script>


import { reactive, ref, toRefs, computed } from "vue";
import { formatTime } from "../assets/utils";
export default {
    name: "Header",

    setup() {
        //状态
        let state = reactive({
            today: formatTime(null, '{0}{1}{2}'),
        })

        //基于计算属性,处理时间
        let time = computed(() => {
            let { today } = state
            let [month, day] = formatTime(today, '{1}-{2}').split('-')
            let newMonth = month.slice(1, 2)
            let arr = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二']
            return {
                day,
                month: arr[newMonth] + '月'
            }
        })

        return {
            ...toRefs(state),
            time,
        }
    },
}   
</script>

swiper

通过useRouter(路由实例对象).push跳转 动态路由匹配

<template>
    <section class="banner-box">
        <van-swipe :autoplay="3000" lazy-render v-if="bannerList.length > 0">
            <van-swipe-item v-for="item in bannerList" :key="item.id" @click="goToDetail(item.id)">
                <img class="abbre" :src=item.image alt="">
                <div class="desc">
                    <h2 class="title">
                        {{ item.title }}
                    </h2>
                    <p class="author">
                        {{ item.hint }}
                    </p>

                </div>
            </van-swipe-item>
        </van-swipe>
    </section>
</template>

<script>
import { useRouter } from "vue-router";
export default {
    name: "Swiper",
    props: ['bannerList'],
    setup() {
        const router = useRouter()
        //跳转详情
        const goToDetail = (id) => router.push(`/detail/${id}`)
        return {
            goToDetail
        }
    },

}
</script>

<style lang="less" scoped>
.banner-box {
    box-sizing: border-box;
    height: 375px;
    background: #eee;
    overflow: hidden;

    .van-swipe {
        height: 100%;
        overflow: hidden;

        .abbre {
            display: block;
            width: 100%;
            min-height: 100%;
        }
    }

    .desc {
        position: absolute;
        bottom: 0;
        left: 0;
        z-index: 10;
        box-sizing: border-box;
        padding: 15px 20px;
        width: 100%;
        background: rgba(0, 0, 0, .4);
        background: -webkit-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, .4));
        color: #fff;

        .title {
            line-height: 25px;
            font-size: 18px;
            max-height: 50px;
            overflow: hidden;
            display: -webkit-box;
            -webkit-line-clamp: 2;
            -webkit-box-orient: vertical;
        }

        .author {
            font-size: 14px;
            line-height: 25px;
            color: rgba(255, 255, 255, .8);
        }
    }

    /deep/.van-swipe__indicators {
        left: auto;
        transform: none;
        right: 15px;

        .van-swipe__indicator {
            width: 5px;
            height: 5px;
            background: rgba(255, 255, 255, .8);
        }

        .van-swipe__indicator--active {
            width: 15px;
            background: #fff;
            border-radius: 5px;
        }
    }
}
</style>

items新闻列表、

通过router-link路由导航跳转

使用vant实现图片懒加载

main.js导入使用

import Vant, { Lazyload } from 'vant';

app.use(Lazyload, {

    lazyComponent: true,

});

把src换为lazy

<template>
    <div class="news-box">
        <router-link :to="`/detail/${cur.id}`">
            <h3 class="title">
                {{ cur.title }}
            </h3>
            <div class="desc"> {{ cur.hint }}</div>
            <div class="pic">
                <img class="abbre" v-if="cur.images && cur.images.length > 0" v-lazy='cur.images[0]' alt="">
            </div>
        </router-link>
    </div>
</template>
        
<script>
export default {
    name: "NewsItem",
    props: {
        cur: {
            type: Object,
            require: true
        }
    },
    setup() { },
}

</script>

<style lang="less" scoped>
.news-box {
    position: relative;

    a {
        display: block;
        padding-right: 90px;
        min-height: 70px;
        overflow: hidden;
        margin-top: 30px;



        .pic {
            position: absolute;
            right: 0;
            top: 0;
            width: 70px;
            height: 70px;
            background: #eee;

            img {
                display: block;
                width: 100%;
                height: 100%;
            }
        }

        .title {
            font-size: 16px;
            color: #000;
            line-height: 25px;
            max-height: 50px;
            overflow: hidden;
            display: -webkit-box;
            -webkit-line-clamp: 2;
            -webkit-box-orient: vertical;
        }

        .desc {
            line-height: 20px;
            font-size: 12px;
            color: #999;
        }
    }


}
</style>

home

v-for与v-if同时使用时 v-for权重高

v-if控制元素销毁创建,v-show控制元素显示隐藏(在组件渲染完毕后想获取dom应该使用v-show)

<template>
    <Header></Header>
    <Swiper :bannerList="bannerList"></Swiper>
    <van-skeleton title :row="5" v-if="newList === 0" />
    <section class="news-box " v-else>
        <div class="day-box" v-for="(item, index ) in newList" :key="item.date">
            <van-divider dashed content-position="left" v-if="index > 0">{{ formatTime(item.date, '{1}月{2}日') }}
            </van-divider>
            <div class="list">
                <NewsItem v-for="cur in item.stories" :key="cur.id" :cur="cur" />
            </div>
        </div>
    </section>
    <section class="loadmore-box" ref="loadmore" v-show="newList">
        <van-loading size="18px">小主,奴家正在努力加载中...</van-loading>
    </section>
</template>

<script>
import Header from '../components/header.vue'
import Swiper from '../components/swiper.vue'
import NewsItem from '../components/items.vue'
import { reactive, ref, toRefs, onBeforeMount, onMounted, onBeforeUnmount } from "vue";
import { NewsLatest, NewsBefore } from "../api/index";
import { formatTime } from "../assets/utils";

export default {
    name: "Home",
    components: {
        Header,
        Swiper,
        NewsItem
    },
    setup() {
        let state = reactive({
            today: '',
            bannerList: [],
            newList: []
        })

        //在第一次渲染之前获取数据:今天日期 ,轮播图数据, 今日新闻
        onBeforeMount(async () => {
            let { date, stories, top_stories } = await NewsLatest();
            state.today = date
            state.bannerList = Object.freeze(top_stories)
            state.newList.push(Object.freeze({
                date,
                stories
            }))
        })

        //加载更多
        let loadmore = ref(null)
        const ob = new IntersectionObserver(async changes => {
            let item = changes[0]
            if (item.isIntersecting) {
                let len = state.newList.length
                if (len === 0) return
                let data = await NewsBefore(state.newList[len - 1].date)
                state.newList.push(Object.freeze(data))
            }
        });
        onMounted(() => {
            if (!loadmore.value) return
            ob.observe(loadmore.value)
        })

        onBeforeUnmount(() => {
            if (!loadmore.value) return
            ob.unobserve(loadmore.value)
        })
        return {
            ...toRefs(state),
            formatTime,
            loadmore
        }
    },
};
</script>
<style lang="less" scoped>
.van-skeleton {
    padding: 15px;
}

.news-box {
    padding: 0 15px;

    .day-box {
        margin-top: 30px;
    }
}

.loadmore-box {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 50px;
    margin-top: 20px;
    background: #f4f4f4;
}
</style>

IntersectionObserver API 使用教程

语法

可以自动"观察"元素是否可见,Chrome 51+ 已经支持。由于可见(visible)的本质是,目标元素与视口产生一个交叉区,所以这个 API 叫做"交叉观察器"。

IntersectionObserver API 是异步的,不随着目标元素的滚动同步触发。

规格写明,IntersectionObserver的实现,应该采用requestIdleCallback(),即只有线程空闲下来,才会执行观察器。这意味着,这个观察器的优先级非常低,只在其他任务执行完,浏览器有了空闲才会执行

var io = new IntersectionObserver(callback, option);

IntersectionObserver是浏览器原生提供的构造函数,接受两个参数:callback是可见性变化时的回调函数,option是配置对象(该参数可选)。

方法

构造函数的返回值是一个观察器实例。实例的observe方法可以指定观察哪个 DOM 节点。

如果要观察多个节点,就要多次调用这个方法。

// 开始观察
io.observe(document.getElementById('example'));

// 停止观察
io.unobserve(element);

// 关闭观察器
io.disconnect();

callback

callback一般会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见) callback函数的参数是一个数组

Option

IntersectionObserver构造函数的第二个参数是一个配置对象。它可以设置以下属性。

threshold属性决定了什么时候触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为[0],即交叉比例(intersectionRatio)达到0时触发回调函数。

new IntersectionObserver(
  entries => {/* ... */}, 
  {
    threshold: [0, 0.25, 0.5, 0.75, 1]
  }
);

用户可以自定义这个数组。比如,[0, 0.25, 0.5, 0.75, 1]就表示当目标元素 0%、25%、50%、75%、100% 可见时,会触发回调函数。

root 属性,rootMargin 属性

很多时候,目标元素不仅会随着窗口滚动,还会在容器里面滚动(比如在iframe窗口里滚动)。容器内滚动也会影响目标元素的可见性,

IntersectionObserver API 支持容器内滚动。root属性指定目标元素所在的容器节点(即根元素)。注意,容器元素必须是目标元素的祖先节点

var opts = { 
  root: document.querySelector('.container'),
  rootMargin: "500px 0px" 
};

var observer = new IntersectionObserver(
  callback,
  opts
);

上面代码中,除了root属性,还有rootMargin属性。后者定义根元素的margin,用来扩展或缩小rootBounds这个矩形的大小,从而影响intersectionRect交叉区域的大小。它使用CSS的定义方法,比如10px 20px 30px 40px,表示 top、right、bottom 和 left 四个方向的值。

这样设置以后,不管是窗口滚动或者容器内滚动,只要目标元素可见性变化,都会触发观察器。

性能优化

vue默认做了对象的深层级劫持 ,内层数据不需要修改的情况下使用 Object.freeze冻结就不会劫持

在组件销毁时清除定时器,监听器

使用vant组件库实现图片懒加载

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

vue3项目实战---知乎日报----首页功能 的相关文章

  • QT中信号和信号槽详解

    如何选择QDialogButtonBox的信号与槽 1 UI中设计了一个QDialogButtonBox 按钮为Cancel和Apply 2 构造函数连接 connect ui gt buttonBox SIGNAL accepted th
  • SpringBoot配置多数据源,三数据源,mysql+oracle+mysql

    废话不多说 直接上代码 yml文件配置 server port 8080 spring datasource primary
  • 小谈设计模式(1)—总序

    小谈设计模式 1 总序 专栏地址 开始操作 设计模式总论 设计模式是什么 组成要素 模式名称 问题描述 解决方案 效果描述 设计模式有什么作用 提供可重用的解决方案 提高代码的可读性和可维护性 促进代码的可扩展性 提高代码的灵活性和可重用性

随机推荐

  • llvm之IR手册翻译(5)

    原网址是 http llvm org docs LangRef html abstract 下面这些是我自己的翻译 肯定有很多不恰当的地方 做这些只是希望自己以后翻阅更加方便 如果能对大家有所帮助那是极好的 别名 别名 不像函数或变量 不会
  • OSError:...libcublasLt.so.11 with link time reference

    程序运行时报错 OSError u01 anaconda3 envs modlscope py39 lib python3 9 site packages nvidia cublas lib libcublas so 11 symbol c
  • 无法从命令行或调试器启动服务,必须首先安装Windows服务....。在“安装”阶段发生异常。 System.Security.SecurityException:未找到源

    此处一共两个问题 第一个问题完整描述是 无法从命令行或调试器启动服务 必须首先安装Windows服务 使用installutil exe 然后用ServerExplorer Windows服务器管理工具或NET START命令启动它 第二个
  • Python基础教程:强大的Pandas数据分析库

    Pandas是一个基于 NumPy 的非常强大的开源数据处理库 它提供了高效 灵活和丰富的数据结构和数据分析工具 当涉及到数据分析和处理时 使得数据清洗 转换 分析和可视化变得更加简单和高效 本文中 我们将学习如何使用Pandas来处理和分
  • 集合addAll方法使用存在的问题。

    集合addAll 方法的时候 这里里有两个集合 集合2要拿到集合1中的元素 然后对集合2进行removeAll方法 结果集合1中的值也没有了 只是因为listTwo listOne 只是把集合1的引用给了集合2 集合1和集合2的引用是指向同
  • GDB and Reverse Debugging

    Overview GDB version 7 0 due September 2009 will be the first public release of gdb to support reverse debugging the abi
  • PTA基础题练习-检查密码

    PTA 检查密码 本题要求你帮助某网站的用户注册模块写一个密码合法性检查的小功能 该网站要求用户设置的密码必须由不少于6个字符组成 并且只能有英文字母 数字和小数点 还必须既有字母也有数字 输出格式 输入样例 输出样例 本题要求你帮助某网站
  • Linux学习(一):查看文件目录的几种常用方式

    一 将主文件夹下的所有文件列出来 包括属性与隐藏文件 完整呈现文件的修改时间 注意 无论如何 ls最长被使用到的功能还是那个 l的参数 为此很多distribution在默认的情况中 已经将ll l的小写 设置成为ls l的意思了 这个功能
  • kodi没有中文设置_kodi播放器设置中文的方法

    KODI播放器是一款强大的多媒体播放器 其能够支持目前几乎所有的主流格式文件的播放 而且除了视频文件外 即使是音乐 图片 电视节目等其他媒体文件也能够正常进行播放 而且能够支持投屏扥更种辅助功能 为用户提供了一套完整的播放方案 因而广受用户
  • zookeeper学习草稿纸

    指令重排序 https baijiahao baidu com s id 1701616903992143186 wfr spider for pc JVM JDK JRE 静态方法为什么不能调用非静态成员 重载和重写的区别 可变参数 基本
  • 高质量、高并发的实时通信架构设计与探索

    中国互联网络信息中心 CNNIC 近日发布的第 47 次 中国互联网络发展状况统计报告 显示 截至 2020 年 12 月 我国网民规模达 9 89 亿 随着社会信息化水平持续提升及电子设备加速普及 手机网民规模持续增长 基本实现对全体网民
  • 关于Semaphore信号量的源码解读

    Semaphore的简单使用 利用Semaphore可以实现对线程数量的控制 比如如下的代码 class SemaphoreTest public static void main String args Semaphore semapho
  • 【RTX 3060Ti 深度学习环境配置图文(安装Anaconda、VScode、CUDA、CUDNN、pytorch)】

    RTX 3060Ti 深度学习环境配置图文 安装Anaconda VScode CUDA CUDNN pytorch 配置 安装驱动 一 安装Anaconda 1 1 Anaconda简介 1 2 下载Anaconda 1 3安装Anaco
  • SpringBoot集成jasypt,加密yml配置文件

    SpringBoot集成jasypt 加密yml配置文件 一 pom配置 二 生成密文代码 三 配置 3 1 yml加密配置 3 2 密文配置 3 3 启动配置 3 4 部署配置 四 遇到的一些坑 最新项目安全检测 发现配置文件中数据库密码
  • Spring学习笔记day01——Spring入门

    Spring学习 Spring介绍 1 1Spring概述 Spring是一个开源框架 Spring是于2003 年兴起的一个轻量级的Java 开发框架 由Rod Johnson 在其著作Expert One On One J2EE Dev
  • 设计模式的 C++ 实现---工厂方法模式(二)

    前文回顾 单例模式 一 单例模式 二 观察者模式 简单工厂模式 工厂方法模式 一 前言 对于工厂方法模式 当增加新产品时 也需要对应增加一个工厂类 可以使用模版进行封装 减少代码工作量 实现举例 产品抽象基类 class Animal pu
  • TypeScript 基本概念

    TypeScript 是什么 目标 能够说出什么是 TypeScript TS 官方文档 TS 中文参考 不再维护 TypeScript 简称 TS 是 JavaScript 的超集 JS 有的 TS 都有 TypeScript Type
  • 分布式锁实现方案2、基于Redis的SET操作实现的分布式锁

    继上一篇文章 分布式锁实现方案1 基于Redis的SETNX操作实现的分布式锁 实现方案之后 redis又提供了更加强大的set方法 可以解决分布式锁实现方案1中提到的缺陷 直接看代码 package com alioo lock impo
  • C++Primer第五版习题答案(二)

    第二章 变量和基本类型 2 8 2 10 2 14 C Primer第五版课后习题答案目录 2 8 include
  • vue3项目实战---知乎日报----首页功能

    目录 网络请求封装 header swiper items新闻列表 home IntersectionObserver API 使用教程 性能优化 网络请求封装 GET传参格式 www baidu com info t 0 age 18 传