style: 更新UI样式和字体设置

This commit is contained in:
2026-03-20 21:13:55 +08:00
parent 1e198af3d3
commit 9586c67a46
21 changed files with 2859 additions and 3606 deletions

View File

@ -1,6 +1,11 @@
<template>
<div ref="waterfallWrapper" class="waterfall-list" :style="{ height: `${wrapperHeight}px` }">
<div v-for="(item, index) in list" :key="getKey(item, index)" :style="{height:`${colWidth * item.height / item.width}px`}" class="waterfall-item">
<div ref="waterfallWrapper" class="waterfall-list" :style="wrapperStyle">
<div
v-for="(item, index) in list"
:key="getKey(item, index)"
:style="getItemStyle(index)"
class="waterfall-item"
>
<div class="waterfall-card h-full">
<slot name="item" :item="item" :index="index" :url="getRenderURL(item)" />
</div>
@ -11,10 +16,10 @@
<script setup lang="ts">
import { useDebounceFn } from "@vueuse/core";
import type { PropType } from "vue";
import { provide, ref, watch } from "vue";
import { computed, provide, useTemplateRef, watch } from "vue";
import type { ViewCard } from "../types/waterfall";
import { useCalculateCols, useLayout } from "../use";
import Lazy from "../utils/Lazy";
import { waterfallImageLoadedKey } from "../utils/keys";
import { getValue } from "../utils/util";
const props = defineProps({
@ -34,6 +39,14 @@ const props = defineProps({
type: Number,
default: 200,
},
widthSelector: {
type: String,
default: "width",
},
heightSelector: {
type: String,
default: "height",
},
columns: {
type: Number,
default: 3,
@ -72,7 +85,7 @@ const props = defineProps({
},
loadProps: {
type: Object,
default: () => { },
default: () => {},
},
crossOrigin: {
type: Boolean,
@ -84,62 +97,91 @@ const props = defineProps({
},
});
const lazy = new Lazy(props.lazyload, props.loadProps, props.crossOrigin);
provide("lazy", lazy);
const waterfallWrapper = useTemplateRef<HTMLElement>("waterfallWrapper");
// 容器块信息
const { waterfallWrapper, wrapperWidth, colWidth, cols, offsetX } =
useCalculateCols(props);
// 瀹瑰櫒鍧椾俊鎭?
const { wrapperWidth, colWidth, cols, offsetX } = useCalculateCols(
props,
waterfallWrapper
);
// 容器高度,块定位
const getNumericValue = (item: ViewCard, selector: string): number | null => {
const value = getValue(item, selector)[0];
const resolved = Number(value);
if (!Number.isFinite(resolved) || resolved <= 0) return null;
return resolved;
};
const getItemHeightByIndex = (index: number): number | null => {
const item = props.list[index];
if (!item || colWidth.value <= 0) return null;
const width = getNumericValue(item, props.widthSelector);
const height = getNumericValue(item, props.heightSelector);
if (!width || !height) return null;
return (colWidth.value * height) / width;
};
const itemHeights = computed(() =>
props.list.map((_, index) => getItemHeightByIndex(index))
);
// 瀹瑰櫒楂樺害锛屽潡瀹氫綅
const { wrapperHeight, layoutHandle } = useLayout(
props,
colWidth,
cols,
offsetX,
waterfallWrapper
waterfallWrapper,
(index, item) => getItemHeightByIndex(index) ?? item.offsetHeight
);
// 1s内最多执行一次排版减少性能开销
const wrapperStyle = computed(() => ({
height: `${wrapperHeight.value}px`,
backgroundColor: props.backgroundColor,
}));
const getItemStyle = (index: number) => {
const height = itemHeights.value[index];
if (!height) return undefined;
return {
height: `${height}px`,
};
};
// 1s鍐呮渶澶氭墽琛屼竴娆℃帓鐗堬紝鍑忓皯鎬ц兘寮€閿€
const renderer = useDebounceFn(() => {
layoutHandle();
// console.log("强制更新排版");
}, props.delay);
// 列表发生变化直接触发排版
watch(
() => [wrapperWidth, colWidth, props.list],
[wrapperWidth, cols, itemHeights, () => props.list],
() => {
renderer();
},
{ deep: true }
{ immediate: true }
);
// 尺寸宽度变化防抖触发
const sizeChangeTime = ref(0);
// 鍥剧墖鍔犺浇瀹屾垚
provide(waterfallImageLoadedKey, renderer);
provide("sizeChangeTime", sizeChangeTime);
// 图片加载完成
provide("imgLoaded", renderer);
// 根据选择器获取图片地址
// 鏍规嵁閫夋嫨鍣ㄨ幏鍙栧浘鐗囧湴鍧€
const getRenderURL = (item: ViewCard): string => {
return getValue(item, props.imgSelector)[0];
return String(getValue(item, props.imgSelector)[0] ?? "");
};
// 获取唯一值
const getKey = (item: ViewCard, index: number): string => {
return item[props.rowKey] || index;
// 鑾峰彇鍞竴鍊?
const getKey = (item: ViewCard, index: number): string | number => {
return item[props.rowKey] ?? index;
};
const clearAndReload = () => {
const originalList = [...props.list];
props.list.length = 0;
setTimeout(() => {
props.list.push(...originalList);
renderer();
}, 0);
layoutHandle();
};
defineExpose({
@ -159,42 +201,53 @@ defineExpose({
width: 100%;
position: relative;
overflow: hidden;
background-color: v-bind(backgroundColor);
--waterfall-radius: 22px;
--waterfall-border: rgba(255, 255, 255, 0.72);
--waterfall-surface: linear-gradient(
180deg,
rgba(255, 255, 255, 0.9),
rgba(244, 250, 255, 0.72)
);
--waterfall-shadow: 0 16px 32px rgba(118, 144, 169, 0.12);
}
.waterfall-item {
position: absolute;
left: 0;
top: 0;
/* transition: .3s; */
/* 初始位置设置到屏幕以外,避免懒加载失败 */
transform: translate3d(0, 3000px, 0);
visibility: hidden;
will-change: transform, opacity;
}
/* 初始的入场效果 */
@-webkit-keyframes fadeIn {
0% {
opacity: 0;
}
.waterfall-card {
height: 100%;
overflow: hidden;
border: 1px solid var(--waterfall-border);
border-radius: var(--waterfall-radius);
background: var(--waterfall-surface);
box-shadow: var(--waterfall-shadow);
backdrop-filter: blur(14px);
transform-origin: center top;
}
100% {
opacity: 1;
}
.waterfall-card :deep(img) {
display: block;
}
@keyframes fadeIn {
0% {
opacity: 0;
opacity: 0.01;
transform: translate3d(0, 18px, 0) scale(0.985);
}
100% {
opacity: 1;
transform: translate3d(0, 0, 0) scale(1);
}
}
.fadeIn {
-webkit-animation-name: fadeIn;
animation-name: fadeIn;
}
</style>