web技术支持| 基于vue3实现自己的组件库第三章:Checkbox组件

大家好今天的内容是基于vue3实现自己的组件库系列第二章,本文默认你会安装和创建vue3项目,如果不会请参考vue官网

Checkbox.vue Template

<template>
    <div :class='["v-checkbox", { border }, 
    { disabled: proxyDisabled || disabled || computedDisabled }, 
    { medium: border && (size || proxySize) === "medium" },
    { small: border && (size || proxySize) === "small" },
    { mini: border && (size || proxySize) === "mini" },
    { "v-radio-checked": computedChecked && !computedDisabled }]' 
    @click.stop='handleChecked'>
        <input type="checkbox" :checked='computedChecked' :disabled='disabled' :name='name || modelValue || proxyValue' :value='label'>
        <span :class='["v-radio-input", { checked: computedChecked }, { indeterminate }]'></span>
        <span :class='["v-radio-label", { active: computedChecked }]'>
            <slot></slot>
        </span>
    </div>
</template>

Checkbox.vue Script

<script>
import { inject, computed } from 'vue';
import VairCheckout from '@/types/checkout';
export default {
    name: 'checkbox',
    props: VairCheckout,
    setup (props, ctx) {
        const proxyValue = inject('proxyValue');
        const proxyDisabled = inject('proxyDisabled');
        const proxySize = inject('proxySize');
        const proxyMin = inject('proxyMin');
        const proxyMax = inject('proxyMax');
        const update = inject('update');

        const handleChecked = () => {
            if (!props.disabled && !computedDisabled.value) {
                if (proxyValue) {
                    ctx.emit('change', props.label);
                    update(props.label);
                } else {
                    const value = props.modelValue? false : true;
                    ctx.emit('update:modelValue', value);
                    ctx.emit('change', value);
                }
            }
        };

        const computedDisabled = computed(() => {
            var bool = false;
            if (proxyMax || proxyMin) {
                if (proxyMax && proxyValue.value.length >= proxyMax.value) {
                    if (!computedChecked.value) {
                        bool = true;
                    }
                } else if (proxyMin && proxyValue.value.length <= proxyMin.value) {
                    if (computedChecked.value) {
                        bool = true;
                    }
                }
            }
            return bool;
        });

        const computedChecked = computed(() => {
            if (proxyValue) { // 如果是被 checkbox-group 组件包裹着的话就判断数组中有没有符合的值
                return proxyValue.value.find(item => item === props.label);
            } else {
                return props.modelValue;
            }
        });

        return {
            handleChecked,
            proxyDisabled,
            computedChecked,
            computedDisabled,
            proxyValue,
            proxySize
        }
    }
}
</script>

Checkbox.js

const VairCheckout = {
    modelValue: null, // 绑定值
    name: String, // 原生 name 属性
    label: [Number, String, Boolean], // Checkbox 的 value
    size: String, // Radio 的尺寸,仅在 border 为真时有效
    indeterminate: { // 设置 indeterminate 状态,只负责样式控制
        type: Boolean,
        default: () => {
            return false
        }
    },
    border: { // 是否显示边框
        type: Boolean,
        default: () => {
            return false
        }
    },
    disabled: { // 是否禁用
        type: Boolean,
        default: () => {
            return false
        }
    },
};

// Event
    // change (label)

export default VairCheckout;

Checkbox.vue Style

