Skip to content

Commit

Permalink
feat(component): 素材瀑布流布局 (#471)
Browse files Browse the repository at this point in the history
* fix(component): 素材瀑布流布局样式

* fix(component):  新增空瀑布流组件

* feat(component): 瀑布流增加样式

* feat(component): 修改返回数据

* feat(component): 新增瀑布流组件

* feat(component): 修改素材列表页面

* feat(component): getPageData接口修改

* feat(component): 修改瀑布流样式

* feat(component): 修改渲染层

* feat(component): 删除冗余代码

* feat(component): 重构逻辑

* feat(component): 移除注释

* feat(component): 加入loading组件

* feat(component): 加入Message组件

* feat(component): 增加瀑布流组件props

* feat(component): 完成关键词瀑布流查询

* feat(component): 删除冗余

* feat(component): 修改瀑布流布局
  • Loading branch information
AliceLanniste authored Jul 23, 2024
1 parent 3730b60 commit 78d2ec2
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 34 deletions.
207 changes: 207 additions & 0 deletions src/components/common/masonry.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
<template>
<div class="masonry-container" ref="containerRef" @scroll="handleScroll">
<div class="masonry-list">
<div
class="masonry-item"
v-for="(item, index) in dataState.cardList"
:key="item.id"
:style="{
width: `${dataState.cardPos[index].width}px`,
height: `${dataState.cardPos[index].height}px`,
transform: `translate(${dataState.cardPos[index].x}px, ${dataState.cardPos[index].y}px)`,
}"
>
<slot name="item" :item="item" :index="index"></slot>
</div>
</div>
<Spin size="large" fix :show="dataState.loading"></Spin>
</div>
</template>

<script setup lang="ts">
interface IVirtualWaterFallProps {
gap: number;
column: number;
bottom: number;
pageSize: number;
request?: (page: number, pageSize: number) => Promise<ICardItem[]>;
}
interface ICardItem {
id: number | string;
url: string;
width: number;
height: number;
[key: string]: any;
}
interface ICardPos {
width: number;
height: number;
x: number;
y: number;
}
const props = defineProps<IVirtualWaterFallProps>();
defineSlots<{
item(props: { item: ICardItem; index: number }): any;
}>();
const dataState = reactive({
isFinish: false,
page: 1,
cardWidth: 0,
cardList: [] as ICardItem[],
cardPos: [] as ICardPos[],
loading: false,
columnHeight: new Array(props.column).fill(0) as number[],
});
const containerRef = ref<HTMLDivElement | null>(null);
const getCardList = async (page: number, pageSize: number) => {
if (dataState.isFinish) {
return;
}
dataState.loading = true;
const list = await props.request(page, pageSize);
dataState.page++;
dataState.loading = false;
if (!list.length) {
dataState.isFinish = true;
return;
}
dataState.cardList = [...dataState.cardList, ...list];
computedCardPos(list);
};
const computedWidth = async () => {
const containerWidth = containerRef.value.clientWidth;
dataState.cardWidth = (containerWidth - props.gap * (props.column - 1)) / props.column;
await getCardList(dataState.page, props.pageSize);
};
const getkeyWordSearch = async () => {
dataState.cardList = [];
dataState.page = 1;
dataState.cardPos = [];
if (dataState.isFinish) {
dataState.isFinish = false;
}
await getCardList(dataState.page, props.pageSize);
};
const init = async () => {
if (containerRef.value) {
await computedWidth();
}
};
const minColumn = computed(() => {
let minIndex = -1,
minHeight = Infinity;
dataState.columnHeight.forEach((item, index) => {
if (item < minHeight) {
minHeight = item;
minIndex = index;
}
});
return {
minIndex,
minHeight,
};
});
const computedCardPos = (list: ICardItem[]) => {
list.forEach((item, index) => {
const cardHeight = Math.floor((item.height * dataState.cardWidth) / item.width);
if (index < props.column && dataState.cardList.length <= props.pageSize) {
dataState.cardPos.push({
width: dataState.cardWidth,
height: cardHeight,
x: index ? index * (dataState.cardWidth + props.gap) : 0,
y: 0,
});
dataState.columnHeight[index] = cardHeight + props.gap;
} else {
const { minIndex, minHeight } = minColumn.value;
dataState.cardPos.push({
width: dataState.cardWidth,
height: cardHeight,
x: minIndex ? minIndex * (dataState.cardWidth + props.gap) : 0,
y: minHeight,
});
dataState.columnHeight[minIndex] += cardHeight + props.gap;
}
});
};
const handleScroll = rafThrottle(() => {
const { scrollTop, clientHeight, scrollHeight } = containerRef.value!;
const bottom = scrollHeight - clientHeight - scrollTop;
if (bottom <= props.bottom) {
!dataState.loading && getCardList(dataState.page, props.pageSize);
}
}, 50);
function rafThrottle(fn) {
let lock = false;
return function (this: any, ...args: any[]) {
if (lock) return;
lock = true;
window.requestAnimationFrame(() => {
fn.apply(this, args);
lock = false;
});
};
}
onMounted(() => {
init();
});
defineExpose({
getkeyWordSearch,
});
</script>

