天天看點

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>
           
web技術支援| 基于vue3實作自己的元件庫第三章:Checkbox元件

繼續閱讀