<style lang='less' scoped>
@import url('../../assets/css/animation.css');
.v-checkbox {
    display: flex;
    align-items: center;
    cursor: pointer;
    transition: .3s;
    margin-right: 30px;
    &:last-child {
        margin-right: 0;
    }
    input {
        display: none;
    }
    .v-radio-input {
        border: 1px solid#dcdfe6;
        width: 14px;
        height: 14px;
        position: relative;
        box-sizing: border-box;
        border-radius: 2px;
        transition: .3s;
        &:after {
            box-sizing: content-box;
            content: "";
            border: 1px solid #fff;
            border-left: 0;
            border-top: 0;
            height: 7px;
            left: 4px;
            position: absolute;
            top: 1px;
            transform: rotate(45deg) scaleY(0);
            width: 3px;
            transition: transform .15s ease-in .05s;
            transform-origin: center;
        }
    }
    .indeterminate {
        background-color: #409eff;
        border-color: #409eff;
        position: relative;
        &:after {
            content: "";
            position: absolute;
            display: block;
            background-color: #fff;
            height: 2px;
            transform: scale(.5);
            left: 0;
            right: 0;
            width: 10px;
            transition: width 0s;
            top: 4px;
        }
    }
    .v-radio-label {
        color: #606266;
        font-weight: 500;
        margin-left: 10px;
        font-size: 14px;
        transition: .3s;
        white-space: nowrap;
        user-select: none;
    }
    .active {
        color: #409eff;
    }
    .checked {
        background-color: #409eff;
        border-color: #409eff;
        &:after {
            transform: rotate(45deg) scaleY(1);
        }
    }
    &:hover {
        border-color: #409eff;
        .v-radio-label {
            color: #409eff;
        }
        .v-radio-input {
            border-color: #409eff;
        }
    }
}
.border {
    border: 1px solid #dcdfe6;
    border-radius: 4px;
    padding: 12px 14px;
}
.medium {
    padding: 10px 10px;
}
.small {
    padding: 8px 8px;
}
.mini {
    padding: 6px 6px;
}
.v-radio-checked {
    border-color: #409eff;
}
.disabled {
    border-color: #dcdfe6;
    .v-radio-input {
        background-color: #F5F7FA;
        border: 1px solid#dcdfe6;
    }
    .v-radio-label {
        color: #c0c4cc;
    }
    .active {
        color: #c0c4cc;
    }
    .checked {
        background-color: #f2f6fc;
        border-color: #dcdfe6;
        &:after {
            transform: rotate(45deg) scaleY(1);
            border-color: #c0c4cc;
        }
    }
    &:hover {
        border-color: #dcdfe6;
        .v-radio-label {
            color: #c0c4cc;
        }
        .v-radio-input {
            border-color: #c0c4cc;
        }
    }
    cursor: not-allowed;
}
</style>

Checkbox-group.vue Template

<template>
    <div class="v-radio-group">
        <slot></slot>
    </div>
</template>

Checkbox-group.vue Script

<script>
import { provide, ref, watchEffect } from 'vue';
export default {
    name: 'checkbox-group',
    props: {
        modelValue: Array, // 绑定值
        size: String, // 单选框组尺寸,仅对按钮形式的 Radio 或带有边框的 Radio 有效
        min: Number, // 可被勾选的 checkbox 的最小数量
        max: Number, // 可被勾选的 checkbox 的最大数量
        textColor: { // 按钮形式的 Radio 激活时的文本颜色
            type: String,
            default: () => {
                return '#FFFFFF';
            }
        },
        fill: { // 按钮形式的 Radio 激活时的填充色
            type: String,
            default: () => {
                return '#409EFF';
            }
        },
        disabled: { // 是否禁用
            type: Boolean,
            default: () => {
                return false
            }
        },
    },
    setup (props, ctx) {
        const proxyValue = ref(props.modelValue);
        const proxyDisabled = ref(props.disabled);
        const proxyFill = ref(props.fill);
        const proxyTextColor = ref(props.textColor);
        const proxySize = ref(props.size);
        const proxyMin = ref(props.min);
        const proxyMax = ref(props.max);

        watchEffect(() => {
            if (!Array.isArray(props.modelValue)) {
                throw new TypeError(`v-model wants to receive an array, but received a ${typeof props.modelValue}`);
            }
            proxySize.value = props.size;
            proxyTextColor.value = props.textColor;
            proxyFill.value = props.fill;
            proxyMin.value = props.min;
            proxyMax.value = props.max;
            proxyDisabled.value = props.disabled;
            proxyValue.value = props.modelValue;
        });

        const update = (value) => {
            const bool = props.modelValue.find(item => item === value);
            var list = [];
            if (bool) {
                props.modelValue.forEach(item => {
                    if (item !== value) {
                        list.push(item);
                    }
                });
            } else {
                list = [].concat(props.modelValue, [value]);
            }
            ctx.emit('update:modelValue', list);
            ctx.emit('change', list);
        };

        provide('proxyValue', proxyValue);
        provide('proxyFill', proxyFill);
        provide('proxyDisabled', proxyDisabled);
        provide('proxyTextColor', proxyTextColor);
        provide('proxySize', proxySize);
        provide('proxyMax', proxyMax);
        provide('proxyMin', proxyMin);
        provide('update', update);
        provide('border', true);
    }
}
</script>

Checkbox-group.vue Style