<style scoped lang="less">
.masonry {
&-container {
width: 100%;
height: 100%;
overflow-y: scroll;
overflow-x: hidden;
}
&-list {
position: relative;
width: 100%;
}
&-item {
position: absolute;
top: 0;
left: 0;
box-sizing: border-box;
}
}
:deep(.ivu-message) {
margin-top: 50%;
.ivu-message-notice {
text-align: left;
padding-left: 20%;
}
}
:deep(.ivu-divider) {
position: absolute;
top: 95%;
.ivu-divider-horizontal.ivu-divider-with-text-center:before {
width: 30%;
}
}
</style>
54 changes: 25 additions & 29 deletions src/components/importTmpl.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<div>
<!-- 搜索组件 -->
<div class="search-box">
<Select class="select" v-model="typeValue" @on-change="startGetList" :disabled="pageLoading">
<Select class="select" v-model="typeValue" @on-change="keywordSearch" :disabled="pageLoading">
<Option v-for="item in typeList" :value="item.value" :key="item.value">
{{ item.label }}
</Option>
Expand All @@ -21,37 +21,34 @@
v-model="searchKeyWord"
search
:disabled="pageLoading"
@on-search="startGetList"
@on-search="keywordSearch"
/>
</div>
<!-- 列表 -->
<div style="height: calc(100vh - 108px)" id="myTemplBox">
<Scroll
key="mysscroll"
v-if="showScroll"
:on-reach-bottom="nextPage"
:height="scrollHeight"
:distance-to-edge="[-1, -1]"
<masonry
:request="getPageData"
:gap="9"
:page-size="10"
:column="column"
:bottom="20"
ref="masonryRef"
>
<!-- 列表 -->
<div class="list-box">
<Tooltip :content="info.name" v-for="info in pageData" :key="info.src" placement="top">
<template #item="{ item }">
<Tooltip transfer :content="item.name" :key="item.src" placement="top">
<div class="tmpl-img-box">
<Image
lazy
:src="info.previewSrc"
:src="item.previewSrc"
fit="contain"
height="100%"
:alt="info.name"
@click="beforeClearTip(info)"
:alt="item.name"
@click="beforeClearTip(item)"
/>
</div>
</Tooltip>
</div>
<Spin size="large" fix :show="pageLoading"></Spin>

<Divider plain v-if="isDownBottm">已经到底了</Divider>
</Scroll>
</template>
</masonry>
</div>
</div>
</template>
Expand All @@ -60,27 +57,22 @@
import useSelect from '@/hooks/select';
import usePageList from '@/hooks/pageList';
import { Spin, Modal } from 'view-ui-plus';

import masonry from './common/masonry.vue';
import { useI18n } from 'vue-i18n';
import { useRouter, useRoute } from 'vue-router';
const router = useRouter();
const route = useRoute();
const { t } = useI18n();
const { canvasEditor } = useSelect();

const column = ref(2);
const {
startPage,
getTypeList,
getPageData,
typeValue,
typeText,
typeList,
pageLoading,
pageData,
searchKeyWord,
isDownBottm,
startGetList,
nextPage,
showScroll,
scrollHeight,
getInfo,
} = usePageList({
typeUrl: 'templ-types',
Expand All @@ -91,7 +83,11 @@ const {
scrollElement: '#myTemplBox',
fields: ['name'],
});
const masonryRef = ref(null);

const keywordSearch = () => {
masonryRef.value.getkeyWordSearch();
};
// 替换提示
const beforeClearTip = (info) => {
Modal.confirm({
Expand All @@ -104,7 +100,7 @@ const beforeClearTip = (info) => {
};

onMounted(() => {
startPage();
getTypeList();
getTemplInfo();
});

Expand Down
22 changes: 17 additions & 5 deletions src/hooks/pageList.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,11 @@ export default function usePageList({
pageLoading.value = false;
};

const getPageData = async () => {
const getPageData = async (page, pageSize) => {
pageLoading.value = true;
try {
const params = getPageParams(
page.value,
page,
typeValue.value,
searchKeyWord.value,
searchTypeKey,
Expand All @@ -146,30 +146,40 @@ export default function usePageList({
fields
);
const res = await pageApi(listUrl, params);
pageLoading.value = false;
const list = res.data.data.map((item) => {
let small = item.attributes.img.data.attributes.formats.small;
let thumbnail = item.attributes.img.data.attributes.formats.thumbnail;
const height = small ? small.height : thumbnail.height;
const width = small ? small.width : thumbnail.width;
return {
id: item.id,
name: item.attributes.name,
desc: item.attributes.desc,
json: item.attributes?.json,
height: height,
width: width,
src: getMaterialInfoUrl(item.attributes.img),
previewSrc: getMaterialPreviewUrl(item.attributes.img),
};
});
Object.keys(res.data.meta.pagination).forEach((key) => {
pagination[key] = res.data.meta.pagination[key];
});
pageData.value = [...pageData.value, ...list];
if (list.length) {
return list;
} else {
return [];
}
} catch (error) {
console.log(error);
}
pageLoading.value = false;
};

const startGetList = () => {
pageData.value = [];
page.value = 1;
getPageData();
getPageData(1, 10);
};

const nextPage = () => {
Expand Down Expand Up @@ -203,6 +213,8 @@ export default function usePageList({
isDownBottm,
startGetList,
nextPage,
getTypeList,
getPageData,
scrollHeight,
showScroll,
getInfo,
Expand Down

0 comments on commit 78d2ec2

Please sign in to comment.