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

422 lines
10 KiB
Plaintext

<script lang="ts" setup>
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick, getCurrentInstance, inject } from "vue"
import { getDefaultColor, colorAddDeepen } from "../../core/util/xCoreColorUtil.uts"
import { checkIsCssUnit, getUid, getUnit } from "../../core/util/xCoreUtil.uts"
import { xConfig } from "../../config/xConfig.uts"
import { CHECKBOX_ITEM_INFO } from '../../interface.uts';
/**
* @name 多选框 xCheckbox
* @page /pages/index/checkbox
* @category 表单组件
* @description 使用时,x-checkbox能单独使用,如果要与x-checkbox-group配合,只能是它的的直接子节点
* @constant 平台兼容
* | Harmony | H5 | andriod | IOS | 小程序 | UTS | UNIAPP-X SDK | version |
| --- | --- | --- | --- | --- | --- | --- | --- |
| ☑ | ☑ | ☑️ | ☑️ | ☑️ | ☑️ | 4.76+ | 1.1.18 |
*/
defineOptions({name:"xCheckbox"})
defineSlots<{
label(props:{
checked: boolean,
value: string|number|boolean
}):any
}>()
type findeParentCall = (parent:VueComponent|null)=>VueComponent|null
export type xCheckboxPropsType = {
/**
* 当前主题色,空值时取全局
*/
color: string,
/**
* 当前未选中时主题色,空值时取全局
*/
unCheckColor: string,
/**
* 当前未选中时的暗黑主题色
*/
darkUnCheckColor: string,
/**
* 当前选中的值,受控时为v-model="x"
*/
modelValue: string|number|boolean,
/**
* 非受控下默认选中的状态
*/
defaultChecked: boolean,
/**
* 选中的值
*/
value: string|number|boolean,
/**
* 未选中的值
*/
unCheckValue: string|number|boolean,
/**
* 是否禁用
*/
disabled: boolean,
/**
* 选中的图标名称。
*/
icon: string,
/**
* 右侧文字。
*/
label: string,
/**
* 是否隐藏选中框。然后利用默认插槽自定义选中所有样式和状态。
*/
hiddenCheckbox: boolean,
/**
* 半选中
*/
indeterminate: boolean,
/**
* 尺寸
*/
size: string,
/**
* 中间小图标大小
*/
iconSize: string,
/**
* 文字大小
*/
labelFontSize: string,
/**
* label和选中框间的间距
*/
labelSpace: string,
/**
* 圆角
*/
round: string
}
const props = withDefaults(defineProps<xCheckboxPropsType>(), {
color: "",
unCheckColor: "",
darkUnCheckColor: "",
modelValue: '',
defaultChecked: false,
value: '1',
unCheckValue: '',
disabled: false,
icon: "check-line",
label: "",
hiddenCheckbox: false,
indeterminate: false,
size: "24",
iconSize: "20",
labelFontSize: "15px",
labelSpace: '10',
round: '4'
})
const emits = defineEmits([
/**
* 用户交互切换,选中变换时触发。
* @param {Boolean} check - 当前是否选中
* @param {string|number|boolean} value - 当前选中的值
*/
'change',
/**
* 点击事件
*/
'click',
'update:modelValue'
])
const proxy = getCurrentInstance()?.proxy ?? null
const checkboxBoxIconRef = ref<UniElement|null>(null)
// 注入父组件的响应式数据
const groupModelValue = inject('xCheckboxModelvalue', computed(():Array<string|number|boolean>|null => null))
// 响应式数据
const nowValue = ref<string|number|boolean>('')
const boxId = ref("xCheckbox-" + getUid())
const tid = ref(0)
const isDestroy = ref(false)
const undefaultCheck = ref(false)
// 计算属性
const _color = computed((): string => {
if (props.color == "") return getDefaultColor(xConfig.color)
return getDefaultColor(props.color)
})
const _round = computed((): string => {
return checkIsCssUnit(props.round, xConfig.unit);
})
const _unCheckColor = computed((): string => {
if (xConfig.dark == 'dark' && props.darkUnCheckColor != '') {
return getDefaultColor(props.darkUnCheckColor)
}
if (props.unCheckColor == "") return getDefaultColor(xConfig.unRadioAndCheckBoxColor)
return getDefaultColor(props.unCheckColor)
})
const _isCheck = computed((): boolean => {
// 如果在组内,使用组的值判断
if (groupModelValue.value != null) {
return groupModelValue.value!.includes(props.value)
}
// 单独使用时,使用自己的值判断
return nowValue.value == props.value || undefaultCheck.value || props.indeterminate
})
const _disabled = computed((): boolean => {
return props.disabled
})
const _label = computed((): string => {
return props.label
})
const _indeterminate = computed((): boolean => {
return props.indeterminate
})
const _size = computed((): string => {
let size = checkIsCssUnit(props.size, xConfig.unit);
if (xConfig.fontScale == 1) return size;
let sizeNumber = parseInt(size)
if (isNaN(sizeNumber)) {
sizeNumber = 24
}
return (sizeNumber * xConfig.fontScale).toString() + getUnit(size)
})
const _labelSpace = computed((): string => {
return checkIsCssUnit(props.labelSpace, xConfig.unit)
})
// 方法
let findParent:findeParentCall|null = null;
findParent = (parent:VueComponent|null):VueComponent|null => {
if(parent == null) return null;
// #ifdef WEB||APP-IOS||MP-WEIXIN
if((parent.$parent?.$options?.name?.indexOf('xCheckboxGroup')??-1)>-1) return parent.$parent;
// #endif
// #ifdef APP-HARMONY
if(parent.$parent?.$options?.name?.indexOf('xCheckboxGroup')>-1) return parent.$parent;
// #endif
// #ifdef APP-ANDROID
if(parent.$parent instanceof XCheckboxGroupComponentPublicInstance) return parent.$parent;
// #endif
let findParentCallReal = findParent!
let parents = findParentCallReal(parent.$parent)
// #ifdef WEB||APP-IOS||MP-WEIXIN
if((parents?.$options?.name?.indexOf('xCheckboxGroup')??-1)>-1) return parents;
// #endif
// #ifdef APP-HARMONY
if(parents?.$options?.name?.indexOf('xCheckboxGroup')>-1) return parents;
// #endif
// #ifdef APP-ANDROID
if(parents instanceof XCheckboxGroupComponentPublicInstance) return parents;
// #endif
return null;
}
function setAni() {
if (props.hiddenCheckbox || isDestroy.value) return;
try {
let el = checkboxBoxIconRef.value!;
el.style.setProperty('opacity', _isCheck.value ? 1 : 0)
el.style.setProperty('transform', `scale(${_isCheck.value ? 0.74 : 0})`)
} catch (e) {
//TODO handle the exception
}
}
function pushDataToParent(isChange : boolean) {
let pelement = findParent!(proxy);
if (pelement == null) return;
let parent : XCheckboxGroupComponentPublicInstance = pelement as XCheckboxGroupComponentPublicInstance;
// #ifndef APP-ANDROID
if (typeof parent?.addItem != 'function') return;
// #endif
parent.addItem({
id: boxId.value as string,
nowvalue: nowValue.value,
value: props.value,
unvalue: props.unCheckValue
} as CHECKBOX_ITEM_INFO, isChange)
}
function boxClick() {
/**
* 点击事件
*/
emits("click")
if (_disabled.value) return;
/**
* 要点提示:当本组件属性半选中状态时,应该继续保持选中状态,
* 并触发change。
*/
if ((_isCheck.value && !props.indeterminate) || undefaultCheck.value) {
nowValue.value = props.unCheckValue
} else {
nowValue.value = props.value
}
/**
* 当前选中的值,等同v-model
*/
emits("update:modelValue", nowValue.value)
/**
* 用户交互切换,选中变换时触发。
* @param check {boolean} 当前是否选中
* @param value {string|number|boolean} 当前选中的值
*/
emits("change", _isCheck.value, nowValue.value)
if(undefaultCheck.value){
undefaultCheck.value = false
}else{
pushDataToParent(true);
}
setAni();
}
/**
* 手动切换选中状态,这里不会触发change
*/
function setSelected(val : Array<string|number|boolean>) {
if (!Array.isArray(val)) {
throw new Error("val must be an array");
}
const isChecked = val.includes(props.value);
if (isChecked) {
nowValue.value = props.value;
// emits("update:modelValue", nowValue.value);
} else {
nowValue.value = props.unCheckValue;
// emits("update:modelValue", nowValue.value);
}
setAni()
}
// 监听器
watch((): string|number|boolean => props.modelValue, (newValue : string|number|boolean) => {
if (newValue == nowValue.value) return;
nowValue.value = newValue;
setAni();
// pushDataToParent();
})
watch((): boolean => props.indeterminate, (newValue : boolean) => {
setAni();
})
// 监听组内值变化
if (groupModelValue.value != null) {
watch(groupModelValue, (val:Array<string|number|boolean>|null) => {
if(val!=null){
setSelected(val!);
}
})
}
// 生命周期
onBeforeUnmount(() => {
isDestroy.value = true;
})
onMounted(() => {
isDestroy.value = false;
// 如果在group中,默认选中状态将失效.否则数据异常错乱.
let pelement = findParent!(proxy);
if(pelement!=null){
undefaultCheck.value = false
}else{
undefaultCheck.value = props.defaultChecked
}
nextTick(() => {
nowValue.value = props.modelValue;
setAni();
pushDataToParent(false);
})
})
</script>
<template>
<view :class="[_disabled?'checkboxDisabled':'']" class="checkbox" @click="boxClick">
<view class="checkboxBox" v-if="!hiddenCheckbox" :style="{
backgroundColor:_isCheck?_color:'transparent',
border: `1px solid ${_isCheck?_color:_unCheckColor}`,
borderRadius:_round,
width:_size,
height:_size
}">
<view :id="boxId" ref="checkboxBoxIconRef" class="checkboxBoxIcon">
<x-icon color="white" :name="_indeterminate?'subtract-line':icon" :font-size="iconSize"></x-icon>
</view>
</view>
<view class="checkboxLabelBox" :style="{paddingLeft:!hiddenCheckbox?_labelSpace:'0px'}">
<!--
@slot 默认文本插槽
@prop {boolean} checked - 是否选中
@prop {number} value - 当前选中的值
-->
<slot name="label" :checked="_isCheck" :value="nowValue">
<x-text :font-size="labelFontSize" class="checkboxLabel">{{_label}}</x-text>
</slot>
</view>
</view>
</template>
<style scoped>
.checkbox {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
}
.checkboxLabelBox {
flex: 1;
}
.checkboxDisabled {
opacity: 0.7;
}
.checkboxBoxIcon {
transition-duration: 350ms;
transition-timing-function: cubic-bezier(.18, .89, .32, 1);
transition-property: opacity, transform;
opacity: 0;
transform: scale(0);
}
.checkboxBox {
/* border-radius: 4px; */
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.checkboxLabelBoxLeftSpace {
padding-left: 10px;
}
.checkboxLabel {
font-size: 14px;
}
</style>