<style lang='less' scoped>
.v-radio-group {
    display: flex;
}
</style>

Checkbox-button.vue Template

<template>
    <div 
    :class='["v-radio-button", 
    { disabled: proxyDisabled || disabled || computedDisabled }, 
    { medium: proxySize === "medium" },
    { small: proxySize === "small" },
    { mini: proxySize === "mini" },
    { disabledChecked: computedChecked && (proxyDisabled || disabled || computedDisabled) }, 
    { "v-radio-button-checked": computedChecked && !computedDisabled}, { border }]' 
    :style='{ 
        backgroundColor: (computedChecked && !proxyDisabled && !disabled && !computedDisabled)? proxyFill : "", 
        color: (computedChecked && !proxyDisabled && !disabled && !computedDisabled)? proxyTextColor : ""
    }'
    @click.stop='handleChecked'>
        <input type="checkbox" :checked='computedChecked' :disabled='disabled' :name='name || modelValue || proxyValue' :value='label'>
        <span
        :style='{ color: (computedChecked && !proxyDisabled && !disabled && !computedDisabled)? proxyTextColor : "" }'
        ><slot></slot></span>
    </div>
</template>

Checkbox-button.vue Script

<script>
import { computed, inject } from 'vue';
export default {
    name: 'checkbox-button',
    props: {
        name: String, // 原生 name 属性
        label: [Number, String, Boolean], // Radio 的 value
        disabled: { // 是否禁用
            type: Boolean,
            default: () => {
                return false
            }
        },
    },
    setup (props, ctx) {
        const proxyValue = inject('proxyValue');
        const proxyDisabled = inject('proxyDisabled');
        const proxySize = inject('proxySize');
        const proxyFill = inject('proxyFill');
        const proxyTextColor = inject('proxyTextColor');
        const proxyMin = inject('proxyMin');
        const proxyMax = inject('proxyMax');
        const update = inject('update');
        const border = inject('border');

        const handleChecked = () => {
            if (!computedChecked.value && !computedDisabled.value) {
                ctx.emit('change', props.label);
            }
            if (!props.disabled && !(proxyDisabled && proxyDisabled.value) && !computedDisabled.value) {
                update(props.label);
            }
        };

        const computedDisabled = computed(() => {
            var bool = false;
            if (proxyMax || proxyMin) {
                if (proxyMax && proxyValue.value.length >= proxyMax.value) {
                    if (!computedChecked.value) {
                        bool = true;
                    }
                } else if (proxyMin && proxyValue.value.length <= proxyMin.value) {
                    if (computedChecked.value) {
                        bool = true;
                    }
                }
            }
            return bool;
        });

        const computedChecked = computed(() => {
            return proxyValue.value.find(item => item === props.label);
        });

        return {
            handleChecked,
            proxyValue,
            computedChecked,
            proxyDisabled,
            proxySize,
            border,
            proxyFill,
            proxyTextColor,
            computedDisabled
        }
    }
}
</script>

Checkbox-button.vue Style

<style lang='less' scoped>
.v-radio-button {
    display: inline-block;
    padding: 12px 18px;
    border-radius: 4px;
    border: 1px solid #dcdfe6;
    cursor: pointer;
    transition: .3s;
    input {
        display: none;
    }
    span {
        color: #606266;
        font-weight: 500;
        font-size: 14px;
        white-space: nowrap;
        transition: .3s;
        user-select: none;
    }
    &:hover {
        span {
            color: #409eff;
        }
    }
}
.v-radio-button-checked {
    background-color: #409eff;
    span {
        color: #fff;
    }
    &:hover {
        span {
            color: #fff;
        }
    }
}
.medium {
    padding: 10px 16px;
}
.small {
    padding: 8px 14px;
}
.mini {
    padding: 6px 12px;
}
.disabled {
    border-color: #dcdfe6;
    background-color: #fff;
    cursor: not-allowed;
    span {
        color: #c0c4cc;
    }
    &:hover {
        span {
            color: #c0c4cc;
        }
    }
}
.disabledChecked {
    background-color: #f2f6fc;
    span {
        color: #c0c4cc;
    }
    &:hover {
        span {
            color: #c0c4cc;
        }
    }
}
.border {
    border-radius: 0;
    border-right: none;
    &:first-child {
        border-radius: 4px 0 0 4px;
    }
    &:last-child {
        border-right: 1px solid #dcdfe6;
        border-radius: 0 4px 4px 0;
    }
}
</style>

