vue3--实现瀑布流-长列表-懒加载

前言

在这一章我们主要学习:瀑布流、长列表、懒加载等功能

瀑布流组件

数据格式

[{"tags": ["all","home","desire","pets"],"_id": "62208123fb7e8b6da85b7dfe","photoLink": "https://www.pexels.com/zh-cn/photo/8051987/","photo": "https://images.pexels.com/photos/8051987/pexels-photo-8051987.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500","authorLike": "https://www.pexels.com/zh-cn/@ugurcan-ozmen-61083217","avatar": "https://images.pexels.com/users/avatars/61083217/ugurcan-ozmen-235.jpeg?auto=compress&fit=crop&h=60&w=60","author": "Uğurcan Özmen","photoDownLink": "https://www.pexels.com/photo/8051987/download/","id": "8051987","title": "图片数据来自 pexels ","photoWidth": 500,"photoHeight": 625,"photoType": "jpg","__v": 0},...
]

构建基础列表展示

api/pexels.js

import request from '@/utils/request.js'/*** 获取图片列表数据*/
export const getPexelsList = (data) => {return request({url: "/api/pexels/list",params: data,})
}

views/home/index.vue

<template><div class="home-container h-full overflow-auto bg-white dark:bg-zinc-800 duration-500"><Navigation></Navigation><div class=" max-w-screen-xl mx-auto relative m-1 xl:mt-4"><ListVue></ListVue></div></div>
</template><script setup>
import Navigation from "./components/Navigation/index.vue";
import ListVue from "./components/list/index.vue";</script>

views/home/components/list/item.vue

<template><divclass="bg-white dark:bg-zinc-900 xl:dark:bg-zinc-800 rounded pb-1 h-[280px] w-[230px]"><div class="relative w-full rounded cursor-zoom-in group"><!-- 图片 --><img class="w-full rounded bg-transparent" :src="data.photo" alt="" /><!-- 遮罩层 --><divclass="hidden opacity-0 w-full h-full bg-zinc-900/50 absolute top-0 right-0 left-0 rounded duration-300 group-hover:opacity-100 xl:block"><!-- 分享按钮 --><m-button class="absolute top-1.5 left-1.5">分享</m-button><!-- 点赞按钮 --><m-buttonclass="absolute top-1.5 right-1.5"type="info"icon="heart"iconClass="fill-zinc-900 dark:fill-zinc-200"></m-button><!-- 下载按钮 --><m-buttonclass="absolute bottom-1.5 left-1.5 bg-zinc-100/70"type="info"icon="download"size="small"iconClass="fill-zinc-900 dark:fill-zinc-200"></m-button><!-- 全屏按钮 --><m-buttonclass="absolute bottom-1.5 right-1.5"type="info"icon="full"size="small"iconClass="fill-zinc-900 dark:fill-zinc-200"></m-button></div></div><!-- 标题 --><p class="text-sm mt-1 font-bold text-zinc-900 dark:text-zinc-300 px-1">{{ data.title }}</p><!-- 作者信息 --><div class="flex items-center mt-1 px-1"><img class="h-2 w-2 rounded-full" :src="data.avatar" alt="" /><span class=" text-sm text-zinc-500 ml-1">{{ data.author }}</span></div></div>
</template><script setup>
const props = defineProps({data: {type: Object,required: true}
})
</script><style lang="scss" scoped></style>

views/home/components/list/index.vue

<template><div><ItemVue v-for="item in pexelsList" :key="item.id" :data="item"></ItemVue></div>
</template><script setup>
import { getPexelsList } from '@/api/pexels'
import ItemVue from "./item.vue";/*** 构建数据请求*/
let query = {page: 1,size: 20,categoryId: '',searchText: ''
}
// 数据是否在加载中
const isLoading = ref(false)
// 数据是否全部加载完成
const isFinished = ref(false)
// 数据源
const pexelsList = ref([])
const getPexlesData = async () => {// 数据全部加载完成则 returnif (isFinished.value) {return}// 完成第一次请求之后,后续请求让 page 自增if (pexelsList.value.length) {query.page += 1}// 触发接口请求const res = await getPexelsList(query)// 初始请求清空数据源if (query.page === 1) {pexelsList.value = res.list} else {pexelsList.value.push(...res.list)}// 判断数据是否全部加载完成if (pexelsList.value.length === res.total) {isFinished.value = true}// 修改 loading 标记isLoading.value = false
}getPexlesData()
</script><style scoped lang="scss"></style>

瀑布流组件构建分析

在上面我们虽然实现了列表的基本构建,但是相比大家已经发现了,还是存在了很多的问题的。

存在的问题如下:

当前每一个item的排列,应该以横向的形式进行排列,在第二行中,第一个item不会按照上面的逻辑依次排列,而是第二行的第一个item会被放到第一行中高度最短的那个item下面,以此类推。

而这样排列的逻辑正是我们构建瀑布流的逻辑。

而要这样做,那么肯定每个item的排列不会像上面那样正常排列堆叠下去,而是需要使用absolute进行定位排列。

这里为了方便复用,我们会将其抽离封装成一个公共的组件--waterfall

大致使用如下:

<m-waterfall :data="" :nodeKey="" :column="" :picturePreReading=""><template v-slot="{ item, width }"><ItemVue :data="item"></ItemVue></template>
</m-waterfall>
  • data:数据源
  • nodeKey:唯一标识
  • column:渲染的列数
  • picturePreReading:是否需要图片预加载

瀑布流渲染机制:通过 absolute 配合 relative 完成布局,布局逻辑为:每个 item 应该横向排列,第二行的 item 顺序连接到当前最短的列中

通过 作用域插槽 将每个 item 中涉及到的关键数据,传递到 item 视图中

瀑布流组件构建

构建瀑布流布局:获取容器宽度与列宽

libs/waterfall/index.vue

