430 lines
10 KiB
Plaintext
430 lines
10 KiB
Plaintext
<template>
|
|
|
|
<view class="xAvatarGroup" ref="xAvatarGroup" :class="[_flat?'xAvatarGroupWrap':'xAvatarGroupNoWrap']">
|
|
<template v-for="(item,index) in _list" :key="index">
|
|
<image @click="itemClick(item,index)" v-if="item.isImg" :style="{
|
|
width:_size,
|
|
height:_size,
|
|
borderRadius:_round,
|
|
marginRight:((index+1) == column&&_showCount) ?'0px':_gutter,
|
|
marginBottom:Math.ceil((index+1)/column)==row?'0px':_gutter
|
|
|
|
}" class="xAvatarGroupImage" :src="item.name" :mode="props.model"></image>
|
|
<view @click="itemClick(item,index)" v-else :style="{
|
|
width:_size,
|
|
height:_size,
|
|
borderRadius:_round,
|
|
marginRight:((index+1) == column&&_showCount) ?'0px':_gutter,
|
|
marginBottom:Math.ceil((index+1)/column)==row?'0px':_gutter,
|
|
backgroundColor:item.bgColor
|
|
}" class="xAvatarGroupImage xAvatarGroupImageBytext">
|
|
<x-text v-if="item.name!=''" :font-size="props.fontSize"
|
|
:color="(_randomBgColor?'white':props.fontColor)" :dark-color="props.darkFontColor">
|
|
{{item.name}}
|
|
</x-text>
|
|
<x-icon v-if="item.name==''" :font-size="props.fontSize"
|
|
:color="(_randomBgColor?'white':props.fontColor)" :dark-color="props.darkFontColor"
|
|
:name="props.placeIcon"></x-icon>
|
|
</view>
|
|
</template>
|
|
<!--
|
|
@slot more更多插槽,如果使用了这个插槽moreClick事件会丢失,请自己写在自己的布局上。
|
|
-->
|
|
<slot name="more">
|
|
<view @click="moreClick" class="xAvatarGroupImageMore" :style="{
|
|
width:_size,
|
|
height:_size,
|
|
borderRadius:_round,
|
|
backgroundColor:_BgColor
|
|
}" v-if="_showCount">
|
|
<x-text :font-size="props.fontSize" :color="props.fontColor"
|
|
:dark-color="props.darkFontColor">{{_count==0?(_list.length>99?99:_list.length)+'+':(_count>99?99:_count)+'+'}}</x-text>
|
|
</view>
|
|
</slot>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
|
|
import { computed, ref } from "vue"
|
|
import { type PropType } from "vue"
|
|
import { getDefaultColor, hslaToCss } from "../../core/util/xCoreColorUtil.uts"
|
|
import { checkIsCssUnit, rpx2px, getUid, getUnit } from "../../core/util/xCoreUtil.uts"
|
|
import { xConfig } from "../../config/xConfig.uts"
|
|
import { xProvitae } from "../../config/xConfig.uts"
|
|
type XAVATARGROUPITEMTYPE = {
|
|
name : string,
|
|
isImg : boolean,
|
|
bgColor : string
|
|
}
|
|
/**
|
|
* @name 头像组 xAvatarGroup
|
|
* @page /pages/index/avatar-group
|
|
* @category 展示组件
|
|
* @description 平铺和堆叠方式。如果想要单头像建议使用:xSheet+xImage配合,+xBadge达到效果,因此我不再提供单头像组件,没有意义。
|
|
* @constant 平台兼容
|
|
* | Harmony | H5 | andriod | IOS | 小程序 | UTS | UNIAPP-X SDK | version |
|
|
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
| ☑ | ☑ | ☑️ | ☑️ | ☑️ | ☑️ | 4.76+ | 1.1.18 |
|
|
*/
|
|
defineOptions({ name: "xAvatarGroup" })
|
|
|
|
let resizeObserver = null as UniResizeObserver | null
|
|
const xAvatarGroup = ref<UniElement | null>(null)
|
|
const emits = defineEmits([
|
|
/**
|
|
* 头像被点击时
|
|
* @param {number} index 当前索引
|
|
* @param {string} src 当前图片地址
|
|
*/
|
|
"click",
|
|
/**
|
|
* 最后一个数字头像more被点击时
|
|
*/
|
|
"moreClick"
|
|
])
|
|
const props = defineProps({
|
|
/**
|
|
* 头像列表,也可以是文本数组,也可以是空字符串数组
|
|
*/
|
|
list: {
|
|
type: Array as PropType<string[]>,
|
|
default: () : string[] => [] as string[]
|
|
},
|
|
/**
|
|
* 不允许使用auto,%只能数字或者带单位的数字2px,2rpx这种
|
|
*/
|
|
size: {
|
|
type: String,
|
|
default: '32'
|
|
},
|
|
/**
|
|
* 最多显示几个头像。
|
|
*/
|
|
maxCount: {
|
|
type: Number,
|
|
default: 5
|
|
},
|
|
/**
|
|
* 圆角
|
|
*/
|
|
round: {
|
|
type: String,
|
|
default: '16'
|
|
},
|
|
/**
|
|
* 平铺或者堆叠时的间隙或者前推差值。
|
|
* 不允许使用auto,%只能数字或者带单位的数字2px,2rpx这种
|
|
*/
|
|
gutter: {
|
|
type: String,
|
|
default: '16'
|
|
},
|
|
/**
|
|
* 显示类型见:
|
|
* https://doc.dcloud.net.cn/uni-app-x/component/image.html#%E5%B1%9E%E6%80%A7
|
|
*/
|
|
model: {
|
|
type: String,
|
|
default: "scaleToFill"
|
|
},
|
|
/**
|
|
* 显示在最后一个时,显示的数字。如果为0取list的长度
|
|
*/
|
|
count: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
/**
|
|
* 是否显示最后一个数字头像
|
|
*/
|
|
showCount: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
/**
|
|
* 是否平铺,如果否就是堆叠。是就是正常排列。
|
|
*/
|
|
flat: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
/**
|
|
* 如果为文本头像时的背景
|
|
*/
|
|
bgColor: {
|
|
type: String,
|
|
default: "#f5f5f5"
|
|
},
|
|
/**
|
|
* 如果为文本头像时的暗黑背景
|
|
* 空时默认取inputDarkBgcolor
|
|
*/
|
|
darkBgColor: {
|
|
type: String,
|
|
default: ""
|
|
},
|
|
/**
|
|
* 如果为文本头像时的文字颜色
|
|
*/
|
|
fontColor: {
|
|
type: String,
|
|
default: "#a6a6a6"
|
|
},
|
|
/**
|
|
* 如果为文本头像时的暗黑背景
|
|
* 空时默认取inputDarkBgcolor
|
|
*/
|
|
darkFontColor: {
|
|
type: String,
|
|
default: "#ffffff"
|
|
},
|
|
/**
|
|
* 字号
|
|
*/
|
|
fontSize: {
|
|
type: String,
|
|
default: "14"
|
|
},
|
|
/**
|
|
* 文本头像时,是否随机背景色
|
|
*/
|
|
randomBgColor: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
/**
|
|
* 如果当图片或者文本为空时的图片占位符
|
|
* 可以是图片地址或者图标名称
|
|
*/
|
|
placeIcon: {
|
|
type: String,
|
|
default: 'user-3-fill'
|
|
}
|
|
})
|
|
const _isFileImg = (name : string) : boolean => {
|
|
if (name.lastIndexOf(".") > -1 ||
|
|
name.indexOf("ftp:") > -1 ||
|
|
name.indexOf("https:") > -1 ||
|
|
name.indexOf("http:") > -1 ||
|
|
name.indexOf("data:image") > -1
|
|
) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
const _randomBgColor = computed(() : boolean => {
|
|
return props.randomBgColor
|
|
})
|
|
const _BgColor = computed(() : string => {
|
|
let bgcolor = props.bgColor;
|
|
if (xConfig.dark == 'dark') {
|
|
bgcolor = props.darkBgColor;
|
|
if (bgcolor == '') {
|
|
bgcolor = xConfig.inputDarkColor
|
|
}
|
|
}
|
|
return getDefaultColor(bgcolor)
|
|
})
|
|
|
|
const UpperEn = new Map<string, string>([
|
|
['a', 'A'],
|
|
['b', 'B'],
|
|
['c', 'C'],
|
|
['d', 'D'],
|
|
['e', 'E'],
|
|
['f', 'F'],
|
|
['g', 'G'],
|
|
['h', 'H'],
|
|
['i', 'I'],
|
|
['j', 'J'],
|
|
['k', 'K'],
|
|
['l', 'L'],
|
|
['m', 'M'],
|
|
['n', 'N'],
|
|
['o', 'O'],
|
|
['p', 'P'],
|
|
['q', 'Q'],
|
|
['r', 'R'],
|
|
['s', 'S'],
|
|
['t', 'T'],
|
|
['u', 'U'],
|
|
['v', 'V'],
|
|
['w', 'W'],
|
|
['x', 'X'],
|
|
['y', 'Y'],
|
|
['z', 'Z']
|
|
]);
|
|
const _list = computed(() : XAVATARGROUPITEMTYPE[] => {
|
|
let nowlist = props.list.slice(0)
|
|
if (props.maxCount > 0) {
|
|
nowlist = nowlist.slice(0, props.maxCount)
|
|
}
|
|
let compeltedList = [] as XAVATARGROUPITEMTYPE[];
|
|
for (let i = 0; i < nowlist.length; i++) {
|
|
let name = nowlist[i]
|
|
let isImg = _isFileImg(name);
|
|
let bgColor = ''
|
|
if (!isImg) {
|
|
name = name.slice(0, 1)
|
|
if (UpperEn.get(name) != null) {
|
|
name = UpperEn.get(name)!
|
|
}
|
|
let h = Math.random() * 360
|
|
let s = 78
|
|
let l = 62
|
|
|
|
bgColor = hslaToCss({ h: parseInt(h.toFixed(0)), s, l, a: 1 } as UTSJSONObject)
|
|
}
|
|
|
|
compeltedList.push({
|
|
name,
|
|
isImg,
|
|
bgColor: props.randomBgColor ? bgColor : _BgColor.value
|
|
} as XAVATARGROUPITEMTYPE)
|
|
}
|
|
return compeltedList;
|
|
})
|
|
|
|
|
|
const _size = computed(() : string => checkIsCssUnit(props.size, xConfig.unit))
|
|
const _sizeBypx = computed(() : number => {
|
|
let rpx = checkIsCssUnit(props.size, xConfig.unit);
|
|
let unit = getUnit(rpx)
|
|
let zhi = parseFloat(rpx)
|
|
if (unit == 'rpx') {
|
|
zhi = uni.rpx2px(parseFloat(rpx))
|
|
}
|
|
return zhi
|
|
})
|
|
|
|
const _showCount = computed(() : boolean => props.showCount)
|
|
const _round = computed(() : string => checkIsCssUnit(props.round, xConfig.unit))
|
|
const _flat = computed(() : boolean => props.flat)
|
|
const _gutter = computed(() : string => {
|
|
if (props.flat) {
|
|
return checkIsCssUnit(props.gutter, xConfig.unit)
|
|
}
|
|
return '-' + checkIsCssUnit(props.gutter, xConfig.unit)
|
|
})
|
|
const _gutterBypx = computed(() : number => {
|
|
let rpx = checkIsCssUnit(props.gutter, xConfig.unit);
|
|
let unit = getUnit(rpx)
|
|
let zhi = parseFloat(rpx)
|
|
if (unit == 'rpx') {
|
|
zhi = uni.rpx2px(parseFloat(rpx))
|
|
}
|
|
return zhi
|
|
})
|
|
const _count = computed(() : number => props.count)
|
|
const totalWidth = ref(0)
|
|
const column = ref(1)
|
|
const row = ref(0)
|
|
let tid = 0
|
|
|
|
const calcColumn = (width : number) => {
|
|
totalWidth.value = width;
|
|
if (_sizeBypx.value > 0) {
|
|
column.value = Math.floor((totalWidth.value + _gutterBypx.value) / (_sizeBypx.value + _gutterBypx.value * (props.flat ? 1 : -1)));
|
|
}
|
|
if (column.value > 0 && _list.value.length > 0) {
|
|
row.value = Math.ceil(props.list.length / column.value)
|
|
}
|
|
}
|
|
const getNodeinfo = () => {
|
|
uni.createSelectorQuery()
|
|
.in(getCurrentInstance()?.proxy)
|
|
.select(".xAvatarGroup")
|
|
.boundingClientRect((res : any) => {
|
|
let node = res as NodeInfo
|
|
calcColumn(node.width!)
|
|
})
|
|
.exec()
|
|
}
|
|
|
|
const createrObr = () => {
|
|
// #ifndef MP
|
|
let ele = xAvatarGroup.value as UniElement | null;
|
|
|
|
if (ele == null) return;
|
|
if (resizeObserver == null) {
|
|
resizeObserver = new UniResizeObserver((entries : Array<UniResizeObserverEntry>) => {
|
|
entries.forEach(entry => {
|
|
if (entry.target == ele) {
|
|
clearTimeout(tid)
|
|
tid = setTimeout(function () {
|
|
let bund = ele!.getBoundingClientRect()
|
|
calcColumn(bund.width)
|
|
}, 150);
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
resizeObserver!.observe(ele!)
|
|
// #endif
|
|
}
|
|
const removeObr = () => {
|
|
// #ifndef MP
|
|
resizeObserver?.disconnect()
|
|
// #endif
|
|
}
|
|
const itemClick = (item : XAVATARGROUPITEMTYPE, index : number) => {
|
|
emits('click', item.name, index)
|
|
}
|
|
const moreClick = () => {
|
|
emits('moreClick')
|
|
}
|
|
onMounted(() => {
|
|
getNodeinfo()
|
|
// #ifndef MP
|
|
createrObr();
|
|
// #endif
|
|
})
|
|
onUnmounted(() => {
|
|
clearTimeout(tid)
|
|
// #ifndef MP
|
|
removeObr()
|
|
// #endif
|
|
})
|
|
</script>
|
|
|
|
<style>
|
|
.xAvatarGroup {
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: flex-start;
|
|
align-items: flex-start;
|
|
|
|
}
|
|
|
|
.xAvatarGroupImage {
|
|
position: relative;
|
|
/* transition-duration: 200ms;
|
|
transition-property: margin-right, margin-bottom;
|
|
transition-timing-function: linear; */
|
|
}
|
|
|
|
.xAvatarGroupImageMore {
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: center;
|
|
align-items: center;
|
|
z-index: 5;
|
|
}
|
|
|
|
.xAvatarGroupImageBytext {
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.xAvatarGroupWrap {
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.xAvatarGroupNoWrap {
|
|
flex-wrap: nowrap;
|
|
}
|
|
</style> |