Files
schisandra-cloud-album-front/src/components/MyUI/Checkbox/Checkbox.vue
landaiqing 33d76461f1 refine MyUI
2024-11-07 21:39:16 +08:00

250 lines
6.8 KiB
Vue
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.

<script setup lang="ts">
import {computed, ref, watchEffect} from 'vue';
interface Option {
label: string // 选项名
value: string | number // 选项值
disabled?: boolean // 是否禁用选项
}
interface Props {
options?: Option[] // 复选框选项数据
disabled?: boolean // 是否禁用
vertical?: boolean // 是否垂直排列
value?: (string | number)[] // (v-model) 当前选中的值,配合 options 使用
gap?: number | number[] // 多个复选框之间的间距;垂直排列时为垂直间距,单位 px数组间距用于水平排列折行时[水平间距, 垂直间距]
width?: string | number // 复选区域最大宽度,超出后折行,单位 px
height?: string | number // 复选区域最大高度,超出后滚动,单位 px
indeterminate?: boolean // 全选时的样式控制
checked?: boolean // (v-model) 当前是否选中
}
const props = withDefaults(defineProps<Props>(), {
options: () => [],
disabled: false,
vertical: false,
value: () => [],
gap: 8,
width: 'auto',
height: 'auto',
indeterminate: false,
checked: false
});
const checkboxChecked = ref<boolean>();
const optionsCheckedValue = ref<any[]>([]);
const emits = defineEmits(['update:value', 'update:checked', 'change']);
const optionsAmount = computed(() => {
// 选项总数
return props.options.length;
});
const maxWidth = computed(() => {
// 复选区域最大宽度
if (typeof props.width === 'number') {
return `${props.width}px`;
} else {
return props.width;
}
});
const maxHeight = computed(() => {
// 复选区域最大高度
if (typeof props.height === 'number') {
return `${props.height}px`;
} else {
return props.height;
}
});
const gapValue = computed(() => {
if (!props.vertical && Array.isArray(props.gap)) {
return `${props.gap[1]}px ${props.gap[0]}px`;
}
return `${props.gap}px`;
});
watchEffect(() => {
checkboxChecked.value = props.checked;
});
watchEffect(() => {
optionsCheckedValue.value = props.value;
});
function checkDisabled(disabled: boolean | undefined) {
if (disabled === undefined) {
return props.disabled;
} else {
return disabled;
}
}
function onClick(value: string | number) {
if (optionsCheckedValue.value.includes(value)) {
// 已选中
const newVal = optionsCheckedValue.value.filter((target) => target !== value);
optionsCheckedValue.value = newVal;
emits('update:value', newVal);
emits('change', newVal);
} else {
// 未选中
const newVal = [...optionsCheckedValue.value, value];
optionsCheckedValue.value = newVal;
emits('update:value', newVal);
emits('change', newVal);
}
}
function onChecked() {
checkboxChecked.value = !checkboxChecked.value;
emits('update:checked', checkboxChecked.value);
emits('change', checkboxChecked.value);
}
</script>
<template>
<div
class="m-checkbox"
:class="{ 'checkbox-vertical': vertical }"
:style="`--checkbox-gap: ${gapValue}; --checkbox-max-width: ${maxWidth}; --checkbox-max-height: ${maxHeight};`"
>
<template v-if="optionsAmount">
<div class="m-checkbox-wrap" v-for="(option, index) in options" :key="index">
<div
class="m-checkbox-box"
:class="{ 'checkbox-disabled': checkDisabled(option.disabled) }"
@click="checkDisabled(option.disabled) ? () => false : onClick(option.value)"
>
<span class="checkbox-box" :class="{ 'checkbox-checked': optionsCheckedValue.includes(option.value) }"></span>
<span class="checkbox-label">
<slot :label="option.label">{{ option.label }}</slot>
</span>
</div>
</div>
</template>
<div v-else class="m-checkbox-wrap">
<div
class="m-checkbox-box"
:class="{ 'checkbox-disabled': disabled }"
@click="disabled ? () => false : onChecked()"
>
<span
class="checkbox-box"
:class="{
'checkbox-checked': checkboxChecked && !indeterminate,
'checkbox-indeterminate': indeterminate
}"
></span>
<span class="checkbox-label">
<slot></slot>
</span>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.m-checkbox {
display: inline-flex;
flex-wrap: wrap;
gap: var(--checkbox-gap);
color: rgba(0, 0, 0, 0.88);
font-size: 14px;
line-height: 1;
max-width: var(--checkbox-max-width);
max-height: var(--checkbox-max-height);
overflow: auto;
.m-checkbox-wrap {
.m-checkbox-box {
display: inline-flex;
align-items: flex-start;
cursor: pointer;
&:not(.checkbox-disabled):hover {
.checkbox-box {
border-color: @themeColor;
}
}
.checkbox-box {
/*
如果所有项目的flex-shrink属性都为1当空间不足时都将等比例缩小
如果一个项目的flex-shrink属性为0其他项目都为1则空间不足时前者不缩小。
*/
flex-shrink: 0; // 默认 1.即空间不足时,项目将缩小
position: relative;
top: 3px;
width: 16px;
height: 16px;
background: transparent;
border: 1px solid #d9d9d9;
border-radius: 4px;
transition: all 0.3s;
&::after {
box-sizing: border-box;
position: absolute;
top: 50%;
left: 21.5%;
display: table;
width: 5.7142857142857135px;
height: 9.142857142857142px;
border: 2px solid #fff;
border-top: 0;
border-left: 0;
transform: rotate(45deg) scale(0) translate(-50%, -50%);
opacity: 0;
content: '';
transition: all 0.1s cubic-bezier(0.71, -0.46, 0.88, 0.6),
opacity 0.1s;
}
}
.checkbox-checked {
background-color: @themeColor;
border-color: @themeColor;
&::after {
opacity: 1;
transform: rotate(45deg) scale(1) translate(-50%, -50%);
transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s;
}
}
.checkbox-indeterminate {
&::after {
top: 50%;
left: 50%;
width: 8px;
height: 8px;
background-color: @themeColor;
border: 0;
transform: translate(-50%, -50%) scale(1);
opacity: 1;
}
}
.checkbox-label {
word-break: break-all;
padding: 0 8px;
line-height: 1.5714285714285714;
}
}
.checkbox-disabled {
color: rgba(0, 0, 0, 0.25);
cursor: not-allowed;
.checkbox-box {
border-color: #d9d9d9;
background-color: rgba(0, 0, 0, 0.04);
&::after {
border-color: rgba(0, 0, 0, 0.25);
animation-name: none;
}
}
}
}
}
.checkbox-vertical {
flex-direction: column;
flex-wrap: nowrap;
}
</style>