Files
2026-09-24 16:25:22 +08:00

379 lines
9.2 KiB
Plaintext

<script lang="ts" setup>
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick, getCurrentInstance } from "vue"
import { RADIO_BUTTON } from "../../interface.uts"
import { getUid } from "../../core/util/xCoreUtil.uts"
import { getDefaultColor } from "../../core/util/xCoreColorUtil.uts"
import { checkIsCssUnit, getUnit } from "../../core/util/xCoreUtil.uts"
import { xConfig } from "../../config/xConfig.uts"
/**
* @name 单选按钮组 xRadioButton
* @description 类似单选导航按钮式或者叫分割选择按钮。
* @page /pages/index/radio-button
* @category 导航组件
* @constant 平台兼容
* | Harmony | H5 | andriod | IOS | 小程序 | UTS | UNIAPP-X SDK | version |
| --- | --- | --- | --- | --- | --- | --- | --- |
| ☑ | ☑ | ☑️ | ☑️ | ☑️ | ☑️ | 4.76+ | 1.1.18 |
*/
defineOptions({name:"xRadioButton"})
const proxy = getCurrentInstance()?.proxy??null;
const emits = defineEmits([
/**
* 用户点击变换时触发。
* @param {string} value - 当前选中的id
* @param {number} index - 当前选中的索引值
*/
'change',
/**
* 用户点击按钮时触发,不管禁没禁用。
* @param {string} value - 当前id
* @param {number} index - 当前索引值
*/
'click',
'update:modelValue'
])
type xRadioButtonPropsType = {
/**
* 数据
*/
list: RADIO_BUTTON[],
/**
* 当前选中的id值
*/
modelValue: string,
/**
* 背景
*/
bgColor: string,
/**
* 暗黑时的背景,如果不提供读取页面暗黑背景
*/
darkBgColor: string,
/**
* 激活的按钮块背景
*/
activeColor: string,
/**
* 激活的按钮块暗黑背景,如果不提供inputDarkColor
*/
darkActiveColor: string,
/**
* 激活时的文字颜色,默认取全局主题色。
*/
activeFontColor: string,
/**
* 文字颜色,暗黑时取白色
*/
fontColor: string,
/**
* 文字大小
*/
fontSize: string,
/**
* 按钮组高度
*/
height: string,
/**
* 四周内间隙
*/
space: string,
/**
* 圆角,空值时读取全局配置。
*/
round: string,
/**
* 文本样式,可修改文字的样式。
*/
textStyle: string
}
const props = withDefaults(defineProps<xRadioButtonPropsType>(), {
list: [] as RADIO_BUTTON[],
modelValue: "",
bgColor: "#f3f5f8",
darkBgColor: "",
activeColor: "white",
darkActiveColor: "",
activeFontColor: "",
fontColor: "#333333",
fontSize: "16",
height: "40",
space: "2",
round: "",
textStyle: ""
})
// 响应式数据
const xRadioButton = ref<UniElement|null>(null)
const nowId = ref<string>("")
const id = ref<string>("xRadiobutton-button-id-" + getUid())
const pos_x = ref<number>(0)
const pos_w = ref<number>(0)
const first = ref<boolean>(true)
const resizeObserver = ref<UniResizeObserver | null>(null)
// 计算属性
const _list = computed((): RADIO_BUTTON[] => props.list)
const _textStyle = computed((): string => props.textStyle)
const _bgColor = computed((): string => {
if (xConfig.dark == 'dark') {
if (props.darkBgColor != "") return getDefaultColor(props.darkBgColor)
return getDefaultColor(xConfig.backgroundColorContentDark)
}
return getDefaultColor(props.bgColor)
})
const _activeColor = computed((): string => {
if (xConfig.dark == 'dark') {
if (props.darkActiveColor != "") return getDefaultColor(props.darkActiveColor)
return getDefaultColor(xConfig.inputDarkColor)
}
return getDefaultColor(props.activeColor)
})
const _activeFontColor = computed((): string => {
if (props.activeFontColor == "") {
return getDefaultColor(xConfig.color)
}
return getDefaultColor(props.activeFontColor)
})
const _fontColor = computed((): string => {
if (xConfig.dark == 'dark') return "#ffffff"
return getDefaultColor(props.fontColor)
})
const _fontSize = computed((): string => {
let fontSize = checkIsCssUnit(props.fontSize, xConfig.unit);
if (xConfig.fontScale == 1) return fontSize;
let sizeNumber = parseInt(fontSize)
if (isNaN(sizeNumber)) {
sizeNumber = 14
}
return (sizeNumber * xConfig.fontScale).toString() + getUnit(fontSize)
})
const _height = computed((): string => checkIsCssUnit(props.height, xConfig.unit))
const _space = computed((): string => checkIsCssUnit(props.space, xConfig.unit))
const _round = computed((): string => {
if (props.round == "") {
return checkIsCssUnit(xConfig.radioButtonRadius, xConfig.unit)
}
return checkIsCssUnit(props.round, xConfig.unit)
})
const _styleRadioButtonBgMap = computed((): Map<string, string> => {
let styleMap = new Map<string, string>()
styleMap.set("transitionDuration", first.value ? '0ms' : '350ms')
styleMap.set("backgroundColor", _activeColor.value)
styleMap.set("width", pos_w.value.toString() + 'px')
const raiuds = parseInt(_round.value) - parseInt(_space.value)
styleMap.set("borderRadius", checkIsCssUnit(raiuds, xConfig.unit))
styleMap.set("transform", 'translateX(' + pos_x.value.toString() + 'px)')
return styleMap;
})
// 方法定义
function getIcon(item: RADIO_BUTTON): string {
if (item.icon == null) return ""
return item.icon as string
}
function setPos(): void {
uni.createSelectorQuery().in(proxy)
.selectAll(`.xRadioButtonBtnItem`)
.boundingClientRect().exec((ret) => {
let nodeinfo = ret[0] as NodeInfo[];
let nowindex = nodeinfo.findIndex((el: NodeInfo): boolean => {
let ids = el.id!;
let oldid = id.value + '-' + nowId.value;
return ids == oldid;
})
if (nowindex > -1) {
let diffleft = 0;
let difar = nodeinfo.slice(0, nowindex)
difar.forEach((el: NodeInfo) => {
diffleft += el.width!;
})
let nowitem = nodeinfo[nowindex]
pos_x.value = diffleft
pos_w.value = nowitem.width!;
}
nextTick(() => {
first.value = false;
})
})
}
function itemClick(item: RADIO_BUTTON, index: number): void {
/**
* 点击按钮时触发.
*/
emits('click', item.id, index)
if (nowId.value == item.id) return;
let disabled = (item?.disabled ?? false) as boolean
if (disabled == true) return;
nowId.value = item.id;
setPos();
/**
* 等同v-model
*/
emits("update:modelValue", nowId.value);
/**
* 变换时触发。
* @param value {string} 当前选中的id
*/
emits("change", nowId.value, index)
}
// 监听器
watch((): string => props.modelValue, (newValue: string): void => {
if (nowId.value == newValue) return;
nowId.value = newValue;
setPos();
})
// 生命周期
onMounted((): void => {
nowId.value = props.modelValue;
setPos();
// #ifdef APP || WEB
let ele = xRadioButton.value
if (ele == null) return;
if (resizeObserver.value == null) {
resizeObserver.value = new UniResizeObserver((entries: Array<UniResizeObserverEntry>) => {
entries.forEach(entry => {
if (entry.target == ele) {
setPos();
}
})
})
}
resizeObserver.value!.observe(ele!)
// #endif
// #ifdef MP
setTimeout(function () {
setPos();
}, 150);
// #endif
})
onBeforeUnmount((): void => {
resizeObserver.value?.disconnect()
})
</script>
<template>
<view class="xRadioButton" ref="xRadioButton" :style="{
height: _height,
padding: _space,
backgroundColor: _bgColor,
borderRadius:_round
}">
<view class="xRadioButtonWrap">
<view class="xRadioButtonBtns">
<view @click="itemClick(item,index)"
v-for="(item,index) in _list"
:key="index"
:id="id+'-'+item.id"
:class="[
`xRadioButtonBtnItem-ids-`+item.id,item.disabled==true?'xRadioButtonDisabled':'',
]"
class="xRadioButtonBtnItem"
>
<x-icon :style="{marginRight:item.title==''?'0px':'5px'}" v-if="getIcon(item) !=''" :name="item.icon"
:font-size="_fontSize" :color="item.id==nowId?_activeFontColor:_fontColor"></x-icon>
<text class="xRadioButtonDisabledText" :style="[
{
fontSize:_fontSize,
color:item.id==nowId?_activeFontColor:_fontColor,
},_textStyle
]">{{item.title}}</text>
</view>
</view>
<view class="xRadioButtonbgBtn" :style="_styleRadioButtonBgMap">
</view>
</view>
</view>
</template>
<style scoped>
.xRadioButtonDisabledText{
text-overflow: ellipsis;
white-space: nowrap;
/* #ifdef APP */
lines: 1;
/* #endif */
/* #ifdef MP||WEB */
line-height:1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
/* #endif */
}
.xRadioButton {
display: flex;
flex-direction: row;
}
.xRadioButtonWrap {
height:100%;
flex: 1;
position: relative;
}
.xRadioButtonBtnItem {
display: flex;
height: 100%;
padding: 0 12px;
flex-direction: row;
align-items: center;
justify-content: center;
flex: 1;
flex-shrink: 0;
}
/* #ifdef WEB */
.xRadioButtonBtnItem{
cursor: pointer;
}
.xRadioButtonDisabled{
cursor: not-allowed;
}
/* #endif */
.xRadioButtonDisabled{
opacity: 0.5;
}
.xRadioButtonBtns {
position: absolute;
z-index: 5;
height: 100%;
left: 0;
top: 0;
width: 100%;
display: flex;
flex-direction: row;
justify-content: flex-start;
}
.xRadioButtonbgBtn {
height: 100%;
background-color: white;
width: 0px;
transition-timing-function: cubic-bezier(.18, .89, .32, 1);
transition-property: transform;
transition-duration: 0ms;
}
</style>