<template><!-- 因为当前为 relative 布局,所以需要主动指定高度 --><divclass="relative"ref="containerTarget":style="{ height: containerHeight + 'px' }"><!-- 数据渲染 --><!-- 因为列数不确定,所以需要根据列数计算每列的宽度,所以等待列宽计算完成,并且有了数据源之后进行渲染 --><template v-if="columnWidth && data.length"><!-- 通过动态的 style 来去计算对应的列宽、left、top --><div class="m-waterfall-item absolute duration-300" v-for="(item, index) in data":key="nodeKey ? item[nodeKey] : index" :style="{width: columnWidth + 'px', left: item._style?.left + 'px', top: item._style?.top + 'px'}"><slot :item="item" :width="columnWidth" :index="index" /></div></template><!-- 加载中的提示 --><div v-else>加载中...</div></div>
</template><script setup>
const props = defineProps({// 数据源data: {type: Array,required: true},// 唯一标识的 keynodeKey: {type: String},// 列数column: {default: 2,type: Number},// 列间距columnSpacing: {default: 20,type: Number},// 行间距rowSpacing: {default: 20,type: Number},// 是否需要进行图片预读取picturePreReading: {type: Boolean,default: true}
})// 容器的总高度
const containerHeight = ref(0)
// 记录每列高度的容器。key:所在列  val:列高
const columnHeightObj = ref({})
/*** 构建记录各列的高度的对象。*/const useColumnHeightObj = () => {columnHeightObj.value = {}for (let i = 0; i < props.column; i++) {columnHeightObj.value[i] = 0}
}// 容器实例
const containerTarget = ref(null)
// 容器总宽度(不包含 padding、margin、border)
const containerWidth = ref(0)
// 容器左边距,计算 item left 时,需要使用定位
const containerLeft = ref(0)
/*** 计算容器宽度*/const useContainerWidth = () => {const { paddingLeft, paddingRight } = getComputedStyle(containerTarget.value,null)// 容器左边距containerLeft.value = parseFloat(paddingLeft)// 容器宽度containerWidth.value =containerTarget.value.offsetWidth -parseFloat(paddingLeft) -parseFloat(paddingRight)
}// 列宽 列宽=容器的宽度-所有列间距宽度 / 列数
const columnWidth = ref(0)
// 列间距合计
const columnSpacingTotal = computed(() => {// 如果是5列,则存在 4 个列间距return (props.column - 1) * props.columnSpacing
})
/*** 开始计算*/const useColumnWidth = () => {// 获取容器宽度useContainerWidth()// 计算列宽columnWidth.value =(containerWidth.value - columnSpacingTotal.value) / props.column
}
onMounted(() => {// 计算列宽useColumnWidth()
})
</script>

views/home/components/list/index.vue

<template><div><m-waterfall :data="pexelsList" nodeKey="id" :column="5" :picturePreReading="true"><template v-slot="{ item, width }"><ItemVue :data="item"></ItemVue></template></m-waterfall></div>
</template><script setup>
import { getPexelsList } from '@/api/pexels'
import ItemVue from './item.vue'/*** 构建数据请求*/
let query = {page: 1,size: 20,categoryId: '',searchText: ''
}
// 数据是否在加载中
const isLoading = ref(false)
// 数据是否全部加载完成
const isFinished = ref(false)
// 数据源
const pexelsList = ref([])
const getPexlesData = async () => {// 数据全部加载完成则 returnif (isFinished.value) {return}// 完成第一次请求之后,后续请求让 page 自增if (pexelsList.value.length) {query.page += 1}// 触发接口请求const res = await getPexelsList(query)// 初始请求清空数据源if (query.page === 1) {pexelsList.value = res.list} else {pexelsList.value.push(...res.list)}// 判断数据是否全部加载完成if (pexelsList.value.length === res.total) {isFinished.value = true}// 修改 loading 标记isLoading.value = false
}getPexlesData()
</script>

区分图片预加载,获取元素关键属性

想要计算每列的 left、right,那么需要拿到每个 item 的高度,因为只有有了每个 item 高,才可以判断下一列的第一个 item 的位置

同时我们根据 picturePreReading 又可以分为两种情况:

  • 需要图片预加载时:图片高度未知
  • 不需要图片预加载时:图片高度已知

根据以上分析可以得出以下代码

根据两种不同的情况,我们需要有两个不同的方法来进行计算:

首先要去掉 views/home/components/list/item.vue中,开始给定的宽高: h-[280px] w-[230px]

封装公共工具类

libs/waterfall/utils.js

/*** 从 itemElement 中抽离出所有的 imgElements*/
export const getImgElements = (itemElements) => {const imgElements = []itemElements.forEach((el) => {imgElements.push(...el.getElementsByTagName('img'))})return imgElements
}/*** 生成所有的图片链接数组*/
export const getAllImg = (imgElements) => {return imgElements.map((imgElement) => {return imgElement.src})
}/*** 监听图片数组加载完成(通过 promise 完成)*/
export const onComplateImgs = (imgs) => {// promise 集合const promiseAll = []// 循环构建 promiseAllimgs.forEach((img, index) => {promiseAll[index] = new Promise((resolve, reject) => {const imageObj = new Image()imageObj.src = imgimageObj.onload = () => {resolve({img,index})}})})return Promise.all(promiseAll)
}/*** 返回列高对象中的最小高度所在的列*/
export const getMinHeightColumn = (columnHeightObj) => {const minHeight = getMinHeight(columnHeightObj)return Object.keys(columnHeightObj).find((key) => {return columnHeightObj[key] === minHeight})
}/*** 返回列高对象中的最小的高度*/
export const getMinHeight = (columnHeightObj) => {const columnHeightArr = Object.values(columnHeightObj)return Math.min(...columnHeightArr)
}/*** 返回列高对象中的最大的高度*/
export const getMaxHeight = (columnHeightObj) => {const columnHeightArr = Object.values(columnHeightObj)return Math.max(...columnHeightArr)
}

需要图片预加载时

图片高度未知

libs/waterfall/index.vue

import {getImgElements,getAllImg,onComplateImgs,getMinHeightColumn,getMinHeight,getMaxHeight
} from './utils/*** 需要图片预加载时*/
// item 高度集合
let itemHeights = []
/*** 监听图片加载完成*/const waitImgComplate = () => {itemHeights = []// 拿到所有元素let itemElements = [...document.getElementsByClassName('m-waterfall-item')]// 获取所有元素的 img 标签const imgElements = getImgElements(itemElements)// 获取所有 img 标签的图片const allImgs = getAllImg(imgElements)onComplateImgs(allImgs).then(() => {// 图片加载完成,获取高度itemElements.forEach((el) => {itemHeights.push(el.offsetHeight)})// 渲染位置useItemLocation()})
}

不需要图片预加载时

图片高度已知

libs/waterfall/index.vue

const useItemHeight = () => {itemHeights = []// 拿到所有元素let itemElements = [...document.getElementsByClassName('m-waterfall-item')]// 计算 item 高度itemElements.forEach((el) => {// 依据传入数据计算出的 img 高度itemHeights.push(el.offsetHeight)})// 渲染位置useItemLocation()
}

完整代码

libs/waterfall/index.vue

