refine MyUI

This commit is contained in:
landaiqing
2024-11-07 21:39:16 +08:00
parent 2263ff213c
commit 33d76461f1
73 changed files with 20404 additions and 58 deletions

View File

@@ -0,0 +1,249 @@
<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>