399 lines
11 KiB
Vue
399 lines
11 KiB
Vue
<template>
|
|
<view @click="prevImage" class="tmImage" :class="[idBox]" ref="tmImage" :id="idBox"
|
|
:style="{ width: _place_size.width, height: _place_size.height }">
|
|
<!-- @vue-skip -->
|
|
<view class="tmImageBox"
|
|
:style="{ width: _img_box_size.width, height: _img_box_size.height, borderRadius: _round, pointerEvent: 'none' }">
|
|
<view v-if="isLoading || isError" class="tmImagePlace" :style="{ backgroundColor: _placeBgColor }">
|
|
<tm-icon :size="iconSize" v-if="isError" color="error" name="landscape-line"></tm-icon>
|
|
<tm-icon :size="iconSize" v-if="isLoading" name="loader-line" color="primary" :spin="true"></tm-icon>
|
|
</view>
|
|
|
|
<image v-if="!isError" class="tmImageImg" :lazy-load="lazy" :draggable="draggable"
|
|
:showMenuByLongpress="showMenuByLongpress"
|
|
:class="[isLoading ? 'tmImageImgAbs' : '', fadeShow ? 'tmImageFadeShow' : '']" :mode="_model"
|
|
:style="[_styleMap]" :src="_src" @error="imgError">
|
|
</image>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
<script setup lang="ts">
|
|
import { ref, computed, onMounted, onBeforeUnmount, watch, PropType, getCurrentInstance, onUpdated, nextTick } from 'vue';
|
|
import { arrayNumberValid, arrayNumberValidByStyleMP, covetUniNumber, arrayNumberValidByStyleBorderColor, linearValid, getUnit, getUid } from "../../libs/tool";
|
|
import { getDefaultColor, getDefaultColorObj, getOutlineColorObj, getTextColorObj, getThinColorObj } from "../../libs/colors";
|
|
import { useTmConfig } from "../../libs/config";
|
|
import { onPageScroll } from '@dcloudio/uni-app';
|
|
/**
|
|
* @displayName 图片
|
|
* @exportName tm-image
|
|
* @category 展示组件
|
|
* @description 宽高可以设置,支持百分比,px,rpx,如果你用了auto,%这种就要注意:如果你把组件放到了容器中,容器设置为v-show,display:none,visible这种,
|
|
* 那你一定要跟随vif组件,不然尺寸是不对的。因为小程序无法监测这种变化的情况,导致无法设置尺寸。如果你设定了固定尺寸则不用担心这个问题。
|
|
* @constant 平台兼容
|
|
* | H5 | uniAPP | 小程序 | version |
|
|
| --- | --- | --- | --- |
|
|
| ☑️| ☑️ | ☑️ | ☑️ | ☑️ | 1.0.0 |
|
|
*/
|
|
defineOptions({ name: 'TmBadge' });
|
|
const { config } = useTmConfig()
|
|
const props = defineProps({
|
|
/**
|
|
* 宽度,默认100%
|
|
* 18rpx,18px,15%支持这三种单位,如果只写"18"就表示18rpx
|
|
*/
|
|
width: {
|
|
type: [String, Number],
|
|
default: "100%"
|
|
},
|
|
/**
|
|
* 高度,auto,%,rpx,px,string number
|
|
* 18rpx,18px,15%支持这三种单位,如果只写"18"就表示18rpx
|
|
*/
|
|
height: {
|
|
type: [String, Number],
|
|
default: "auto"
|
|
},
|
|
/** 图片源 */
|
|
src: {
|
|
type: String,
|
|
default: ""
|
|
},
|
|
/**
|
|
* 模式
|
|
* @link https://uniapp.dcloud.net.cn/uni-app-x/component/image.html#mode-values
|
|
*/
|
|
model: {
|
|
type: String as PropType<"fill" | "top" | "bottom" | "center" | "left" | "right" | "top left" | "top right" | "bottom left" | "bottom right" | "aspectFit" | "aspectFill" | "widthFix" | "heightFix" | "scaleToFill">,
|
|
default: "fill"
|
|
},
|
|
/**
|
|
* 点击后是否预览图片
|
|
*/
|
|
preview: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
/**
|
|
* 预览占位比例
|
|
* 宽/高,当数据没加载前,如果你设置了一项值比如宽,高会自动根据这个比例计算
|
|
* 当图片加载成功后,使用正确的原图片比例设置。
|
|
* 默认是5/4=1.25
|
|
*/
|
|
ratio: {
|
|
type: Number,
|
|
default: 1.25
|
|
},
|
|
/**
|
|
* 圆角
|
|
*/
|
|
round: {
|
|
type: [String, Number],
|
|
default: '0'
|
|
},
|
|
/**
|
|
* 加载和失败时的图标大小。
|
|
*/
|
|
iconSize: {
|
|
type: [String, Number],
|
|
default: "36"
|
|
},
|
|
/**
|
|
* 占位背景色
|
|
*/
|
|
placeBgColor: {
|
|
type: String,
|
|
default: "#F5F5F5"
|
|
},
|
|
/**
|
|
* 点位暗黑时的背景,如果不填写默认填充inputDarkBgcolor
|
|
*/
|
|
placeDarkBgColor: {
|
|
type: String,
|
|
default: ""
|
|
},
|
|
/**
|
|
* 是否显示过渡动画
|
|
*/
|
|
fadeShow: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
/**
|
|
* 图片懒加载
|
|
*/
|
|
lazy: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
/**
|
|
* 是否允许拖动。
|
|
*/
|
|
draggable: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
showMenuByLongpress: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
});
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'click'): void
|
|
}>();
|
|
|
|
const id = ref(`tmImage-${getUid()}`);
|
|
const idBox = ref(`tmImage-${getUid()}`);
|
|
const isLoading = ref(true);
|
|
const isError = ref(false);
|
|
const reload = ref(0);
|
|
const imgrealWidth = ref(0);
|
|
const imgrealHeight = ref(0);
|
|
const boxWidth = ref(0);
|
|
const boxHeight = ref(0);
|
|
|
|
const isLoaded = ref(false);
|
|
const tid = ref(0);
|
|
const androidAndWebUrl = ref("");
|
|
|
|
const tid2 = ref(0);
|
|
const proxy = getCurrentInstance()?.proxy
|
|
|
|
const _model = computed(() => props.model);
|
|
const _placeBgColor = computed(() => {
|
|
let bgcolor = props.placeBgColor;
|
|
if (config.mode == 'dark') {
|
|
bgcolor = props.placeDarkBgColor;
|
|
if (props.placeDarkBgColor == '') {
|
|
bgcolor = config.inputDarkColor;
|
|
}
|
|
}
|
|
return getDefaultColor(bgcolor);
|
|
});
|
|
|
|
const _round = computed(() => covetUniNumber(props.round, config.unit));
|
|
const _src = computed(() => props.src);
|
|
|
|
const _place_size = computed(() => ({
|
|
width: covetUniNumber(props.width, config.unit),
|
|
height: covetUniNumber(props.height, config.unit),
|
|
}));
|
|
|
|
|
|
|
|
const _img_box_size = computed(() => {
|
|
if (imgrealHeight.value > 0) {
|
|
return _img_size.value;
|
|
}
|
|
|
|
let _w = props.width;
|
|
let _h = props.height;
|
|
let us_w = covetUniNumber(props.width, config.unit);
|
|
let us_h = covetUniNumber(props.height, config.unit);
|
|
|
|
if (typeof props.width == 'string' && props.width.lastIndexOf('%') > -1 || props.width == 'auto') {
|
|
us_w = '100%';
|
|
_w = '100%';
|
|
}
|
|
|
|
if (typeof props.height == 'string' && props.height.lastIndexOf('%') > -1 || props.height == 'auto' || isError.value) {
|
|
if (boxHeight.value >= 5) {
|
|
_h = `${boxHeight.value}px`;
|
|
} else {
|
|
if (typeof props.width == 'string' &&props.width.lastIndexOf('%') > -1 || props.width == 'auto') {
|
|
if (props.height == '100%') {
|
|
_h = `${props.ratio * boxHeight.value}px`;
|
|
} else {
|
|
_h = `${props.ratio * boxWidth.value}px`;
|
|
}
|
|
} else {
|
|
_h = `${props.ratio * parseFloat(us_w)}${getUnit(us_w)}`;
|
|
}
|
|
}
|
|
return { width: _w, height: _h };
|
|
}
|
|
|
|
return { width: us_w, height: us_h };
|
|
});
|
|
|
|
const _img_size = computed(() => {
|
|
let us_w = covetUniNumber(props.width, config.unit);
|
|
let us_h = covetUniNumber(props.height, config.unit);
|
|
|
|
if (!isLoaded.value) {
|
|
return { width: "300px", height: "300px" };
|
|
}
|
|
|
|
if (boxWidth.value > 0) {
|
|
let ratio = boxWidth.value / imgrealWidth.value;
|
|
|
|
if (props.height == 'auto') {
|
|
us_h = `${ratio * imgrealHeight.value}px`;
|
|
}
|
|
|
|
if (props.width == 'auto') {
|
|
us_w = `${boxWidth.value}px`;
|
|
}
|
|
|
|
if (typeof props.height == 'string'&&props.height.lastIndexOf('%') > -1) {
|
|
us_h = `${boxHeight.value}px`;
|
|
}
|
|
if (typeof props.width == 'string'&&props.width.lastIndexOf('%') > -1) {
|
|
us_w = `${boxWidth.value}px`;
|
|
}
|
|
}
|
|
|
|
return { width: us_w, height: us_h };
|
|
});
|
|
|
|
const _styleMap = computed(() => {
|
|
let styleMap: Record<string, any> = {}
|
|
styleMap["width"] = _img_size.value.width
|
|
styleMap["height"] = _img_size.value.height
|
|
styleMap["transform"] = isLoading.value ? 'scale(0.1)' : 'scale(1)'
|
|
styleMap["visibility"] = isLoading.value ? 'visible' : (!isError.value ? 'visible' : 'hidden')
|
|
styleMap["opacity"] = isLoading.value ? '0' : '1'
|
|
styleMap["border-radius"] = _round.value
|
|
return styleMap;
|
|
});
|
|
|
|
const prevImage = () => {
|
|
emit('click');
|
|
if (props.preview) {
|
|
uni.previewImage({
|
|
current: props.src,
|
|
urls: [props.src]
|
|
});
|
|
}
|
|
};
|
|
|
|
|
|
|
|
const imgLoad = () => {
|
|
uni.getImageInfo({
|
|
src: _src.value,
|
|
fail(_) {
|
|
console.log('error');
|
|
isError.value = true;
|
|
isLoading.value = false;
|
|
},
|
|
success(result: any) {
|
|
isLoading.value = false;
|
|
isError.value = false;
|
|
isLoaded.value = true;
|
|
imgrealWidth.value = result.width;
|
|
imgrealHeight.value = result.height;
|
|
androidAndWebUrl.value = result.path;
|
|
}
|
|
});
|
|
};
|
|
|
|
const imgError = () => {
|
|
isError.value = true;
|
|
isLoading.value = false;
|
|
};
|
|
|
|
|
|
|
|
|
|
const getNodes = (): Promise<any> => {
|
|
return new Promise((res) => {
|
|
uni.createSelectorQuery().in(proxy)
|
|
.select(".tmImage")
|
|
.boundingClientRect().exec((ret) => {
|
|
let nodeinfo = ret[0] as UniApp.NodeInfo;
|
|
boxWidth.value = nodeinfo.width!;
|
|
boxHeight.value = nodeinfo.height!;
|
|
res(true)
|
|
});
|
|
})
|
|
};
|
|
|
|
const resize = (): Promise<any> => {
|
|
isLoaded.value = false;
|
|
return new Promise((res) => {
|
|
uni.createSelectorQuery().in(proxy)
|
|
.select(".tmImage")
|
|
.boundingClientRect().exec((ret) => {
|
|
let nodeinfo = ret[0] as UniApp.NodeInfo;
|
|
boxWidth.value = nodeinfo.width!;
|
|
boxHeight.value = nodeinfo.height!;
|
|
isLoaded.value = true;
|
|
res(true)
|
|
});
|
|
})
|
|
};
|
|
|
|
onMounted(async () => {
|
|
await getNodes();
|
|
imgLoad();
|
|
|
|
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
clearTimeout(tid.value);
|
|
clearTimeout(tid2.value);
|
|
});
|
|
|
|
watch(() => props.src, () => {
|
|
imgLoad();
|
|
});
|
|
|
|
|
|
</script>
|
|
<script lang="ts">
|
|
export default {
|
|
options: {
|
|
styleIsolation: "apply-shared",
|
|
virtualHost: true,
|
|
addGlobalClass: true,
|
|
multipleSlots: true,
|
|
},
|
|
};
|
|
</script>
|
|
<style scoped>
|
|
.tmImage {
|
|
position: relative;
|
|
overflow: hidden;
|
|
display: inline-flex;
|
|
}
|
|
|
|
.tmImageBox {
|
|
pointer-events: none;
|
|
overflow: hidden;
|
|
|
|
}
|
|
|
|
.tmImageImg {
|
|
opacity: 0;
|
|
}
|
|
|
|
@keyframes animateFadeIn {
|
|
0% {
|
|
opacity: 0;
|
|
}
|
|
|
|
100% {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.tmImageFadeShow {
|
|
animation: animateFadeIn 0.2s linear forwards;
|
|
}
|
|
|
|
.tmImagePlace {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 100%;
|
|
height: 100%
|
|
}
|
|
|
|
.tmImageImgAbs {
|
|
position: absolute;
|
|
|
|
}
|
|
</style> |