<template><!-- 因为当前为 relative 布局,所以需要主动指定高度 --><divclass="relative"ref="containerTarget":style="{ height: containerHeight + 'px' }"><!-- 数据渲染 --><!-- 因为列数不确定,所以需要根据列数计算每列的宽度,所以等待列宽计算完成,并且有了数据源之后进行渲染 --><template v-if="columnWidth && data.length"><!-- 通过动态的 style 来去计算对应的列宽、left、top --><div class="m-waterfall-item absolute duration-300" v-for="(item, index) in data":key="nodeKey ? item[nodeKey] : index" :style="{width: columnWidth + 'px', left: item._style?.left + 'px', top: item._style?.top + 'px'}"><slot :item="item" :width="columnWidth" :index="index" /></div></template><!-- 加载中的提示 --><div v-else>加载中...</div></div>
</template><script setup>
import {getImgElements,getAllImg,onComplateImgs,getMinHeightColumn,getMinHeight,getMaxHeight
} from './utils'const props = defineProps({// 数据源data: {type: Array,required: true},// 唯一标识的 keynodeKey: {type: String},// 列数column: {default: 2,type: Number},// 列间距columnSpacing: {default: 20,type: Number},// 行间距rowSpacing: {default: 20,type: Number},// 是否需要进行图片预读取picturePreReading: {type: Boolean,default: true}
})// 容器的总高度
const containerHeight = ref(0)
// 记录每列高度的容器。key:所在列  val:列高
const columnHeightObj = ref({})
/*** 构建记录各列的高度的对象。*/const useColumnHeightObj = () => {columnHeightObj.value = {}for (let i = 0; i < props.column; i++) {columnHeightObj.value[i] = 0}
}// 容器实例
const containerTarget = ref(null)
// 容器总宽度(不包含 padding、margin、border)
const containerWidth = ref(0)
// 容器左边距,计算 item left 时,需要使用定位
const containerLeft = ref(0)
/*** 计算容器宽度*/const useContainerWidth = () => {const { paddingLeft, paddingRight } = getComputedStyle(containerTarget.value,null)// 容器左边距containerLeft.value = parseFloat(paddingLeft)// 容器宽度containerWidth.value =containerTarget.value.offsetWidth -parseFloat(paddingLeft) -parseFloat(paddingRight)
}// 列宽 列宽=容器的宽度-所有列间距宽度 / 列数
const columnWidth = ref(0)
// 列间距合计
const columnSpacingTotal = computed(() => {// 如果是5列,则存在 4 个列间距return (props.column - 1) * props.columnSpacing
})
/*** 开始计算*/const useColumnWidth = () => {// 获取容器宽度useContainerWidth()// 计算列宽columnWidth.value =(containerWidth.value - columnSpacingTotal.value) / props.column
}
onMounted(() => {// 计算列宽useColumnWidth()
})/*** 需要图片预加载时*/
// item 高度集合
let itemHeights = []
/*** 监听图片加载完成*/const waitImgComplate = () => {itemHeights = []// 拿到所有元素let itemElements = [...document.getElementsByClassName('m-waterfall-item')]// 获取所有元素的 img 标签const imgElements = getImgElements(itemElements)// 获取所有 img 标签的图片const allImgs = getAllImg(imgElements)onComplateImgs(allImgs).then(() => {// 图片加载完成,获取高度itemElements.forEach((el) => {itemHeights.push(el.offsetHeight)})// 渲染位置useItemLocation()})
}/*** 不需要图片预加载时,计算 item 高度*/const useItemHeight = () => {itemHeights = []// 拿到所有元素let itemElements = [...document.getElementsByClassName('m-waterfall-item')]// 计算 item 高度itemElements.forEach((el) => {// 依据传入数据计算出的 img 高度itemHeights.push(el.offsetHeight)})// 渲染位置useItemLocation()
}/*** 为每个 item 生成位置属性*/
const useItemLocation = () => {
}// 触发计算
watch(() => props.data,(newVal) => {nextTick(() => {if (props.picturePreReading) {waitImgComplate()} else {useItemHeight()}})},{immediate: true,deep: true}
)
</script>

触发计算,定位item位置

libs/waterfall/index.vue

