173 lines
5.2 KiB
Vue
173 lines
5.2 KiB
Vue
|
|
<template>
|
|||
|
|
<view class="u-checkbox-group u-clearfix" :class="customClass" :style="$u.toStyle(customStyle)">
|
|||
|
|
<slot></slot>
|
|||
|
|
</view>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script lang="ts">
|
|||
|
|
export default {
|
|||
|
|
name: 'u-checkbox-group',
|
|||
|
|
options: {
|
|||
|
|
addGlobalClass: true,
|
|||
|
|
// #ifndef MP-TOUTIAO
|
|||
|
|
virtualHost: true,
|
|||
|
|
// #endif
|
|||
|
|
styleIsolation: 'shared'
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<script setup lang="ts">
|
|||
|
|
import { getCurrentInstance, computed, watch, nextTick, onMounted } from 'vue';
|
|||
|
|
import { $u, useParent, useChildren, useDebounce } from '../..';
|
|||
|
|
import { CheckboxGroupProps } from './types';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* checkboxGroup 开关选择器父组件Group
|
|||
|
|
* @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
|
|||
|
|
* @tutorial https://uviewpro.cn/zh/components/checkbox.html
|
|||
|
|
* @property {Array} modelValue 绑定值,选中的复选框name组成的数组(支持v-model双向绑定)
|
|||
|
|
* @property {String Number} max 最多能选中多少个checkbox(默认999)
|
|||
|
|
* @property {String Number} size 组件整体的大小,单位rpx(默认40)
|
|||
|
|
* @property {Boolean} disabled 是否禁用所有checkbox(默认false)
|
|||
|
|
* @property {String Number} icon-size 图标大小,单位rpx(默认20)
|
|||
|
|
* @property {Boolean} label-disabled 是否禁止点击文本操作checkbox(默认false)
|
|||
|
|
* @property {String} width 宽度,需带单位
|
|||
|
|
* @property {String} shape 外观形状,shape-方形,circle-圆形(默认circle)
|
|||
|
|
* @property {Boolean} wrap 是否每个checkbox都换行(默认false)
|
|||
|
|
* @property {String} active-color 选中时的颜色,应用到所有子Checkbox组件(默认主题色primary)
|
|||
|
|
* @event {Function} change 任一个checkbox状态发生变化时触发,回调为选中的name数组
|
|||
|
|
* @example <u-checkbox-group v-model="selectedValues">
|
|||
|
|
* <u-checkbox name="apple">苹果</u-checkbox>
|
|||
|
|
* <u-checkbox name="banana">香蕉</u-checkbox>
|
|||
|
|
* </u-checkbox-group>
|
|||
|
|
*/
|
|||
|
|
const props = defineProps(CheckboxGroupProps);
|
|||
|
|
const emit = defineEmits(['update:modelValue', 'change']);
|
|||
|
|
|
|||
|
|
// 使用父组件Hook
|
|||
|
|
const { children, broadcast } = useParent('u-checkbox-group');
|
|||
|
|
const { emitToParent } = useChildren('u-checkbox-group', 'u-form-item');
|
|||
|
|
const { debounce } = useDebounce(1);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据modelValue设置子组件状态
|
|||
|
|
*/
|
|||
|
|
function syncChildrenSelection() {
|
|||
|
|
if (!children || children.length === 0 || !props.modelValue) return;
|
|||
|
|
const modelValueSet = new Set(props.modelValue);
|
|||
|
|
children.forEach((child: any) => {
|
|||
|
|
const childValue = child.getExposed?.()?.value;
|
|||
|
|
const shouldBeChecked = modelValueSet.has(childValue);
|
|||
|
|
const isCurrentlyChecked = child.getExposed?.()?.isChecked.value;
|
|||
|
|
|
|||
|
|
if (shouldBeChecked !== isCurrentlyChecked) {
|
|||
|
|
child.getExposed?.()?.setChecked({ checked: shouldBeChecked });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 监听modelValue变化,同步子组件状态
|
|||
|
|
*/
|
|||
|
|
watch(
|
|||
|
|
() => props.modelValue,
|
|||
|
|
() => {
|
|||
|
|
syncChildrenSelection();
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 派发 change 事件和表单校验
|
|||
|
|
*/
|
|||
|
|
function emitEvent() {
|
|||
|
|
debounce(() => {
|
|||
|
|
// 收集所有选中的 name
|
|||
|
|
let values: any[] = [];
|
|||
|
|
children.forEach((child: any) => {
|
|||
|
|
if (child.getExposed?.()?.isChecked.value) {
|
|||
|
|
values.push(child.getExposed?.()?.value);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
emit('change', values);
|
|||
|
|
emit('update:modelValue', values);
|
|||
|
|
setTimeout(() => {
|
|||
|
|
emitToParent('onFormChange', values);
|
|||
|
|
}, 60);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 全选/全不选方法
|
|||
|
|
*/
|
|||
|
|
function setAllChecked(checked: boolean) {
|
|||
|
|
if (props.disabled) {
|
|||
|
|
console.warn('u-checkbox-group已禁用,无法操作');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
broadcast('setChecked', { checked });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取选中的值
|
|||
|
|
*/
|
|||
|
|
function getSelectedValues() {
|
|||
|
|
return children
|
|||
|
|
.filter(child => child.getExposed?.()?.isChecked.value)
|
|||
|
|
.map(child => child.getExposed?.()?.name)
|
|||
|
|
.filter(Boolean);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 验证选择是否超过最大数量
|
|||
|
|
*/
|
|||
|
|
function validateSelection() {
|
|||
|
|
const selectedCount = children.filter(child => child.getExposed?.()?.isChecked.value).length;
|
|||
|
|
if (props.max && selectedCount >= props.max) {
|
|||
|
|
$u.toast(`超过最大选择数量: ${props.max}`);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
onMounted(() => {
|
|||
|
|
nextTick(() => {
|
|||
|
|
syncChildrenSelection();
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 使用defineExpose暴露给外部
|
|||
|
|
defineExpose({
|
|||
|
|
// props
|
|||
|
|
props,
|
|||
|
|
|
|||
|
|
// 方法
|
|||
|
|
emitEvent,
|
|||
|
|
setAllChecked,
|
|||
|
|
getSelectedValues,
|
|||
|
|
validateSelection,
|
|||
|
|
syncChildrenSelection,
|
|||
|
|
|
|||
|
|
// 计算属性
|
|||
|
|
selectedCount: computed(() => children.filter(child => child.getExposed?.()?.isChecked.value).length),
|
|||
|
|
isFull: computed(() => {
|
|||
|
|
const selectedCount = children.filter(child => child.getExposed?.()?.isChecked.value).length;
|
|||
|
|
return props.max && selectedCount >= props.max;
|
|||
|
|
}),
|
|||
|
|
isEmpty: computed(() => children.filter(child => child.getExposed?.()?.isChecked.value).length === 0),
|
|||
|
|
// 工具方法
|
|||
|
|
getChildrenCount: () => children.length
|
|||
|
|
});
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style lang="scss" scoped>
|
|||
|
|
@import '../../libs/css/style.components.scss';
|
|||
|
|
|
|||
|
|
.u-checkbox-group {
|
|||
|
|
/* #ifndef MP || APP-NVUE */
|
|||
|
|
display: inline-flex;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
/* #endif */
|
|||
|
|
}
|
|||
|
|
</style>
|