Files
qiming/qiming-mobile/uni_modules/lime-checkbox/components/l-checkbox/l-checkbox.uvue

424 lines
14 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="l-checkbox" :class="[rootCasses]" :style="[styles]" @click="handleChange">
<slot name="checkbox" :checked="isChecked" :disabled="isDisabled">
<slot name="icon" :checked="isChecked" :disabled="isDisabled">
<view class="l-checkbox__icon" ref="iconRef"
:class="[
`l-checkbox__icon--${icon}`,
{
'l-checkbox__icon--checked': isChecked,
'l-checkbox__icon--disabled': isDisabled,
'l-checkbox__icon--indeterminate': isIndeterminate,
}
]" :style="[iconStyle]"></view>
</slot>
<text class="l-checkbox__label" :style="[labelStyles]" :class="labelClass" v-if="label!= null || $slots['default'] !=null">
<slot>{{label}}</slot>
</text>
</slot>
</view>
</template>
<script lang="ts" setup>
/**
* Checkbox 复选框组件
* @description 用于在多个选项中进行选择的表单组件,支持单选、全选和不确定态
* <br> 插件类型LCheckboxComponentPublicInstance
* @tutorial https://ext.dcloud.net.cn/plugin?name=lime-checkbox
*
* @property {boolean} defaultChecked 默认选中状态(非受控属性)
* @property {string} label 显示文本(支持插槽)
* @property {boolean} indeterminate 半选状态优先级高于checked
* @property {boolean} disabled 禁用状态覆盖Group设置
* @property {'small' | 'medium' | 'large'} size 组件尺寸
* @value small
* @value medium
* @value large
* @property {string} name 唯一标识符(表单提交使用)
* @property {boolean} checkAll 标记为全选选项需在Group中使用
* @property {string} value 选项值Group模式下必填
* @property {'circle' | 'line' | 'rectangle' | 'dot'} icon 图标样式类型
* @value circle 圆
* @value line 线
* @value rectangle 方
* @value dot 点
* @property {string} fontSize 文本字号支持CSS单位
* @property {string} iconSize 图标尺寸支持CSS单位
* @property {string} checkedColor 选中状态主题色
* @property {string} iconBgColor 图标背景色
* @property {string} iconBorderColor 图标边框色
* @property {string} iconDisabledColor 禁用状态图标颜色
* @property {string} iconDisabledBgColor 禁用状态背景色
* @property {string|object} labelStyle label的样式
* @event {Function} change 状态变化时触发参数CheckboxChangeOptions
*/
import type { CheckboxProps, ManageChildInList, CheckboxStatus, OnCheckedChange, CheckboxChangeOptions, ComputedRef } from './type';
// import { isDarkMode, themeTokens } from '@/uni_modules/lime-style'
const themeVars = inject('limeConfigProviderThemeVars', computed(()=> ({})))
defineSlots<{
checkbox(props : { checked : boolean, disabled: boolean}) : any,
icon(props : { checked : boolean, disabled: boolean}) : any,
default(props : { checked : boolean, disabled: boolean}) : any,
}>()
const name = 'l-checkbox';
const emit = defineEmits(['update:modelValue', 'change']);
const props = withDefaults(defineProps<CheckboxProps>(), {
defaultChecked: false,
indeterminate: false,
disabled: false,
readonly: false,
// checked: null,
// modelValue: null,
size: 'medium',
checkAll: false,
icon: 'rectangle'
})
defineOptions({
mixins: [{
props: {
checked: {
type: [null, Boolean],
default: null,
},
modelValue: {
type: [null, Boolean],
default: null,
},
}
}]
})
const instance = getCurrentInstance()!
const formDisabled = inject<Ref<boolean|null>|null>('formDisabled', null)
const formReadonly = inject<Ref<boolean|null>|null>('formReadonly', null)
const checkboxGroup = inject<LCheckboxGroupComponentPublicInstance|null>('limeCheckboxGroup', null);
const checkboxGroupValue = inject<ComputedRef<any[]>|null>('limeCheckboxGroupValue', null);
const checkboxGroupStatus = inject<ComputedRef<CheckboxStatus>|null>('limeCheckboxGroupStatus', null);
const checkboxGroupCheckedSet = inject<ComputedRef<Set<any>>|null>('limeCheckboxGroupCheckedSet', null);
const manageChildInList = inject<ManageChildInList|null>('limeCheckboxGroupManageChildInList', null);
const onCheckedChange = inject<OnCheckedChange|null>('limeCheckboxGroupOnCheckedChange', null);
if(manageChildInList != null) {
manageChildInList(instance.proxy as LCheckboxComponentPublicInstance, true)
}
const max = computed(():number => checkboxGroup?.max ?? -1)
const _innerChecked = ref(props.defaultChecked)
const innerChecked = computed({
set(value: boolean) {
_innerChecked.value = value
emit('update:modelValue', value)
emit('change', value)
},
get():boolean {
const value = (props.checked ?? props.modelValue)
if(value != null) return value
return _innerChecked.value
}
} as WritableComputedOptions<boolean>)
const isChecked = computed(():boolean=>{
if (props.checkAll) {
const checkAllStatus = checkboxGroupStatus?.value ?? 'uncheck';
return checkAllStatus == 'checked' || checkAllStatus == 'indeterminate'
}
const value = props.value ?? props.name;
if (checkboxGroupCheckedSet != null && value != null) {
return checkboxGroupCheckedSet.value.has(value)
}
return innerChecked.value;
})
const isDisabled = computed(():boolean=>{
if(max.value > -1 && checkboxGroupValue != null) {
return max.value <= checkboxGroupValue.value.length && !isChecked.value;
}
if (props.disabled) return props.disabled;
return formDisabled?.value ?? checkboxGroup?.disabled ?? false;
})
const isReadonly= computed(():boolean=>{
if (props.readonly) return props.readonly;
return formReadonly?.value ?? checkboxGroup?.readonly ?? false;
})
const isIndeterminate = computed(():boolean=>{
if (props.checkAll && checkboxGroupStatus != null) return checkboxGroupStatus.value == 'indeterminate';
return props.indeterminate;
})
const innerIcon = computed(():string => checkboxGroup?.icon ?? props.icon)
const innerSize = computed(():string => checkboxGroup?.size ?? props.size)
const innerIconSize = computed(():string|null => checkboxGroup?.iconSize ?? props.iconSize)
const innerFontSize = computed(():string|null => checkboxGroup?.fontSize ?? props.fontSize)
const innerCheckedColor = computed(():string|null => checkboxGroup?.checkedColor ?? props.checkedColor)
const innerIconBgColor = computed(():string|null => props.iconBgColor ?? checkboxGroup?.iconBgColor)
const innerIconBorderColor = computed(():string|null => props.iconBorderColor ?? checkboxGroup?.iconBorderColor )
const innerIconDisabledColor = computed(():string|null => props.iconDisabledColor ?? checkboxGroup?.iconDisabledColor )
const innerIconDisabledBgColor = computed(():string|null => props.iconDisabledBgColor ?? checkboxGroup?.iconDisabledBgColor)
const rootCasses = computed(():Map<string, boolean>=>{
const cls = new Map<string, boolean>()
cls.set(`${name}--${props.size}`, true)
cls.set(`${name}--disabled`, isDisabled.value)
return cls
})
const iconClasses = computed(():Map<string, boolean>=>{
const cls = new Map<string, boolean>()
cls.set(`${name}__icon--disabled`, isDisabled.value)
cls.set(`${name}__icon--${props.icon}`, true)
// #ifdef APP
cls.set(`${name}__icon--checked`, isChecked.value && innerCheckedColor.value == null)
cls.set(`${name}__icon--indeterminate`, isIndeterminate.value && innerCheckedColor.value == null)
// #endif
// #ifndef APP
cls.set(`${name}__icon--checked`, isChecked.value)
cls.set(`${name}__icon--indeterminate`, isIndeterminate.value)
// #endif
return cls
})
const labelClass = computed(():Map<string, boolean>=>{
const cls = new Map<string, boolean>();
cls.set(`${name}__label--disabled`, isDisabled.value)
return cls
})
const styles = computed(():Map<string, any>=>{
const style = new Map<string, any>();
if(checkboxGroup != null && checkboxGroup.gap != null) {
style.set(checkboxGroup.direction == 'horizontal' ? 'margin-right' : 'margin-bottom', checkboxGroup.gap!)
}
// #ifndef APP
if(innerCheckedColor.value != null) {
style.set('--l-checkbox-icon-checked-color', innerCheckedColor.value!)
}
if(innerIconBorderColor.value != null) {
style.set('--l-checkbox-icon-border-color', innerIconBorderColor.value!)
}
if(innerIconDisabledColor.value != null) {
style.set('--l-checkbox-icon-disabled-color', innerIconDisabledColor.value!)
}
if(innerIconDisabledBgColor.value != null) {
style.set('--l-checkbox-icon-disabled-bg-color', innerIconDisabledBgColor.value!)
}
if(innerFontSize.value != null) {
style.set('--l-checkbox-font-size', innerFontSize.value!)
}
// #endif
return style
})
const iconStyle = computed(():Map<string, any>=>{
const style = new Map<string, any>();
if(innerIconSize.value != null) {
style.set('width', innerIconSize.value!)
style.set('height', innerIconSize.value!)
// #ifndef APP
style.set('--l-checkbox-icon-size', innerIconSize.value!)
// #endif
}
if(innerCheckedColor.value != null) {
// #ifndef APP
style.set('--l-checkbox-icon-checked-color', innerCheckedColor.value!)
// #endif
// #ifdef APP
if(!isDisabled.value && !isChecked.value && ['dot', 'circle', 'rectangle'].includes(innerIcon.value)) {
if(innerIconBgColor.value!=null) {
style.set('background-color', innerIconBgColor.value!)
}
if(innerIconBorderColor.value!=null) {
style.set('border-color', innerIconBorderColor.value!)
}
}
if(isDisabled.value && ['dot', 'circle', 'rectangle'].includes(innerIcon.value)) {
if(innerIconDisabledBgColor.value!=null) {
style.set('background-color', innerIconDisabledBgColor.value!)
}
if(innerIconDisabledColor.value!=null) {
style.set('border-color', innerIconDisabledColor.value!)
}
}
if(isChecked.value && ['dot', 'circle', 'rectangle'].includes(innerIcon.value)) {
style.set('background-color', innerCheckedColor.value!)
style.set('border-color', innerCheckedColor.value!)
}
// #endif
}
return style
})
// const labelStyle = computed(():Map<string, any>=>{
// const style = new Map<string, any>();
// const fontSize = props.fontSize ?? checkboxGroup?.fontSize
// if(fontSize != null) {
// style.set('font-size', fontSize)
// }
// return style
// })
const labelStyles = computed(():any => {
if(typeof props.labelStyle == 'string') {
return `${props.labelStyle};` + (innerIconSize.value != null ? `font-size: ${innerIconSize.value}`: '')
}
if(typeof props.labelStyle == 'object') {
return UTSJSONObject.assign({}, (props.labelStyle??{}) as UTSJSONObject, innerFontSize.value != null ? {'font-size': innerFontSize.value}: {})
}
return {}
})
const handleChange = (e: UniPointerEvent) => {
if (isDisabled.value || isReadonly.value) return;
const value = !isChecked.value;
innerChecked.value = value;
if(onCheckedChange != null) {
onCheckedChange({
checked: value,
checkAll: props.checkAll,
value: props.value ?? props.name //?? instance.uid
})
}
}
// #ifdef APP
const iconRef = ref<UniElement|null>(null)
const update = () => {
if(iconRef.value == null) return
const ctx = iconRef.value!.getDrawableContext()!;
const rect = iconRef.value!.getBoundingClientRect();
// #ifndef APP-HARMONY
const iconSize = rect.width
// #endif
// #ifdef APP-HARMONY
const iconSize = rect.width - 1.5
// #endif
const x = iconSize / 2;
const y = iconSize / 2;
const primaryColor = `${themeVars.value['checkboxIconCheckedColor'] ?? '#3283ff'}`
const disabledColor = `${themeVars.value['checkboxIconDisabledColor'] ?? '#c5c5c5'}`
ctx.reset();
const drawIndeterminate = () => {
ctx.strokeStyle = isDisabled.value
? innerIconDisabledColor.value ?? disabledColor//themes.get(isDarkMode.value ? 'dark' :'light')!
: innerIcon.value == 'line'
? props.checkedColor ?? primaryColor //'#3283ff'
: 'white';
ctx.lineWidth = innerIcon.value == 'line' ? iconSize * 0.16 : iconSize * 0.12;
const width = innerIcon.value == 'line' ? iconSize * 0.8 : iconSize * 0.5;
ctx.beginPath();
ctx.moveTo((iconSize - width) / 2, iconSize * 0.5);
ctx.lineTo((iconSize - width) / 2 + width, iconSize * 0.5);
ctx.stroke();
};
const drawCheckedIcon = () => {
if (isDisabled.value) {
ctx.strokeStyle = innerIconDisabledColor.value ?? disabledColor;
ctx.fillStyle = innerIconDisabledColor.value ?? disabledColor;
} else if (innerIcon.value == 'line') {
ctx.strokeStyle = props.checkedColor ?? primaryColor //'#3283ff';
} else {
ctx.strokeStyle = 'white';
ctx.fillStyle = 'white';
}
ctx.lineWidth = innerIcon.value == 'line' ? iconSize * 0.16 : iconSize * 0.12;
ctx.lineCap = 'round';
if (innerIcon.value == 'circle' || innerIcon.value == 'rectangle') {
ctx.beginPath();
// #ifndef APP-HARMONY
ctx.moveTo(iconSize * 0.2967, iconSize * 0.53);
ctx.lineTo(iconSize * 0.4342, iconSize * 0.6675);
ctx.lineTo(iconSize * 0.7092, iconSize * 0.3925);
// #endif
// #ifdef APP-HARMONY
// 鸿蒙不支持lineCap
ctx.moveTo(iconSize * 0.23, iconSize * 0.45);
ctx.lineTo(iconSize * 0.44, iconSize * 0.6675);
ctx.lineTo(iconSize * 0.73, iconSize * 0.3);
// #endif
ctx.stroke();
} else if (innerIcon.value == 'line') {
ctx.beginPath();
ctx.moveTo(iconSize * 0.10, iconSize * 0.5466);
ctx.lineTo(iconSize * 0.3666, iconSize * 0.8134);
ctx.lineTo(iconSize * 0.90, iconSize * 0.28);
ctx.stroke();
} else if (innerIcon.value == 'dot') {
ctx.beginPath();
ctx.arc(x, y, iconSize * 0.22, 0, Math.PI * 2);
ctx.fill();
}
};
if (isIndeterminate.value) {
drawIndeterminate();
}else if (isChecked.value) {
drawCheckedIcon();
}
ctx.update();
}
const resizeObserver = new UniResizeObserver((entries : Array<UniResizeObserverEntry>) => {
update()
})
const stopWatch = watch(():UniElement|null => iconRef.value, (el:UniElement|null) => {
if(el== null) return
resizeObserver.observe(el)
})
let init = ref(false)
// 在list-view里一开始没有尺寸
watchEffect(()=>{
if(iconRef.value == null) return
if(init.value) {
update()
return
}
iconRef.value?.getBoundingClientRectAsync()?.then(res=>{
requestAnimationFrame(update)
init.value = true
});
})
onBeforeUnmount(()=>{
try {
// 鸿蒙在hbx4.66 由导航返回时会报错
stopWatch()
resizeObserver.disconnect()
} catch(err) {
}
})
// #endif
onBeforeUnmount(()=>{
if(manageChildInList != null) {
manageChildInList(instance.proxy as LCheckboxComponentPublicInstance, false)
}
})
</script>
<style lang="scss">
@import './index-u';
</style>