/*** 为每个 item 生成位置属性*/
const useItemLocation = () => {// 遍历数据源props.data.forEach((item, index) => {// 避免重复计算if (item._style) {return}// 生成 _style 属性item._style = {}// leftitem._style.left = getItemLeft()// topitem._style.top = getItemTop()// 指定列高度自增increasingHeight(index)})// 指定容器高度containerHeight.value = getMaxHeight(columnHeightObj.value)
}
/*** 返回下一个 item 的 left*/const getItemLeft = () => {// 最小高度所在的列 * (列宽 + 间距)const column = getMinHeightColumn(columnHeightObj.value)return (column * (columnWidth.value + props.columnSpacing) + containerLeft.value)
}
/*** 返回下一个 item 的 top*/
const getItemTop = () => {// 列高对象中的最小的高度return getMinHeight(columnHeightObj.value)
}
/*** 指定列高度自增*/const increasingHeight = (index) => {// 最小高度所在的列const minHeightColumn = getMinHeightColumn(columnHeightObj.value)// 该列高度自增columnHeightObj.value[minHeightColumn] +=itemHeights[index] + props.rowSpacing
}
/*** 在组件销毁时,清除所有的 _style*/onUnmounted(() => {props.data.forEach((item) => {delete item._style})
})// 触发计算
watch(() => props.data,(newVal) => {// 重置数据源const resetColumnHeight = newVal.every((item) => !item._style)if (resetColumnHeight) {// 构建高度记录容器useColumnHeightObj()}nextTick(() => {if (props.picturePreReading) {waitImgComplate()} else {useItemHeight()}})},{immediate: true,deep: true}
)

完整代码

libs/waterfall/index.vue

<template><!-- 因为当前为 relative 布局,所以需要主动指定高度 --><divclass="relative"ref="containerTarget":style="{ height: containerHeight + 'px' }"><!-- 数据渲染 --><!-- 因为列数不确定,所以需要根据列数计算每列的宽度,所以等待列宽计算完成,并且有了数据源之后进行渲染 --><template v-if="columnWidth && data.length"><!-- 通过动态的 style 来去计算对应的列宽、left、top --><div class="m-waterfall-item absolute duration-300" v-for="(item, index) in data":key="nodeKey ? item[nodeKey] : index" :style="{width: columnWidth + 'px', left: item._style?.left + 'px', top: item._style?.top + 'px'}"><slot :item="item" :width="columnWidth" :index="index" /></div></template><!-- 加载中的提示 --><div v-else>加载中...</div></div>
</template><script setup>
import {getImgElements,getAllImg,onComplateImgs,getMinHeightColumn,getMinHeight,getMaxHeight
} from './utils'const props = defineProps({// 数据源data: {type: Array,required: true},// 唯一标识的 keynodeKey: {type: String},// 列数column: {default: 2,type: Number},// 列间距columnSpacing: {default: 20,type: Number},// 行间距rowSpacing: {default: 20,type: Number},// 是否需要进行图片预读取picturePreReading: {type: Boolean,default: true}
})// 容器的总高度
const containerHeight = ref(0)
// 记录每列高度的容器。key:所在列  val:列高
const columnHeightObj = ref({})
/*** 构建记录各列的高度的对象。*/const useColumnHeightObj = () => {columnHeightObj.value = {}for (let i = 0; i < props.column; i++) {columnHeightObj.value[i] = 0}
}// 容器实例
const containerTarget = ref(null)
// 容器总宽度(不包含 padding、margin、border)
const containerWidth = ref(0)
// 容器左边距,计算 item left 时,需要使用定位
const containerLeft = ref(0)
/*** 计算容器宽度*/const useContainerWidth = () => {const { paddingLeft, paddingRight } = getComputedStyle(containerTarget.value,null)// 容器左边距containerLeft.value = parseFloat(paddingLeft)// 容器宽度containerWidth.value =containerTarget.value.offsetWidth -parseFloat(paddingLeft) -parseFloat(paddingRight)
}// 列宽 列宽=容器的宽度-所有列间距宽度 / 列数
const columnWidth = ref(0)
// 列间距合计
const columnSpacingTotal = computed(() => {// 如果是5列,则存在 4 个列间距return (props.column - 1) * props.columnSpacing
})
/*** 开始计算*/const useColumnWidth = () => {// 获取容器宽度useContainerWidth()// 计算列宽columnWidth.value =(containerWidth.value - columnSpacingTotal.value) / props.column
}
onMounted(() => {// 计算列宽useColumnWidth()
})/*** 需要图片预加载时*/
// item 高度集合
let itemHeights = []
/*** 监听图片加载完成*/const waitImgComplate = () => {itemHeights = []// 拿到所有元素let itemElements = [...document.getElementsByClassName('m-waterfall-item')]// 获取所有元素的 img 标签const imgElements = getImgElements(itemElements)// 获取所有 img 标签的图片const allImgs = getAllImg(imgElements)onComplateImgs(allImgs).then(() => {// 图片加载完成,获取高度itemElements.forEach((el) => {itemHeights.push(el.offsetHeight)})// 渲染位置useItemLocation()})
}/*** 不需要图片预加载时,计算 item 高度*/const useItemHeight = () => {itemHeights = []// 拿到所有元素let itemElements = [...document.getElementsByClassName('m-waterfall-item')]// 计算 item 高度itemElements.forEach((el) => {// 依据传入数据计算出的 img 高度itemHeights.push(el.offsetHeight)})// 渲染位置useItemLocation()
}/*** 为每个 item 生成位置属性*/
const useItemLocation = () => {// 遍历数据源props.data.forEach((item, index) => {// 避免重复计算if (item._style) {return}// 生成 _style 属性item._style = {}// leftitem._style.left = getItemLeft()// topitem._style.top = getItemTop()// 指定列高度自增increasingHeight(index)})// 指定容器高度containerHeight.value = getMaxHeight(columnHeightObj.value)
}
/*** 返回下一个 item 的 left*/const getItemLeft = () => {// 最小高度所在的列 * (列宽 + 间距)const column = getMinHeightColumn(columnHeightObj.value)return (column * (columnWidth.value + props.columnSpacing) + containerLeft.value)
}
/*** 返回下一个 item 的 top*/
const getItemTop = () => {// 列高对象中的最小的高度return getMinHeight(columnHeightObj.value)
}
/*** 指定列高度自增*/const increasingHeight = (index) => {// 最小高度所在的列const minHeightColumn = getMinHeightColumn(columnHeightObj.value)// 该列高度自增columnHeightObj.value[minHeightColumn] +=itemHeights[index] + props.rowSpacing
}
/*** 在组件销毁时,清除所有的 _style*/onUnmounted(() => {props.data.forEach((item) => {delete item._style})
})// 触发计算
watch(() => props.data,(newVal) => {// 重置数据源const resetColumnHeight = newVal.every((item) => !item._style)if (resetColumnHeight) {// 构建高度记录容器useColumnHeightObj()}nextTick(() => {if (props.picturePreReading) {waitImgComplate()} else {useItemHeight()}})},{immediate: true,deep: true}
)
</script><style scoped lang="scss"></style>

解决瀑布流展示不全问题

views/layout/index.vue

<template><div class="h-screen"><!-- 需要在 tailwind 中定义 h-header、h-main 高度 --><headerVue class="h-header"></headerVue>++   <div class="h-main"><mainVue></mainVue></div><floatingVue></floatingVue></div>
</template>

适配移动端,动态列

views/home/components/list/index.vue

<m-waterfall class="px-1 w-full" :data="pexelsList" nodeKey="id" :column="isMobileTerminal ? 2 : 5" :picturePreReading="true">import { isMobileTerminal } from "@/utils/flexible.js"

解决移动端下 navigationBar 不吸顶问题

App.vue

<template><!-- 一级路由出口 --><div class="h-screen w-screen fixed top-0 left-0"><router-view /></div>
</template>

我们希望瀑布流可以有一个响应式的切换,即瀑布流具备响应式的能力

libs/waterfall/index.vue

/*** 监听列数变化,重新构建瀑布流*/
const reset = () => {setTimeout(() => {// 重新计算列宽useColumnWidth()// 重置所有的定位数据,因为 data 中进行了深度监听,所以该操作会触发 data 的 watchprops.data.forEach((item) => {item._style = null})}, 100)
}
watch(() => props.column,() => {if (props.picturePreReading) {// 在 picturePreReading 为 true 的前提下,需要首先为列宽滞空,列宽滞空之后,会取消瀑布流渲染columnWidth.value = 0// 等待页面渲染之后,重新执行计算。否则在 item 没有指定过高度的前提下,计算出的 item 高度会不正确nextTick(reset)} else {reset()}}
)

无需图片预加载时,优化功能处理

当我们将 :picturePreReading="false"改为 false,后刷新页面会出现下面的情况

出现这样现象的原因是一旦我们不进行图片预加载时,对于我们的代码而言我们会直接去获取当前item的高度,那么此时,得到的item的高度,因为没有等待图片加载完成就渲染了,因此此时的高度不包含我们图片的高度,从而导致item整个的高度出现了错误,也就出现了上面的现象

如何解决?

其实在服务端返回数据的时候已经给我们返回了图片的高度和宽度信息的

解决的方式就是当 :picturePreReading="false"改为 false时,我们就需要利用服务端给我们返回的高度来主动的为每个item指定这样的一个高度

views/home/components/list/index.vue

<template v-slot="{ item, width }"><ItemVue :data="item" :width="width"></ItemVue>
</template>

views/home/components/list/item.vue

<!-- 图片 -->
<img class="w-full rounded bg-transparent" :src="data.photo" :style="{height: (width / data.photoWidth) * data.photoHeight + 'px'}" alt="" />

通过以上这样就可以完美解决该问题了

完整瀑布流组件代码

libs/waterfall/index.vue

<template><!-- 因为当前为 relative 布局,所以需要主动指定高度 --><divclass="relative"ref="containerTarget":style="{ height: containerHeight + 'px' }"><!-- 数据渲染 --><!-- 因为列数不确定,所以需要根据列数计算每列的宽度,所以等待列宽计算完成,并且有了数据源之后进行渲染 --><template v-if="columnWidth && data.length"><!-- 通过动态的 style 来去计算对应的列宽、left、top --><div class="m-waterfall-item absolute duration-300" v-for="(item, index) in data":key="nodeKey ? item[nodeKey] : index" :style="{width: columnWidth + 'px', left: item._style?.left + 'px', top: item._style?.top + 'px'}"><slot :item="item" :width="columnWidth" :index="index" /></div></template><!-- 加载中的提示 --><div v-else>加载中...</div></div>
</template><script setup>
import {getImgElements,getAllImg,onComplateImgs,getMinHeightColumn,getMinHeight,getMaxHeight
} from './utils'/*** 使用方式*  <m-waterfall class="px-1 w-full" :data="pexelsList" nodeKey="id" :column="isMobileTerminal ? 2 : 5" :picturePreReading="true"><template v-slot="{ item, width }"><ItemVue :data="item"></ItemVue></template></m-waterfall>*/const props = defineProps({// 数据源data: {type: Array,required: true},// 唯一标识的 keynodeKey: {type: String},// 列数column: {default: 2,type: Number},// 列间距columnSpacing: {default: 20,type: Number},// 行间距rowSpacing: {default: 20,type: Number},// 是否需要进行图片预读取picturePreReading: {type: Boolean,default: true}
})// 容器的总高度
const containerHeight = ref(0)
// 记录每列高度的容器。key:所在列  val:列高
const columnHeightObj = ref({})
/*** 构建记录各列的高度的对象。*/const useColumnHeightObj = () => {columnHeightObj.value = {}for (let i = 0; i < props.column; i++) {columnHeightObj.value[i] = 0}
}// 容器实例
const containerTarget = ref(null)
// 容器总宽度(不包含 padding、margin、border)
const containerWidth = ref(0)
// 容器左边距,计算 item left 时,需要使用定位
const containerLeft = ref(0)
/*** 计算容器宽度*/const useContainerWidth = () => {const { paddingLeft, paddingRight } = getComputedStyle(containerTarget.value,null)// 容器左边距containerLeft.value = parseFloat(paddingLeft)// 容器宽度containerWidth.value =containerTarget.value.offsetWidth -parseFloat(paddingLeft) -parseFloat(paddingRight)
}// 列宽 列宽=容器的宽度-所有列间距宽度 / 列数
const columnWidth = ref(0)
// 列间距合计
const columnSpacingTotal = computed(() => {// 如果是5列,则存在 4 个列间距return (props.column - 1) * props.columnSpacing
})
/*** 开始计算*/const useColumnWidth = () => {// 获取容器宽度useContainerWidth()// 计算列宽columnWidth.value =(containerWidth.value - columnSpacingTotal.value) / props.column
}
onMounted(() => {// 计算列宽useColumnWidth()
})/*** 需要图片预加载时*/
// item 高度集合
let itemHeights = []
/*** 监听图片加载完成*/const waitImgComplate = () => {itemHeights = []// 拿到所有元素let itemElements = [...document.getElementsByClassName('m-waterfall-item')]// 获取所有元素的 img 标签const imgElements = getImgElements(itemElements)// 获取所有 img 标签的图片const allImgs = getAllImg(imgElements)onComplateImgs(allImgs).then(() => {// 图片加载完成,获取高度itemElements.forEach((el) => {itemHeights.push(el.offsetHeight)})// 渲染位置useItemLocation()})
}/*** 不需要图片预加载时,计算 item 高度*/const useItemHeight = () => {itemHeights = []// 拿到所有元素let itemElements = [...document.getElementsByClassName('m-waterfall-item')]// 计算 item 高度itemElements.forEach((el) => {// 依据传入数据计算出的 img 高度itemHeights.push(el.offsetHeight)})// 渲染位置useItemLocation()
}/*** 为每个 item 生成位置属性*/
const useItemLocation = () => {// 遍历数据源props.data.forEach((item, index) => {// 避免重复计算if (item._style) {return}// 生成 _style 属性item._style = {}// leftitem._style.left = getItemLeft()// topitem._style.top = getItemTop()// 指定列高度自增increasingHeight(index)})// 指定容器高度containerHeight.value = getMaxHeight(columnHeightObj.value)
}
/*** 返回下一个 item 的 left*/const getItemLeft = () => {// 最小高度所在的列 * (列宽 + 间距)const column = getMinHeightColumn(columnHeightObj.value)return (column * (columnWidth.value + props.columnSpacing) + containerLeft.value)
}
/*** 返回下一个 item 的 top*/
const getItemTop = () => {// 列高对象中的最小的高度return getMinHeight(columnHeightObj.value)
}
/*** 指定列高度自增*/const increasingHeight = (index) => {// 最小高度所在的列const minHeightColumn = getMinHeightColumn(columnHeightObj.value)// 该列高度自增columnHeightObj.value[minHeightColumn] +=itemHeights[index] + props.rowSpacing
}
/*** 在组件销毁时,清除所有的 _style*/onUnmounted(() => {props.data.forEach((item) => {delete item._style})
})// 触发计算
watch(() => props.data,(newVal) => {// 重置数据源const resetColumnHeight = newVal.every((item) => !item._style)if (resetColumnHeight) {// 构建高度记录容器useColumnHeightObj()}nextTick(() => {if (props.picturePreReading) {waitImgComplate()} else {useItemHeight()}})},{immediate: true,deep: true}
)/*** 监听列数变化,重新构建瀑布流(解决最开始在移动端,点击切换到pc端再切换回移动端后容器宽度不一致问题)*/const reset = () => {setTimeout(() => {// 重新计算列宽useColumnWidth()// 重置所有的定位数据,因为 data 中进行了深度监听,所以该操作会触发 data 的 watchprops.data.forEach((item) => {item._style = null})}, 100)
}
watch(() => props.column,() => {if (props.picturePreReading) {// 在 picturePreReading 为 true 的前提下,需要首先为列宽滞空,列宽滞空之后,会取消瀑布流渲染columnWidth.value = 0// 等待页面渲染之后,重新执行计算。否则在 item 没有指定过高度的前提下,计算出的 item 高度会不正确nextTick(reset)} else {reset()}}
)
</script><style scoped lang="scss"></style>

libs/waterfall/utils.js的代码和上面的--封装公共工具类 里的一样,因此这里省略

瀑布流总结

瀑布流是一个比较复杂的通用组件,因为我们要尽量做到 普适,所以就需要考虑到各种场景下的处理方案,尽量可以满足日常开发的场景,所以这就在原本就复杂的前提下,让这个功能变得更加复杂了。

整体构建过程:

  1. 瀑布流的核心:通过 relative 和 absolute 定位的方式,来控制每个 item 的位置
  2. 影响瀑布流高度的主要元素,通常都是 img 标签
  3. 有些服务端会返回 关键 img 的高度,有些不会,所以我们需要分别处理:
    1. 当服务端 不返回 高度时:我们需要等待 img 加载完成之后,再来计算高度,然后通过得到的高度计算定位。否则会出现高度计算不准确导致定位计算不准确的问题。
    2. 当服务端 返回 高度时:开发者则必须利用此高度为 item 进行高度设定。一旦 item 具备指定高度,那么我们就不需要等待 img 加载的过程,这样效率更高,并且可以使业务逻辑变得更加简单。
  1. 当进行响应式切换时,通用需要区分对应的场景:
    1. 当服务端 不返回 高度时:我们需要 重新执行整个渲染的流程,虽然会耗费一些性能,但是这样可以最大可能的避免出现逻辑错误。让组件拥有更强的普适性。
    2. 当服务端 返回 高度时:我们通用需要重新计算 列宽 和 定位,但是因为 item 具备明确的高度,所以我们可以直接拿到具体的高度,而无需重复整个渲染流程,从而可以实现更多的交互逻辑。比如:位移动画、将来的图片懒加载站位等...

长列表 infinite 组件

构建分析

处理好瀑布流之后,接下来我们就需要来处理对应的长列表功能。

我们知道对于首页中的瀑布流而言,是需要进行长列表展示的,也就是说它是一个分页的数据

那么对于这种分页功能而言,我们又该如何进行实现呢?

想要搞明白这个问题,那么同样我们需要分成两个方面来看:

  1. 长列表实现的原理?
  2. 我们使用长列表时,希望如何进行使用?

长列表实现原理

所谓长列表分页加载,其实指的就是:当滚动到列表底部时,加载数据

那么我们想要实现咱们的长列表组件,围绕这的依然是这句话

那么想要实现该功能,我们需要做的核心的一点就是能够 监听到列表滚动到底部

那么想要监听到列表滚动到底部的话,我们可以利用 IntersectionObserver,该接口可以判断:目标元素与其祖先元素或顶级文档视口(viewport)的交叉状态是否可见

那么我们就可以利用这个特性,把一个元素 置于列表底部,当这个元素可见时则表示 列表滚动到了底部

那么原生的 IntersectionObserver 使用起来比较复杂,所以 vueuse 提供了 useIntersectionObserver 方法

我们使用长列表时,希望如何使用

这个的判断和瀑布流时的判断逻辑是一样的,通过这样的逻辑,可以让我们知道这个组件的 prop 应该如何构建

那么我们期望使用他时是这样的:

<m-infinite-list v-model="" :isFinished="" @onload=""></m-infinite-list>

v-model:当前是否处于加载状态

isFinished:数据是否全部加载完毕

onload:加载下一页数据的触发事件

构建 infinite-list 长列表组件

libs/infinite-list/index.vue

<template><div><!-- 内容 --><slot /><!-- 加载状态 --><div ref="laodingTarget" class="h-6 py-4"><!-- 加载更多图标 --><m-svg-iconv-show="loading"class-name="infinite-load-icon"icon-class="infinite-load"class="w-4 h-4 mx-auto animate-spin"></m-svg-icon><!-- 没有更多数据了 --><p v-if="isFinished" class="text-center text-base text-zinc-400">已经没有更多数据了!</p></div></div>
</template><script setup>
import { useIntersectionObserver, useVModel } from '@vueuse/core'const props = defineProps({// 是否处于加载状态modelValue: {type: Boolean,required: true},// 数据是否全部加载完成isFinished: {type: Boolean,default: false}
})// 处理 loading 状态
const loading = useVModel(props)// 滚动的元素
const laodingTarget = ref(null)
// 记录当前是否在底部(是否交叉)
const targetIsIntersecting = ref(false)
useIntersectionObserver(laodingTarget,([{ isIntersecting }], observerElement) => {// 获取当前交叉状态  isIntersecting表示当前视图是否可见targetIsIntersecting.value = isIntersecting// 触发 loademitLoad()}
)/*** 触发 load*/
const emitLoad = () => {// 当加载更多的视图可见时,同时loading为false,同时 数据尚未全部加载完成----加载更多数据if (targetIsIntersecting.value && !loading.value && !props.isFinished) {// 修改加载数据标记loading.value = true// 触发加载更多行为emits('onLoad')}
}/*** 监听 loading 的变化,解决数据加载完成后,首屏未铺满的问题*/watch(loading, (val) => {// 触发 load,延迟处理,等待 渲染和 useIntersectionObserver 的再次触发setTimeout(() => {emitLoad()}, 100)
})const emits = defineEmits(['onLoad', 'update:modelValue'])
</script><style scoped lang="scss"></style>

应用 infinite-list 结合 waterfall

views/home/components/list/index.vue

<template><div><!-- 长列表处理 --><m-infinite-listv-model="isLoading":isFinished="isFinished"@onLoad="getPexelsData"><m-waterfallclass="px-1 w-full":data="pexelsList"nodeKey="id":column="isMobileTerminal ? 2 : 5":picturePreReading="false"><template v-slot="{ item, width }"><ItemVue :data="item" :width="width"></ItemVue></template></m-waterfall></m-infinite-list></div>
</template><script setup>
import { getPexelsList } from '@/api/pexels'
import ItemVue from './item.vue'
import { isMobileTerminal } from '@/utils/flexible.js'/*** 构建数据请求*/
let query = {page: 1,size: 20,categoryId: '',searchText: ''
}
// 数据是否在加载中
const isLoading = ref(false)
// 数据是否全部加载完成
const isFinished = ref(false)
// 数据源
const pexelsList = ref([])
const getPexelsData = async () => {// 数据全部加载完成则 returnif (isFinished.value) {return}// 完成第一次请求之后,后续请求让 page 自增if (pexelsList.value.length) {query.page += 1}// 触发接口请求const res = await getPexelsList(query)// 初始请求清空数据源(判断当前是否是第一页)if (query.page === 1) {pexelsList.value = res.list} else {pexelsList.value.push(...res.list)}// 判断数据是否全部加载完成if (pexelsList.value.length === res.total) {isFinished.value = true}// 修改 loading 标记isLoading.value = false
}getPexelsData()
</script>

解决首次数据无法铺满全屏时,数据无法继续加载问题

之前我们是一次性加载20条数据,当然看不出问题,那么当我们改为一次加载5条数据时,就会出现该问题了

也就是当首次加载的数据无法铺满全屏时,无法进行继续加载,这是因为在infinite-list组件中,我们通过监听

这个区域是否可见,从而来判断是否需要进行下次的loading,但是当首次加载的数据无法铺满全屏时,isIntersecting也就是是否可见的变量虽然为true,但是它只会被触发一次,也就是说,我们这里的回调

不会进行第二次触发,也就不会去触发第二次loading,也就会出现该现象

因此我们需要监听loading,只要它发生了变化,为true,时就去加载

libs/infinite-list/index.vue

// 记录当前是否在底部(是否交叉)
const targetIsIntersecting = ref(false)
useIntersectionObserver(laodingTarget,([{ isIntersecting }], observerElement) => {// 获取当前交叉状态  isIntersecting表示当前视图是否可见targetIsIntersecting.value = isIntersecting// 触发 loademitLoad()}
)/*** 触发 load*/
const emitLoad = () => {// 当加载更多的视图可见时,同时loading为false,同时 数据尚未全部加载完成----加载更多数据if (targetIsIntersecting.value && !loading.value && !props.isFinished) {// 修改加载数据标记loading.value = true// 触发加载更多行为emits('onLoad')}
}/*** 监听 loading 的变化,解决数据加载完成后,首屏未铺满的问题*/watch(loading, (val) => {// 触发 load,延迟处理,等待 渲染和 useIntersectionObserver 的再次触发setTimeout(() => {emitLoad()}, 100)
})

图片懒加载

上面我们已经实现了瀑布流和上列表加载

接下来我们将来处理图片懒加载

懒加载构建原因+实现原理

如果此时将请求里的 size 改为 100,

我们可以看到这里的请求次数会大于我们的100

那么多余的这些请求是不是就会显得比较浪费

我们不想有这种浪费,那么就可以利用 图片懒加载 功能进行实现

图片懒加载原理:

当图片不可见时,不加载图片。盯那个图片可见时,才去加载图片。

如何实现?

我们可以 监听所有图片是否被可见,如果图片处于不可见状态,那么就不加载图片,如果图片处于可见状态,那么就开始加载图片。

而这个功能的实现关键就是 IntersectionObserver。

通用指令:实现图片懒加载

directives/modules/lazy.js

import { useIntersectionObserver } from '@vueuse/core'export default {// 图片懒加载:在用户无法看到图片时,不加载图片,在用户可以看到图片后加载图片// 如何判断用户是否看到了图片:useIntersectionObserver// 如何做到不加载图片(网络):img 标签渲染图片,指的是 img 的 src 属性,src 属性是网络地址时,则会从网络中获取该图片资源。那么如果 img 标签不是网络地址呢?把该网络地址默认替换为非网络地址,然后当用户可见时,在替换成网络地址。mounted(el) {// 1. 拿到当前 img 标签的 srcconst imgSrc = el.src// 2. 把 img 标签的 src 替换为本地地址el.src = ''const { stop } = useIntersectionObserver(el, ([{ isIntersecting }]) => {if (isIntersecting) {el.src = imgSrcstop()}})}
}

directives/index.js

/*** 全局指令注册*/
// export default {
//   async install(app) {
//     // https://cn.vitejs.dev/guide/features.html#glob-import
//     // import.meta.globEager 为同步导入  vite3起已废除,改为glob
//     const directives = import.meta.glob('./modules/*.js')
//     for (const [key, value] of Object.entries(directives)) {
//       // 拼接组件注册的 name
//       const arr = key.split('/')
//       const directiveName = arr[arr.length - 1].replace('.js', '')
//       // 完成注册
//       app.directive(directiveName, value.default)
//     }
//   }
// }import lazy from "./modules/lazy.js";
export default function directive(app) {app.directive('lazy', lazy)
}

main.js

import directive from './directives'directive(app);

然后我们需要给涉及到的地方加上懒加载指令--v-lazy

指定彩色占位图

utils/color.js

/*** 生成随机色值*/
export const randomRGB = () => {const r = Math.floor(Math.random() * 255)const g = Math.floor(Math.random() * 255)const b = Math.floor(Math.random() * 255)return `rgb(${r}, ${g}, ${b})`
}

views/home/components/list/item.vue

<div class="relative w-full rounded cursor-zoom-in group" :style="{backgroundColor: randomRGB()}"><!-- 图片 -->import { randomRGB } from "@/utils/color.js";

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/57389.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录算法训练营Day38 | 62. 不同路径、63. 不同路径 II

目录 62. 不同路径 63. 不同路径 II 62. 不同路径 题目 62. 不同路径 - 力扣&#xff08;LeetCode&#xff09; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到…

免费开源的微信开发框架

近年来&#xff0c;随着人工智能技术的快速发展&#xff0c;聊天机器人在各个领域得到了广泛的应用。在社交媒体中&#xff0c;自动回复成为了一个流行的功能&#xff0c;让用户可以方便地与机器人进行互动。gewe框架&#xff0c;一个开源的微信聊天机器人框架&#xff0c;实现…

LeetCode第100题:相同的树

文章目录 &#x1f60a;1.题目&#x1f609;2.解法 &#x1f60a;1.题目 尝试一下该题 &#x1f609;2.解法 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ bool isSameTree…

PeakLight恶意软件活动分析

PeakLight恶意软件 在过去几个月中&#xff0c;PeakLight恶意软件因其通过CDN链接&#xff08;内容交付网络&#xff09;进行的激烈感染活动而备受关注。这种活动负责诱使用户执行编码的命令行或通过伪造的验证码和/或验证门户执行恶意工件。 它旨在通过不同的持久性和规避保护…

【电商项目】1分布式基础篇

1 项目简介 1.2 项目架构图 1.2.1 项目微服务架构图 1.2.2 微服务划分图 2 分布式基础概念 3 Linux系统环境搭建 查看网络IP和网关 linux网络环境配置 补充P123&#xff08;修改linux网络设置&开启root密码访问&#xff09; 设置主机名和hosts映射 主机名解析过程分析&…

S2 引擎-大数据分析表格

一、特性 1&#xff09;开箱即用&#xff1a;提供不同场景下开箱即用的 React, Vue3 表组件及配套分析组件&#xff0c;只需要简单的配置即可轻松实现复杂场景。 2&#xff09;多维交叉分析&#xff1a; 告别单一分析维度&#xff0c;全面拥抱任意维度的自由组合分析。 3&#…

数据脱敏方案总结

什么是数据脱敏 数据脱敏的定义 数据脱敏百度百科中是这样定义的&#xff1a; 数据脱敏&#xff0c;指对某些敏感信息通过脱敏规则进行数据的变形&#xff0c;实现敏感隐私数据的可靠保护。这样就可以在开发、测试和其它非生产环境以及外包环境中安全地使用脱敏后的真实数据集…

YOLOv11模型改进-注意力-引入简单无参数注意力模块SimAM 提升小目标和遮挡检测

本篇文章将介绍一个新的改进机制——卷积和注意力融合模块SimAM &#xff0c;并阐述如何将其应用于YOLOv11中&#xff0c;显著提升模型性能。首先&#xff0c;SimAM 是一种用于卷积神经网络的简单且无参数的注意力模块&#xff0c;它基于神经科学理论定义能量函数来计算 3-D 注…

若依框架的下载与配置

1. 若依版本 RuoYi-Vue前后端分离版。 2. 框架下载 2.1 后端框架下载 https://gitee.com/y_project/RuoYi-Vue 2.2 前端框架下载 https://github.com/yangzongzhuan/RuoYi-Vue3 3. 数据库配置 3.1 创建数据库 基于MySQL数据库&#xff0c;创建数据库&#xff1a;ry-vu…

Top Down 2D Dojo Chip Set

以下是对这款 2D 微型像素关卡芯片集的简洁介绍&#xff1a; 这是一款基于 8x8 像素网格的 2D 微型像素关卡芯片集&#xff0c;采用经典的像素风格。它包含 66 个.png 格式的芯片&#xff0c;涵盖多种墙壁和门的变体&#xff0c;非常适合用于快速搭建游戏原型的道场关卡。利用…

gazebo显示urdf

最近想要将urdf显示在gazebo中。也就是实现下面这样的效果。 因为我看到网上&#xff0c;很多都是在rviz中显示urdf文件。 <launch><!-- 将 Urdf 文件的内容加载到参数服务器 --><param name"robot_description" textfile"$(find urdf_gazebo)/…

【GAMES101笔记速查——Lecture 17 Materials and Appearances】

目录 1 材质和外观 1.1 自然界中&#xff0c;外观是光线和材质共同作用的结果 1.2 图形学中&#xff0c;什么是材质&#xff1f; 1.2.1 渲染方程严格正确&#xff0c;其中BRDF项决定了物体的材质 1.2.2 漫反射材质 &#xff08;1&#xff09;如何定义漫反射系数&#xff1…

mysql8以上版本第一次下载后的登录问题

mysql8以上版本第一次下载后的登录问题 在官网下载mysql后&#xff0c;按照MySQL下载和安装教程操作就可以 如果出现问题&#xff0c;参考https://blog.csdn.net/weixin_63107823/article/details/136588474 注意ini配置文件&#xff0c;如果你是复制的别人的代码&#xff0…

ESD防静电闸机如何保护汽车电子产品

随着汽车电子技术的快速发展&#xff0c;汽车中集成了越来越多的电子设备&#xff0c;如车载信息娱乐系统、自动驾驶传感器、驾驶辅助系统等。静电放电可能导致电子组件的损坏、性能下降&#xff0c;甚至使整个系统失效。因此&#xff0c;如何有效保护汽车电子产品免受静电损害…

2024 四川省大学生信息安全技术大赛 安恒杯 部分 WP

文章目录 一、前言二、MISCunzip-png拓展 第47张图片重要的文件 三、WEB四、CRYPTO五、REVERSE 一、前言 WP不完整&#xff0c;仅供参考&#xff01; 除WEB外&#xff0c;其余附件均已打包完毕&#xff0c;在这里也是非常感谢师傅的附件支持&#xff01; 123网盘下载&#x…

Web应用框架-Django应用基础(2)

1.请求响应对象 1.1 请求对象HttpRequest测试 #hello\views def http_request(request):#1.获得请求方式print(request.method)#2.获得请求头信息#2.1 获取META中的请求头信息headers request.METAprint(headers)#2.2 获取请求头信息的内容ua request.META.get(HTTP_USER_AG…

[已解决] pycharm添加本地conda虚拟环境 + 配置解释器 - pycharm找不到conda可执行文件

目录 问题&#xff1a; 方法&#xff1a; 补充&#xff1a;创建conda虚拟环境 参考文档&#xff1a;pycharm找不到conda可执行文件怎么办&#xff1f;-CSDN 问题&#xff1a; 1.显示&#xff1a;未为项目配置 Python 解释器 2.想在pycharm中使用本地创建的虚拟环境 方法&a…

通过前端UI界面创建VUE项目

通过前端UI界面创建VUE项目&#xff0c;是比较方面的一种方式&#xff0c;下面我们详细分析一下流程&#xff1a; 1、找到合适目录 右键鼠标&#xff0c;点击在终端打开 2、开始创建 输入 vue ui 浏览器弹出页面 3、点击Create项目 显示已有文件列表&#xff0c;另外可以点击…

ChatGLM4重磅开源! 连忙实操测试一波,效果惊艳,真的好用!附带最新相关资料和喂饭级实操推理教程!!

本文目录 GLM4重磅开源啦 GLM4系列版本介绍 GLM4大模型能力测评结果 经典测评任务结果 长文本能力 工具调用能力 多模态能力 手把手实操GLM-4-9B-Chat推理预测&&效果展示 GLM4运行硬件和环境要求 配置对应的库环境 使用peftbitsandbytes 进行4位量化推理 进…

【大模型报告】2024年中国AI大模型场景探索及产业应用调研报告【免费下载】

1.行业概况 市场规模&#xff1a; 2023年中国AI大模型行业规模达到147亿元&#xff0c;预计到2028年将突破1000亿元&#xff0c;复合增速超过50%。 应用价值&#xff1a; AI大模型技术能够提升生产要素的产出效率&#xff0c;并提高数据要素在生产要素组合中的地位。 应用路…