add comment user info card

This commit is contained in:
landaiqing
2024-11-05 02:11:20 +08:00
parent ec55a2f8c8
commit c0269dfa5a
69 changed files with 2958 additions and 819 deletions

View File

@@ -0,0 +1,182 @@
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue';
import Spin from '../Spin/Spin.vue';
import { useResizeObserver } from '../utils';
/*
宽度固定图片等比例缩放使用JS获取每张图片宽度和高度结合 `relative` 和 `absolute` 定位
计算每个图片的位置 `top``left`,保证每张新的图片都追加在当前高度最小的那列末尾
*/
interface Image {
name?: string // 图片名称
src: string // 图片地址
}
interface Props {
images?: Image[] // 图片数组
columnCount?: number // 要划分的列数
columnGap?: number // 各列之间的间隙,单位 px
width?: string | number // 瀑布流区域的总宽度,单位 px
borderRadius?: number // 瀑布流区域和图片圆角,单位 px
backgroundColor?: string // 瀑布流区域背景填充色
spinProps?: object // Spin 组件属性配置,参考 Spin Props用于配置图片加载中样式
}
const props = withDefaults(defineProps<Props>(), {
images: () => [],
columnCount: 3,
columnGap: 20,
width: '100%',
borderRadius: 8,
backgroundColor: '#F2F4F8',
spinProps: () => ({})
});
const waterfallRef = ref();
const waterfallWidth = ref<number>();
const loaded = ref(Array(props.images.length).fill(false)); // 图片是否加载完成
const imageWidth = ref<number>();
const imagesProperty = ref<{ width: number; height: number; top: number; left: number }[]>([]);
const preColumnHeight = ref<number[]>(Array(props.columnCount).fill(0)); // 每列的高度
const flag = ref(0);
const totalWidth = computed(() => {
if (typeof props.width === 'number') {
return `${props.width}px`;
} else {
return props.width;
}
});
const height = computed(() => {
return Math.max(...preColumnHeight.value) + props.columnGap;
});
const len = computed(() => {
return props.images.length;
});
watch(
() => [props.images, props.columnCount, props.columnGap, props.width],
() => {
waterfallWidth.value = waterfallRef.value.offsetWidth;
preColumnHeight.value = Array(props.columnCount).fill(0);
flag.value++;
preloadImages(flag.value);
},
{
deep: true, // 强制转成深层侦听器
flush: 'post' // 在侦听器回调中访问被 Vue 更新之后的 DOM
}
);
onMounted(() => {
waterfallWidth.value = waterfallRef.value.offsetWidth;
preloadImages(flag.value);
});
function updateWatefall() {
const currentWidth = waterfallRef.value.offsetWidth;
// 窗口宽度改变时重新计算瀑布流布局
if (props.images.length && currentWidth !== waterfallWidth.value) {
waterfallWidth.value = currentWidth;
flag.value++;
preloadImages(flag.value);
}
}
useResizeObserver(waterfallRef, updateWatefall);
async function preloadImages(symbol: number) {
// 计算图片宽高和位置topleft
// 计算每列的图片宽度
imageWidth.value = ((waterfallWidth.value as number) - (props.columnCount + 1) * props.columnGap) / props.columnCount;
imagesProperty.value.splice(0);
for (let i = 0; i < len.value; i++) {
if (symbol === flag.value) {
await loadImage(props.images[i].src, i);
} else {
return false;
}
}
}
function loadImage(url: string, n: number) {
return new Promise((resolve) => {
const image = new Image();
image.src = url;
image.onload = function () {
// 图片加载完成时执行此时可通过image.width和image.height获取到图片原始宽高
const height = image.height / (image.width / (imageWidth.value as number));
imagesProperty.value[n] = {
// 存储图片宽高和位置信息
width: imageWidth.value as number,
height: height,
...getPosition(n, height)
};
resolve('load');
};
});
}
function getPosition(i: number, height: number) {
// 获取图片位置信息topleft
if (i < props.columnCount) {
preColumnHeight.value[i] = props.columnGap + height;
return {
top: props.columnGap,
left: ((imageWidth.value as number) + props.columnGap) * i + props.columnGap
};
} else {
const top = Math.min(...preColumnHeight.value);
let index = 0;
for (let n = 0; n < props.columnCount; n++) {
if (preColumnHeight.value[n] === top) {
index = n;
break;
}
}
preColumnHeight.value[index] = top + props.columnGap + height;
return {
top: top + props.columnGap,
left: ((imageWidth.value as number) + props.columnGap) * index + props.columnGap
};
}
}
function onLoaded(index: number) {
loaded.value[index] = true;
}
function getImageName(image: Image) {
// 从图像地址src中获取图像名称
if (image) {
if (image.name) {
return image.name;
} else {
const res = image.src.split('?')[0].split('/');
return res[res.length - 1];
}
}
}
</script>
<template>
<div
ref="waterfallRef"
class="m-waterfall"
:style="`--border-radius: ${borderRadius}px; background-color: ${backgroundColor}; width: ${totalWidth}; height: ${height}px;`"
>
<Spin
class="waterfall-image"
:style="`width: ${property.width}px; height: ${property.height}px; top: ${property && property.top}px; left: ${property && property.left}px;`"
:spinning="!loaded[index]"
size="small"
indicator="dynamic-circle"
v-bind="spinProps"
v-for="(property, index) in imagesProperty"
:key="index"
>
<img class="u-image" :src="images[index].src" :alt="getImageName(images[index])" @load="onLoaded(index)" />
</Spin>
</div>
</template>
<style lang="less" scoped>
.m-waterfall {
position: relative;
border-radius: var(--border-radius);
.waterfall-image {
position: absolute;
.u-image {
width: 100%;
height: 100%;
border-radius: var(--border-radius);
display: inline-block;
vertical-align: bottom;
}
}
}
</style>