index.js 出口文件中引入组件

// Checkbox 单选框
import Checkbox from './components/Checkbox/Checkbox.vue';
import CheckboxGroup from './components/Checkbox/components/Checkbox-group.vue';
import CheckboxButton from './components/Checkbox/components/Checkbox-button.vue';

const Vair = function(Vue) {
    // Checkbox 单选框
    Vue.component(`v-${Checkbox.name}`, Checkbox);
    Vue.component(`v-${CheckboxGroup.name}`, CheckboxGroup);
    Vue.component(`v-${CheckboxButton.name}`, CheckboxButton);
}

export default Vair;
复制代码

使用组件

  • 在main.js中引入
import { createApp } from 'vue'; 
import App from './App.vue'; 
import Vair from './libs/vair/index.js'; 
const app = createApp(App); 
app.use(Vair).mount('#app');

App.vue中调用

<template>
    <div class='checkbox'>
        <div class='box'>
            <p>基础用法</p>
            <div class='son'>
                <v-checkbox v-model="checked" @change='change'>上海</v-checkbox>
            </div>
        </div>

        <div class='box'>
            <p>禁用状态</p>
            <div class='son'>
                <v-checkbox v-model="checked1" @change='change' disabled>上海</v-checkbox>
                <v-checkbox v-model="checked2" @change='change' disabled>北京</v-checkbox>
            </div>
        </div>

        <div class='box'>
            <p>多选框组</p>
            <div class='son radio-group'>
                <v-checkbox-group v-model="checked3" @change='change' class='radio-group'>
                    <v-checkbox @change='change' label='上海'>上海</v-checkbox>
                    <v-checkbox @change='change' label='北京'>北京</v-checkbox>
                    <v-checkbox @change='change' label='广州'>广州</v-checkbox>
                    <v-checkbox @change='change' label='深圳'>深圳</v-checkbox>
                    <v-checkbox @change='change' label='耶路撒冷'>耶路撒冷</v-checkbox>
                </v-checkbox-group>
            </div>
        </div>

        <div class='box'>
            <p>indeterminate 状态</p>
            <div class='son radio-group' style='display: block'>
                <v-checkbox @change='handleCheckAllChange' :indeterminate='indeterminate' v-model="checkAll">全选</v-checkbox>
                <div style="margin: 15px 0;"></div>
                <v-checkbox-group v-model="checked4" @change='handleCheckedCitiesChange' class='radio-group'>
                    <v-checkbox v-for='city in data' :key='city' :label='city' @change='change'>{{ city }}</v-checkbox>
                </v-checkbox-group>
            </div>
        </div>

        <div class='box'>
            <p>可选项目数量的限制</p>
            <div class='son radio-group' style='display: block'>
                <v-checkbox-group v-model="checked5" @change='change' :min='min' :max='max' class='radio-group'>
                    <v-checkbox v-for='city in data1' :key='city' :label='city' @change='change'>{{ city }}</v-checkbox>
                </v-checkbox-group>
            </div>
        </div>
        
        <div class='box'>
            <p>按钮样式</p>
            <div class='son radio-group'>
                <v-checkbox-group v-model="checked6" @change='change' class='radio-group'>
                    <v-checkbox-button @change='change' label='上海'>上海</v-checkbox-button>
                    <v-checkbox-button @change='change' label='北京'>北京</v-checkbox-button>
                    <v-checkbox-button @change='change' label='广州'>广州</v-checkbox-button>
                    <v-checkbox-button @change='change' label='深圳'>深圳</v-checkbox-button>
                    <v-checkbox-button @change='change' label='耶路撒冷'>耶路撒冷</v-checkbox-button>
                </v-checkbox-group>
            </div>
            
            <div class='son radio-group'>
                <v-checkbox-group v-model="checked8" @change='change' size='medium' class='radio-group'>
                    <v-checkbox-button @change='change' label='上海'>上海</v-checkbox-button>
                    <v-checkbox-button @change='change' label='北京'>北京</v-checkbox-button>
                    <v-checkbox-button @change='change' label='广州'>广州</v-checkbox-button>
                    <v-checkbox-button @change='change' label='深圳'>深圳</v-checkbox-button>
                    <v-checkbox-button @change='change' label='耶路撒冷'>耶路撒冷</v-checkbox-button>
                </v-checkbox-group>
            </div>

            <div class='son radio-group'>
                <v-checkbox-group v-model="checked9" @change='change' size='small' class='radio-group'>
                    <v-checkbox-button @change='change' label='上海'>上海</v-checkbox-button>
                    <v-checkbox-button @change='change' label='北京'>北京</v-checkbox-button>
                    <v-checkbox-button @change='change' label='广州'>广州</v-checkbox-button>
                    <v-checkbox-button @change='change' label='深圳'>深圳</v-checkbox-button>
                    <v-checkbox-button @change='change' label='耶路撒冷'>耶路撒冷</v-checkbox-button>
                </v-checkbox-group>
            </div>

            <div class='son radio-group'>
                <v-checkbox-group v-model="checked10" disabled @change='change' size='mini' class='radio-group'>
                    <v-checkbox-button @change='change' label='上海'>上海</v-checkbox-button>
                    <v-checkbox-button @change='change' label='北京'>北京</v-checkbox-button>
                    <v-checkbox-button @change='change' label='广州'>广州</v-checkbox-button>
                    <v-checkbox-button @change='change' label='深圳'>深圳</v-checkbox-button>
                    <v-checkbox-button @change='change' label='耶路撒冷'>耶路撒冷</v-checkbox-button>
                </v-checkbox-group>
            </div>
        </div>

        <div class='box'>
            <p>带边框</p>
            <div class='son'>
                <v-checkbox v-model="checked7" @change='change' border>选项一</v-checkbox>
                <v-checkbox v-model="checked7" @change='change' border>选项二</v-checkbox>
            </div>

            <div class='son'>
                <v-checkbox v-model="checked7" @change='change' size='medium' border>选项一</v-checkbox>
                <v-checkbox v-model="checked7" @change='change' size='medium' border>选项二</v-checkbox>
            </div>

            <div class='son'>
                <v-checkbox v-model="checked7" @change='change' size='small' border>选项一</v-checkbox>
                <v-checkbox v-model="checked7" @change='change' size='small' border>选项二</v-checkbox>
            </div>

            <div class='son'>
                <v-checkbox v-model="checked7" @change='change' size='mini' border>选项一</v-checkbox>
                <v-checkbox v-model="checked7" @change='change' size='mini' border>选项二</v-checkbox>
            </div>
        </div>
    </div>
