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>