Files
hospital-rescue-app-v2/uni_modules/tmx-ui/components/x-image/x-image.uvue
T
2026-09-24 16:25:22 +08:00

501 lines
12 KiB
Plaintext

<script lang="ts">
import { type PropType } from "vue"
import { checkIsCssUnit, getUid,getUnit } from "../../core/util/xCoreUtil.uts";
import { xConfig,xProvitae } from "../../config/xConfig.uts"
import { getDefaultColor } from "../../core/util/xCoreColorUtil.uts"
type IMG_MODEL = "fill" | "top" | "bottom" | "center" | "left" | "right" | "top left" | "top right" | "bottom left" | "bottom right" | "aspectFit" | "aspectFill" | "widthFix" | "heightFix" | "scaleToFill";
type IMG_SIZE_INFO = {
width : number,
height : number,
}
type IMG_SIZE_INFO_PLACE = {
width : string,
height : string,
}
/**
* @name 图片 xImage
* @description 宽高可以设置,支持百分比,px,rpx
* @page /pages/index/image
* @category 展示组件
* @constant 平台兼容
* | Harmony | H5 | andriod | IOS | 小程序 | UTS | UNIAPP-X SDK | version |
| --- | --- | --- | --- | --- | --- | --- | --- |
| ☑ | ☑ | ☑️ | ☑️ | ☑️ | ☑️ | 4.76+ | 1.1.18 |
*/
export default {
data() {
return {
id: ("xImage-" + getUid()) as string,
idBox: ("xImage-" + getUid()) as string,
/** 是否正在加载,默认为false */
isLoading: true,
/** 是否加载出错,默认为false */
isError: false,
reload: 0,
imgrealWidth: 0,
imgrealHeight: 0,
boxWidth: 0,
boxHeight: 0,
ratioWidth: 0,
ratioHeight: 0,
isLoaded:false,
tid:0,
resizeObserver: null as UniResizeObserver | null,
androidAndWebUrl:"",
dateTime: 0,
_x: 0,
_y: 0,
boxLeft:0,
boxTop:0,
isVisibled:false,
tid2:0,
keyidsf:0
}
},
emits:[
/**
* 图片被点击
*/
'click'],
props: {
/**
* 宽度,默认100%
* 18rpx,18px,15%支持这三种单位,如果只写"18"就表示18rpx
*/
width: {
type: String,
default: "100%"
},
/**
* 高度,auto,%,rpx,px,string number
* 18rpx,18px,15%支持这三种单位,如果只写"18"就表示18rpx
*/
height: {
type: String,
default: "auto"
},
/** 图片源 */
src: {
type: String,
default: ""
},
/**
* 预览的图片源,如果为空则与src同步
*/
previewSrc:{
type:String,
default:""
},
/**
* 模式
* @link https://uniapp.dcloud.net.cn/uni-app-x/component/image.html#mode-values
*
*/
model: {
type: String as PropType<IMG_MODEL>,
default: "fill"
},
/**
* 点击后是否预览图片
*/
preview: {
type: Boolean,
default: true
},
/**
* 预览占位比例
* 宽/高,当数据没加载前,如果你设置了一项值比如宽,高会自动根据这个比例计算
* 当图片加载成功后,使用正确的原图片比例设置。
* 默认是5/4=1.25
*/
ratio: {
type: Number,
default: 1.25
},
/**
* 圆角
*/
round: {
type: String,
default: '0'
},
/**
* 加载和失败时的图标大小。
*/
iconSize:{
type:String,
default:"16"
},
/**
* 占位背景色
*/
placeBgColor:{
type:String,
default:"#F5F5F5"
},
/**
* 点位暗黑时的背景,如果不填写默认填充inputDarkBgcolor
*/
placeDarkBgColor:{
type:String,
default:""
},
/**
* 是否在安卓上显示过渡动画
*/
fadeShow:{
type:Boolean,
default:false
},
/**
* 用于在scorllview根节点的页面进行懒加载,不可视范围内的不显示.请仅慎使用,不可在list-view中使用.
*/
lazy:{
type:Boolean,
default:false
}
},
mounted() {
let t= this;
// #ifdef APP
this.imgLoad()
// #endif
let ele = uni.getElementById(this.idBox!);
if(ele==null) return;
// #ifdef APP || WEB
if (this.resizeObserver == null) {
this.resizeObserver = new UniResizeObserver((entries : Array<UniResizeObserverEntry>) => {
entries.forEach(entry => {
if (entry.target == ele) {
// #ifdef APP
t.tid = setTimeout(function() {
t.getNodes();
}, 50);
// #endif
// #ifdef WEB
t.getNodes();
// #endif
}
})
})
}
this.resizeObserver!.observe(ele!)
// #ifdef uniVersion >= 4.31 && APP-ANDROID
t.tid = setTimeout(function() {
t.getNodes();
}, 50);
// #endif
// #endif
// #ifdef MP-WEIXIN
this.getNodes();
// #endif
this.setIsVisibledShow()
},
beforeUnmount() {
clearTimeout(this.tid)
clearTimeout(this.tid2)
if(this.resizeObserver!=null){
this.resizeObserver?.disconnect()
}
},
updated() {
// #ifdef MP-WEIXIN
this.getNodes()
// #endif
},
computed: {
_model():string{
return this.model;
},
_placeBgColor():string{
let bgcolor = this.placeBgColor;
if(xConfig.dark=='dark'){
bgcolor = this.placeDarkBgColor
if(this.placeDarkBgColor==''){
bgcolor = xConfig.inputDarkColor
}
}
return getDefaultColor(bgcolor)
},
_round() : string {
return checkIsCssUnit(this.round, xConfig.unit)
},
_src() : string {
return this.src;
},
_previewSrc() : string {
if(this.previewSrc == '') return this.src;
return this.previewSrc;
},
_place_size() : IMG_SIZE_INFO_PLACE {
return {
width: checkIsCssUnit(this.width, xConfig.unit),
height: checkIsCssUnit(this.height, xConfig.unit),
} as IMG_SIZE_INFO_PLACE
},
_scrollTop():number{
return xProvitae.scrollTop;
},
_img_box_size() : IMG_SIZE_INFO_PLACE {
if(this.imgrealHeight>0){
return this._img_size;
}
let _w = this.width;
let _h = this.height;
let us_w = checkIsCssUnit(this.width, xConfig.unit)
let us_h = checkIsCssUnit(this.height, xConfig.unit)
if(this.width.lastIndexOf('%')>-1||this.width=='auto' ){
us_w = '100%'
_w = '100%'
}
if(this.height.lastIndexOf('%')>-1||this.height=='auto' || this.isError){
if(this.boxHeight>=5){
_h = (this.boxHeight).toString()+'px'
}else{
if(this.width.lastIndexOf('%')>-1||this.width=='auto'){
if(this.height=='100%'){
_h = (this.ratio * this.boxHeight).toString()+'px'
}else{
_h = (this.ratio * this.boxWidth).toString()+'px'
}
}else{
_h = (this.ratio * parseFloat(us_w)).toString()+getUnit(us_w)
}
}
return { width: _w, height: _h } as IMG_SIZE_INFO_PLACE
}
return { width: us_w, height: us_h } as IMG_SIZE_INFO_PLACE
},
_img_size() : IMG_SIZE_INFO_PLACE {
let us_w = checkIsCssUnit(this.width, xConfig.unit)
let us_h = checkIsCssUnit(this.height, xConfig.unit)
if (!this.isLoaded) {
return { width: "300px", height: "300px" } as IMG_SIZE_INFO_PLACE
}
if (this.boxWidth > 0) {
let ratio = this.boxWidth / this.imgrealWidth;
// 如果图片高为auto,表示自动高。
if((this.height=='auto')){
us_h = (ratio*this.imgrealHeight).toString()+'px'
}
if((this.width=='auto')){
us_w = this.boxWidth.toString()+'px'
}
if((this.height.lastIndexOf('%')>-1)){
us_h = (this.boxHeight).toString()+'px'
}
if((this.width.lastIndexOf('%')>-1)){
us_w = (this.boxWidth).toString()+'px'
}
}
return { width: us_w, height: us_h } as IMG_SIZE_INFO_PLACE
},
_styleMap() : Map<string, string> {
let styleMap = new Map<string, string>();
styleMap.set("width", this._img_size.width)
styleMap.set("height", this._img_size.height)
styleMap.set("transform", this.isLoading ? 'scale(0.1)' : 'scale(1)')
styleMap.set("visibility", this.isLoading ? 'visible' : (!this.isError ? 'visible' : 'hidden'))
styleMap.set("opacity", this.isLoading ? '0' : '1')
styleMap.set("border-raiuds", this._round)
return styleMap;
}
},
watch:{
src(){
// #ifdef APP-ANDROID || WEB
this.imgLoad()
// #endif
},
_scrollTop(){
if(!this.lazy) return;
clearTimeout(this.tid2)
let t = this;
this.tid2 = setTimeout(function() {
t.setIsVisibledShow()
}, 10);
}
},
methods: {
setIsVisibledShow(){
let win = uni.getWindowInfo()
let t = this;
// let ele = this?.$parent?.$el as UniElement||null;
uni.createSelectorQuery().in(this)
.select(".xImage")
.boundingClientRect().exec((ret) => {
let nodeinfo = ret[0] as NodeInfo;
let winHeight = win.windowHeight;
// let winWidth = win.windowWidth;
let eleTop = nodeinfo.top!
let eleBotom = nodeinfo.bottom!
t.isVisibled = eleBotom < 0 || eleTop > winHeight ?false:true;
})
},
prevImage() {
/**
* 图片被点击
*/
this.$emit('click')
if (this.preview) {
uni.previewImage({
current: this._previewSrc,
urls: [this._previewSrc]
})
}
},
imgLoad2(evt : ImageLoadEvent) {
this.imgrealWidth = evt.detail.width;
this.imgrealHeight = evt.detail.height;
this.isLoading = false;
this.androidAndWebUrl = this._src
this.isError = false;
this.isLoaded = true;
},
imgLoad() {
let t = this;
uni.getImageInfo({
src:this._src,
fail(error:IMediaError){
console.log('error',error.errMsg)
t.isError = true;
t.isLoading = false
},
success(result:GetImageInfoSuccess){
t.isLoading = false;
t.isError = false;
t.isLoaded = true;
if( result.path!=t.androidAndWebUrl){
t.imgrealWidth = result.width
t.imgrealHeight = result.height
t.androidAndWebUrl = result.path;
t.keyidsf+=1;
}
}
} as GetImageInfoOptions)
},
// evt:ImageErrorEvent
imgError() {
this.isError = true;
this.isLoading = false
},
getNodes() {
let t = this;
// let ele = this?.$parent?.$el as UniElement||null;
uni.createSelectorQuery().in(this)
.select(".xImage")
.boundingClientRect().exec((ret) => {
let nodeinfo = ret[0] as NodeInfo;
t.boxWidth = nodeinfo.width!
t.boxHeight = nodeinfo.height!
})
},
resize(){
this.isLoaded = false
let t = this;
// let ele = this?.$parent?.$el as UniElement||null;
uni.createSelectorQuery().in(this)
.select(".xImage")
.boundingClientRect().exec((ret) => {
let nodeinfo = ret[0] as NodeInfo;
t.boxWidth = nodeinfo.width!
t.boxHeight = nodeinfo.height!
t.isLoaded = true
})
}
},
}
</script>
<template>
<view
:key="keyidsf"
@click="prevImage"
class="xImage" ref="xImage" :id="idBox" :style="{width:_place_size.width,height:_place_size.height}">
<view class="xImageBox"
:style="{width:_img_box_size.width,height:_img_box_size.height,borderRadius:_round,pointerEvent:'none'}">
<!--
@slot 加载中的插槽,插槽内自行给你布局的view宽和高写100%
-->
<slot name="loading" v-if="isLoading">
<view class="xImagePlace" :style="{backgroundColor:_placeBgColor}">
<x-icon :font-size="iconSize" v-if="isLoading" name="loader-2-line" color="primary" :spin="true"></x-icon>
</view>
</slot>
<!--
@slot 加载失败时的插槽,插槽内自行给你布局的view宽和高写100%
-->
<slot name="error" v-if="isError">
<view class="xImagePlace" :style="{backgroundColor:_placeBgColor}">
<x-icon :font-size="iconSize" v-if="isError" color="error" name="landscape-line"></x-icon>
</view>
</slot>
<!-- @error="imgError" -->
<!-- #ifdef APP -->
<image :fade-show="fadeShow" v-if="!isError" class="xImageImg" :class="[isLoading?'xImageImgAbs':'']"
:mode="_model" :style="[_styleMap,{visibility:isVisibled||!lazy?'visible':'hidden'}]" :src="_src" >
</image>
<!-- #endif -->
<!-- #ifndef APP -->
<image @load="imgLoad2" @error="imgError" :fade-show="fadeShow" v-if="!isError" class="xImageImg" :class="[isLoading?'xImageImgAbs':'']"
:mode="_model" :style="[_styleMap,{visibility:isVisibled||!lazy?'visible':'hidden'}]" :src="_src" >
</image>
<!-- #endif -->
</view>
</view>
</template>
<style scoped>
.xImage {
position: relative;
overflow: hidden;
}
.xImageBox{
pointer-events: none;
}
.xImagePlace {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 100%;
height: 100%
}
.xImageImgAbs {
position: absolute;
pointer-events: none;
}
</style>