Files
WAI_Project_UNIX/uni_modules/cool-ui/components/cl-image/cl-image.uvue

234 lines
4.0 KiB
Plaintext
Raw Normal View History

2025-07-21 16:47:04 +08:00
<template>
<view
class="cl-image"
:class="[
{
'cl-image--border': border
},
pt.className
]"
:style="{
width: parseRpx(width!),
height: parseRpx(height!)
}"
>
<view
class="cl-image__error"
:class="[
{
'is-dark': isDark
},
pt.error?.className
]"
v-if="isError"
>
<slot name="error">
<cl-icon
:name="pt.error?.name ?? 'close-line'"
:size="pt.error?.size ?? 40"
:pt="{
className: parseClass(['!text-surface-400', pt.error?.className])
}"
></cl-icon>
</slot>
</view>
<view
class="cl-image__loading"
:class="[
{
'is-dark': isDark
},
pt.loading?.className
]"
v-else-if="isLoading && showLoading"
>
<slot name="loading">
<cl-loading
:loading="true"
:pt="{ icon: { className: '!text-surface-400' } }"
></cl-loading>
</slot>
</view>
<image
class="cl-image__inner"
:class="[pt.inner?.className]"
:src="src"
:mode="mode"
:lazy-load="lazyLoad"
:webp="webp"
:show-menu-by-longpress="showMenuByLongpress"
@load="onLoad"
@error="onError"
@tap="onTap"
/>
<slot></slot>
</view>
</template>
<script setup lang="ts">
import { computed, ref, type PropType } from "vue";
import type { PassThroughProps } from "../../types";
import { isDark, isEmpty, parseClass, parsePt, parseRpx } from "@/cool";
import type { ClIconProps } from "../cl-icon/props";
defineOptions({
name: "cl-image"
});
const props = defineProps({
// 透传样式
pt: {
type: Object,
default: () => ({})
},
// 图片源
src: {
type: String,
default: ""
},
// 图片裁剪、缩放的模式
mode: {
type: String as PropType<
| "scaleToFill"
| "aspectFit"
| "aspectFill"
| "widthFix"
| "heightFix"
| "top"
| "bottom"
| "center"
| "left"
| "right"
| "top left"
| "top right"
| "bottom left"
| "bottom right"
>,
default: "aspectFill"
},
// 是否显示边框
border: {
type: Boolean,
default: false
},
// 是否预览
preview: {
type: Boolean,
default: false
},
// 预览图片列表
previewList: {
type: Array as PropType<string[]>,
default: () => []
},
// 图片高度
height: {
type: [String, Number] as PropType<string | number>,
default: 120
},
// 图片宽度
width: {
type: [String, Number] as PropType<string | number>,
default: 120
},
// 是否显示加载状态
showLoading: {
type: Boolean,
default: true
},
// 是否懒加载
lazyLoad: {
type: Boolean,
default: false
},
// 图片显示动画效果
fadeShow: {
type: Boolean,
default: false
},
// 是否解码webp格式
webp: {
type: Boolean,
default: false
},
// 是否长按显示菜单
showMenuByLongpress: {
type: Boolean,
default: false
}
});
// 事件定义
const emit = defineEmits(["load", "error"]);
// 透传样式类型
type PassThrough = {
className?: string;
inner?: PassThroughProps;
error?: ClIconProps;
loading?: PassThroughProps;
};
// 解析透传样式
const pt = computed(() => parsePt<PassThrough>(props.pt));
// 加载状态
const isLoading = ref(true);
// 加载失败状态
const isError = ref(false);
// 图片加载成功
function onLoad(e: UniEvent) {
isLoading.value = false;
isError.value = false;
emit("load", e);
}
// 图片加载失败
function onError(e: UniEvent) {
isLoading.value = false;
isError.value = true;
emit("error", e);
}
// 图片点击
function onTap() {
if (props.preview) {
const urls = isEmpty(props.previewList) ? [props.src] : props.previewList;
uni.previewImage({
urls,
current: props.src
});
}
}
</script>
<style lang="scss" scoped>
.cl-image {
@apply relative flex flex-row items-center justify-center rounded-xl;
&__inner {
@apply w-full h-full rounded-xl;
}
&__loading,
&__error {
@apply absolute h-full w-full bg-surface-200;
@apply flex flex-col items-center justify-center;
&.is-dark {
@apply bg-surface-700;
}
}
&--border {
@apply border border-solid border-surface-300;
}
}
</style>