马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
最近在研发AI副业项目平台,然后本身设计了一个瀑布流组件,可以随意调解展示的列数、懒加载、每页滚动数量、高度、点击效果等。
一、效果
先看看效果如何,如何随意调解4列、5列、6列、N列展示。
二、实现方法
现建立components/waterfall/index.vue组件
- <template>
- <div class="waterfall-container" ref="containerRef" @scroll="handleScroll">
- <div class="waterfall-list">
- <div
- class="waterfall-item"
- v-for="(item, index) in resultList"
- :key="index"
- :style="{
- width: `${item.width}px`,
- height: `${item.height}px`,
- transform: `translate3d(${item.x}px, ${item.y}px, 0)`,
- }"
- >
- <slot name="item" v-bind="item"></slot>
- </div>
- <div v-if="isEnd" class="no-more-data">暂无更多数据</div>
- </div>
- </div>
- </template>
- <script setup>
- import { ref, onMounted, computed, onUnmounted, watch } from "vue";
- import { throttle, debounce } from "@/utils/waterfall/utils.js";
- const props = defineProps({
- gap: {
- type: Number,
- default: 10,
- },
- columns: {
- type: Number,
- default: 3,
- },
- bottom: {
- type: Number,
- default: 0,
- },
- images: {
- type: Array,
- default: () => [],
- },
- fetchMoreImages: {
- type: Function,
- required: true,
- },
- isEnd: {
- type: Boolean,
- default: false,
- },
- });
- const containerRef = ref(null);
- const cardWidth = ref(0);
- const columnHeight = ref(new Array(props.columns).fill(0));
- const resultList = ref([]);
- const loading = ref(false);
- const minColumn = computed(() => {
- let minIndex = -1,
- minHeight = Infinity;
- columnHeight.value.forEach((item, index) => {
- if (item < minHeight) {
- minHeight = item;
- minIndex = index;
- }
- });
- return {
- minIndex,
- minHeight,
- };
- });
- const handleScroll = throttle(() => {
- const { scrollTop, clientHeight, scrollHeight } = containerRef.value;
- const bottom = scrollHeight - clientHeight - scrollTop;
- if (bottom <= props.bottom && !props.isEnd) {
- !loading.value && props.fetchMoreImages();
- }
- });
- const getList = (list) => {
- return list.map((x, index) => {
- const cardHeight = Math.floor((x.height * cardWidth.value) / x.width);
- const { minIndex, minHeight } = minColumn.value;
- const isInit = index < props.columns && resultList.value.length < props.columns;
- if (isInit) {
- columnHeight.value[index] = cardHeight + props.gap;
- } else {
- columnHeight.value[minIndex] += cardHeight + props.gap;
- }
- return {
- width: cardWidth.value,
- height: cardHeight,
- x: isInit
- ? index % props.columns !== 0
- ? index * (cardWidth.value + props.gap)
- : 0
- : minIndex % props.columns !== 0
- ? minIndex * (cardWidth.value + props.gap)
- : 0,
- y: isInit ? 0 : minHeight,
- image: x,
- };
- });
- };
- const resizeObserver = new ResizeObserver(() => {
- handleResize();
- });
- const handleResize = debounce(() => {
- const containerWidth = containerRef.value.clientWidth;
- cardWidth.value =
- (containerWidth - props.gap * (props.columns - 1)) / props.columns;
- columnHeight.value = new Array(props.columns).fill(0);
- resultList.value = getList(resultList.value);
- });
- const init = () => {
- if (containerRef.value) {
- const containerWidth = containerRef.value.clientWidth;
- cardWidth.value =
- (containerWidth - props.gap * (props.columns - 1)) / props.columns;
- resultList.value = getList(props.images);
- resizeObserver.observe(containerRef.value);
- }
- };
- watch(() => props.images, (newImages) => {
- const newList = getList(newImages);
- resultList.value = [...resultList.value, ...newList];
- });
- onMounted(() => {
- init();
- });
- onUnmounted(() => {
- containerRef.value && resizeObserver.unobserve(containerRef.value);
- });
- </script>
- <style lang="scss">
- .waterfall {
- &-container {
- width: 100%;
- height: 100%;
- overflow-y: scroll;
- overflow-x: hidden;
- }
- &-list {
- width: 100%;
- position: relative;
- }
- &-item {
- position: absolute;
- left: 0;
- top: 0;
- box-sizing: border-box;
- transition: all 0.3s;
- }
- .no-more-data {
- text-align: center;
- padding: 20px;
- color: #999;
- font-size: 14px;
- }
- }
- </style>
复制代码 此中@/utils/waterfall/utils.js如下
- // 用于模拟接口请求
- export const getRemoteData = (data = '获取数据', time = 2000) => {
- return new Promise((resolve) => {
- setTimeout(() => {
- console.log(`模拟获取接口数据`, data)
- resolve(data)
- }, time)
- })
- }
- // 获取数组随机项
- export const getRandomElement = (arr) => {
- var randomIndex = Math.floor(Math.random() * arr.length);
- return arr[randomIndex];
- }
- // 指定范围随机数
- export const getRandomNumber = (min, max) => {
- return Math.floor(Math.random() * (max - min + 1) + min);
- }
- // 节流
- export const throttle = (fn, time) => {
- let timer = null
- return (...args) => {
- if (!timer) {
- timer = setTimeout(() => {
- timer = null
- fn.apply(this, args)
- }, time)
- }
- }
- }
- // 防抖
- export const debounce = (fn, time) => {
- let timer = null
- return (...args) => {
- clearTimeout(timer)
- timer = setTimeout(() => {
- fn.apply(this, args)
- }, time)
- }
- }
复制代码 调用组件
- <template>
- <div>
- <div class="page-dall">
- <el-row>
- <el-col :span="6">
- <div class="inner">
- <div class="sd-box">
- <h2>DALL-E 创作中心</h2>
- <div>
- <el-form label-position="left">
- <div style="padding-top: 10px">
- <el-form-item :label-style="{ color: 'white' }" label="图片尺寸">
- <template #default>
- <div>
- <el-select v-model="selectedValue" @change="updateSize" style="width:176px">
- <el-option label="1024*1024" value="1024*1024"/>
- <el-option label="1972*1024" value="1972*1024"/>
- <el-option label="1024*1972" value="1024*1972"/>
- </el-select>
- </div>
- </template>
- </el-form-item>
- </div>
- <div style="padding-top: 10px">
- <div class="param-line">
- <el-input
- v-model="dalleParams.prompt"
- :autosize="{ minRows: 4, maxRows: 6 }"
- type="textarea"
- ref="promptRef"
- placeholder="请在此输入绘画提示词,系统会自动翻译中文提示词,高手请直接输入英文提示词"
- />
- </div>
- </div>
- </el-form>
- </div>
- <div class="submit-btn">
- <el-button color="#ffffff" :loading="loading" :dark="false" round @click="generate">
- 立即生成
- </el-button>
- </div>
- </div>
- </div>
- </el-col>
- <el-col :span="18">
- <div class="inner">
- <div class="right-box">
- <h2>创作记录</h2>
- <div>
- <el-form label-position="left">
- <div class="container">
- <WaterFall :columns="columns" :gap="10" :images="images" :fetchMoreImages="fetchMoreImages" :isEnd="isEnd">
- <template #item="{ image }">
- <div class="card-box">
- <el-image :src="image.url" @click="previewImg(image)" alt="waterfall image" fit="cover" style="width: 100%; height: 100%;cursor:pointer;" loading="lazy"></el-image>
-
-
- </div>
- </template>
- </WaterFall>
- </div>
- </el-form>
- </div>
- </div>
- </div>
- </el-col>
- </el-row>
- </div>
- <el-image-viewer @close="() => { previewURL = '' }" v-if="previewURL !== ''" :url-list="[previewURL]"/>
- </div>
- </template>
- <script lang="ts" setup>
- import { ElUpload, ElImage, ElDialog, ElRow, ElCol, ElButton, ElIcon, ElTag, ElInput, ElSelect, ElTooltip, ElForm, ElFormItem, ElOption ,ElImageViewer} from "element-plus";
- import {Delete, InfoFilled, Picture} from "@element-plus/icons-vue";
- import feedback from "~~/utils/feedback";
- import { useUserStore } from '@/stores/user';
- import WaterFall from '@/components/waterfall/index.vue';
- import * as xmgai from "~~/api/ai";
- // 获取图片前缀
- const config = useRuntimeConfig();
- const filePrefix = config.public.filePrefix;
- const router = useRouter();
- const selectedValue = ref('1024*1024');
- const previewURL = ref("")
- const loading = ref(false);
- // 请求参数
- const dalleParams = reactive({
- size:"1024*1024",
- prompt: ""
- });
- // 创建绘图任务
- const promptRef = ref(null);
- const updateSize = () => {
- dalleParams.size = selectedValue.value;
- };
- const generate = async () => {
- loading.value = true;
- if (dalleParams.prompt === '') {
- promptRef.value.focus();
- loading.value = false;
- return feedback.msgError("请输入绘画提示词!");
-
- }
- const ctdata = await xmgai.dalle3(dalleParams);
- console.info("ctdata",ctdata);
- if (ctdata.code === 0) {
- feedback.msgError(ctdata.msg);
- loading.value = false;
- return [];
- }
- if (ctdata.code === 1) {
- // 获取新生成的图片地址
- const newImage = {
- url: filePrefix + ctdata.data,
- width: 300 + Math.random() * 300,
- height: 400 + Math.random() * 300,
- };
- // 将新图片插入到 images 数组的开头
- // 将新图片插入到 images 数组的开头
- images.value = [newImage, ...images.value];
- // 将 WaterFall 组件的滚动条滚动到顶部
- nextTick(() => {
- const waterfallContainer = document.querySelector('.waterfall-container');
- if (waterfallContainer) {
- waterfallContainer.scrollTop = 0;
- }
- });
- feedback.msgSuccess(ctdata.msg);
- loading.value = false;
- }
- };
- const images = ref([]);
- const pageNo = ref(1);
- const pageSize = ref(10);
- const isEnd = ref(false);
- // 请求参数
- const paramsCreate = reactive({
- aiType: "dalle3",
- pageNo: pageNo.value,
- pageSize: pageSize.value,
- });
- const fetchImages = async () => {
- const ctdata = await xmgai.aiList(paramsCreate);
- if (ctdata.code === 0) {
- feedback.msgError(ctdata.msg);
- return [];
- }
- if (ctdata.code === 1) {
- const data = ctdata.data.lists;
- if (data.length === 0) {
- isEnd.value = true;
- return [];
- }
- paramsCreate.pageNo++;
- return data.map(item => ({
- ...item, // 保留所有原始字段
- url: filePrefix + item.localUrls,
- width: 300 + Math.random() * 300,
- height: 400 + Math.random() * 300,
- }));
- }
- };
- const fetchMoreImages = async () => {
- if (isEnd.value) {
- return; // 如果已经没有更多数据了,直接返回
- }
- const newImages = await fetchImages();
- images.value = [...newImages];
- };
- // 列数设置
- const columns = ref(4); // 你可以在这里修改列数
- //放大预览
- const previewImg = (item) => {
- console.info("item",item.url);
- previewURL.value = item.url
- }
- onMounted(async () => {
- const initialImages = await fetchImages();
- images.value = initialImages;
- });
- </script>
- <style scoped>
- .page-dall {
- background-color: #0c1c9181;
- border-radius: 10px; /* 所有角的圆角大小相同 */
- border: 1px solid #3399FF;
- }
- .page-dall .inner {
- display: flex;
- }
- .page-dall .inner .sd-box {
- margin: 10px;
- background-color: #222542b4;
- width: 100%;
- padding: 10px;
- border-radius: 10px;
- color: #ffffff;
- font-size: 14px;
- }
- .page-dall .inner .sd-box h2 {
- font-weight: bold;
- font-size: 20px;
- text-align: center;
- color: #ffffff;
- }
- .page-dall .inner .right-box {
- margin: 10px;
- background-color: #222542b4;
- width: 100%;
- padding: 10px;
- border-radius: 10px;
- color: #ffffff;
- font-size: 14px;
- }
- .page-dall .inner .right-box h2 {
- font-weight: bold;
- font-size: 20px;
- text-align: center;
- color: #ffffff;
- }
- .submit-btn {
- padding: 10px 15px 0 15px;
- text-align: center;
- }
- ::v-deep(.el-form-item__label) {
- color: white !important;
- }
- .container {
- height: 600px;
- border: 2px solid #000;
- margin-top: 10px;
- margin-left: auto;
- margin-right: auto; /* 添加居中处理 */
- }
- .card-box {
- position: relative;
- width: 100%;
- height: 100%;
- border-radius: 4px;
- overflow: hidden;
- }
- .card-box img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- .card-box .remove {
- display: none;
- position: absolute;
- right: 10px;
- top: 10px;
- }
- .card-box:hover .remove {
- display: block;
- }
- </style>
复制代码 项目源码和题目交流,可以通过文末名片找到我。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |