1
0
mirror of https://github.com/sendevia/website.git synced 2026-03-05 23:32:45 +08:00

add: ArticleMasonry component

This commit is contained in:
2025-12-05 22:57:41 +08:00
parent 6e5dd124b4
commit 341da38027
3 changed files with 114 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
<script setup lang="ts">
import { usePostStore, type PostData } from "../stores/posts";
import { useGlobalData } from "../composables/useGlobalData";
import { computed, onMounted, onUnmounted, ref } from "vue";
const postsStore = usePostStore();
const articlesList = computed(() => postsStore.posts || []);
const { theme } = useGlobalData();
// 定义断点配置:屏幕宽度 -> 列数
const breakpoints = {
1600: 3,
1200: 3,
840: 2,
0: 1,
};
const columnCount = ref(2);
// 根据屏幕宽度更新列数
const updateColumnCount = () => {
const width = window.innerWidth;
const match = Object.keys(breakpoints)
.map(Number)
.sort((a, b) => b - a)
.find((bp) => width > bp);
columnCount.value = match !== undefined ? breakpoints[match as keyof typeof breakpoints] : 1;
};
// 将文章列表拆分成 N 个数组
const masonryGroups = computed(() => {
const groups: PostData[][] = Array.from({ length: columnCount.value }, () => []);
articlesList.value.forEach((item, index) => {
const groupIndex = index % columnCount.value;
groups[groupIndex].push(item);
});
return groups;
});
// 图片处理逻辑
const getArticleImage = (item: PostData): string[] => {
if (item.impression && item.impression.length > 0) {
return item.impression;
}
const themeValue = theme.value;
if (themeValue?.defaultImpression) {
return [themeValue.defaultImpression];
}
return [];
};
onMounted(() => {
updateColumnCount();
window.addEventListener("resize", updateColumnCount);
});
onUnmounted(() => {
window.removeEventListener("resize", updateColumnCount);
});
</script>
<template>
<div id="articleMasonry">
<div class="masonry-column" v-for="(column, index) in masonryGroups" :key="index">
<MaterialCard
v-for="item in column"
class="feed"
size="m"
:key="item.id"
:href="item.url"
:title="item.title"
:description="item.description"
:date="item.date"
:impression="getArticleImage(item)"
/>
</div>
</div>
</template>
<style lang="scss" scoped>
@use "sass:meta";
@include meta.load-css("../styles/components/ArticleMasonry");
</style>

View File

@@ -3,6 +3,7 @@ import { createPinia } from "pinia";
import Layout from "./layouts/Default.vue";
import AppBar from "./components/AppBar.vue";
import ArticleMasonry from "./components/ArticleMasonry.vue";
import Button from "./components/Button.vue";
import Card from "./components/Card.vue";
import Footer from "./components/Footer.vue";
@@ -23,6 +24,7 @@ export default {
app.use(pinia);
app.component("AppBar", AppBar);
app.component("ArticleMasonry", ArticleMasonry);
app.component("Footer", Footer);
app.component("Header", Header);
app.component("ImageViewer", ImageViewer);

View File

@@ -0,0 +1,24 @@
#articleMasonry {
display: flex;
align-items: flex-start;
flex-direction: row;
gap: 12px;
width: 100%;
.masonry-column {
display: flex;
flex-direction: column;
flex: 1;
gap: 12px;
min-width: 0px;
.MaterialCard {
width: 100%;
}
}
}
@media screen and (max-width: 840px) {
}