</template>

<script>
import { ref } from 'vue';
export default {
    setup () {
        const min = ref(1);
        const max = ref(3);
        const checked = ref(true);
        const checked1 = ref(false);
        const checked2 = ref(true);
        const checked3 = ref(['上海', '北京']);
        const data = ref(['上海', '北京', '广州', '深圳']);
        const checked4 = ref(['上海', '北京']);
        const checkAll = ref(false);
        const indeterminate = ref(true);
        const data1 = ref(['上海', '北京', '广州', '深圳']);
        const checked5 = ref(['上海', '北京']);
        const checked6 = ref(['上海']);
        const checked8 = ref(['上海']);
        const checked9 = ref(['上海']);
        const checked10 = ref(['上海']);
        const checked7 = ref(true);
        const change = (label) => {
            console.log(label)
        };

        const handleCheckAllChange = (val) => {
            checked4.value = val? data.value : [];
            indeterminate.value = false;
        };
        
        const handleCheckedCitiesChange = (value) => {
            let checkedCount = value.length;
            checkAll.value = checkedCount === data.value.length;
            indeterminate.value = checkedCount > 0 && checkedCount < data.value.length;
        };

        return {
            checked,
            checked1,
            checked2,
            checked3,
            checked4,
            checkAll,
            data,
            data1,
            checked5,
            checked6,
            checked7,
            checked8,
            checked9,
            checked10,
            indeterminate,
            handleCheckAllChange,
            handleCheckedCitiesChange,
            change,
            min,
            max
        }
    }
}
</script>

<style lang='less' scoped>
.checkbox {
    .box {
        margin-bottom: 50px;
        p {
            margin-bottom: 20px;
            font-size: 14px;
        }
        .son {
            width: 200px;
            display: flex;
            margin-bottom: 10px;
            justify-content: space-between;           
        }
        .radio-group {
            margin-bottom: 10px;
            width: 300px;
        }
    }
}
</style>

在这里插入图片描述

posted @ 2022-08-10 16:16  anyRTC  阅读(140)  评论(0编辑  收藏  举报