项目实训 - Scene-UI - 编写radio

单选框

单选按钮允许你选择单一的值来提交表单。编写单选框时要考虑的因素有:

  • 单选框的表单控件属性,也就是基础的name、value、label属性
  • 单选框的尺寸
  • 单选框是否被禁用
  • 单选框的大小

同时保留了单选框的modelValue改变时的change事件,参数是当前modelValue。radio.ts的代码如下:

import { ComponentConstants } from '../../core/constants'; 
import {isString ,isNumber, isBoolean} from "../../core/lib/type-assert"

export const radioProps = {
    modelValue:{//单选框绑定的值
        type:[String,Number,Boolean],
        required: true,
    },
    name:{//随表单一起提交的原生的name属性
        type: String,
        required: true,
    },
    label:{//单选框的文本
        type: String,
        required:true,
    },
    disabled:{//是否禁用单选框
        type: Boolean,
        default: false
    },
    size:{//Radio 的尺寸
        type: String,
        default: 'default',
        validator(value: string) {
            return ComponentConstants.StandardSizeArr.includes(value)
        }
    },
}

export const radioEmits = {
    // onchange函数的验证函数
    change: (val: string | number | boolean) => isString(val) || isNumber(val) || isBoolean(val),
    'update:modelValue': (val: string | number | boolean) => isString(val) || isNumber(val) || isBoolean(val),
}

因为无法直接修改input元素的样式,也就是那个选择按钮的样式,因此准备自己实现一个选择的样式,以size="default"为例,使用opacity隐藏原生radio,再利用绝对布局将其移动到自己画的圆圈的位置。

/* size=default */
.scene-radio-default input[type="radio"]{
  position: absolute;
  width: 16px;
  height: 16px;
  margin: 0;
  opacity: 0;
  top: 6px;
  left: -8px;
  z-index: var(--scene-index-heighter);
}

利用:before伪元素构造一个圆圈:

.scene-radio-default label:before{
  content: '';
  display: inline-block;
  position: relative;
  width: 16px;
  height: 16px;
  border-radius: 18px;
  border: 1px solid var(--scene-border-color-dark);
  background: white;
  top: 5px;
  left: -9px;
  cursor: pointer;
  -webkit-transition: all 250ms ease;
  transition: all 250ms ease;
}

Radio选中时的特殊样式:

/* 选中时圆圈样式 */
.scene-radio input[type="radio"]:checked + label:before{
  background: var(--scene-theme-color-main);
  box-shadow: inset 0 0 0 4px #f4f4f4;
}
/* 获取焦点时圆圈样式 */
.scene-radio input[type="radio"]:focus + label:before{
  border-color: var(--scene-theme-color-main);
}
/* 选中时文本样式 */
.scene-radio input[type="radio"]:checked + label{
  color: var(--scene-theme-color-main);
}

完整代码:

<template>
  <div 
    class="scene-radio" 
    :class="[
      `scene-radio-${size}`,
    ]" 
  >
    <input
      type="radio" 
      :name="name" 
      :value="modelValue" 
      :disabled="disabled"
      @click="handleClick"
      @change="handleChange"
      :checked="label === modelValue"
    />
    <label>
      {{label}}
    </label>
  </div>
</template>

<script lang='ts'>
import {defineComponent} from 'vue'
import {radioProps, radioEmits} from './radio'
import '../../styles/global.css'

export default defineComponent({
  name:'scene-radio',
  props:radioProps,
  emits:radioEmits,
  setup(props,{emit,attrs,slots,expose}){
    // 组件原生的change事件回调函数
    const handleChange = (evt: MouseEvent)=>{
        if(props.modelValue === undefined)
          emit('change','undefined')
        else
          emit('change',props.modelValue)
    }

    const handleClick = ()=>{
      if(props.label !== undefined)
        emit('update:modelValue',props.label)
    }

    return {
      handleChange,
      handleClick
    }
  }
})
</script>

<style scoped>
.scene-radio{
  display: inline-flex;
  position: relative;
}
/* size=default */
.scene-radio-default input[type="radio"]{
  position: absolute;
  width: 16px;
  height: 16px;
  margin: 0;
  opacity: 0;
  top: 6px;
  left: -8px;
  z-index: var(--scene-index-heighter);
}
.scene-radio-default label:before{
  content: '';
  display: inline-block;
  position: relative;
  width: 16px;
  height: 16px;
  border-radius: 18px;
  border: 1px solid var(--scene-border-color-dark);
  background: white;
  top: 5px;
  left: -9px;
  cursor: pointer;
  -webkit-transition: all 250ms ease;
  transition: all 250ms ease;
}
.scene-radio-default label{
  color: var(--scene-font-color-dark);
  font-size: var(--scene-font-size);
  font-family: var(--scene-font-family);
  letter-spacing: var(--scene-letter-spacing);
}
/* size=small */
.scene-radio-small input[type="radio"]{
  width: 14px;
  height: 14px;
  position: absolute;
  margin: 0;
  opacity: 0;
  top: 5px;
  left: -7px;
  z-index: var(--scene-index-heighter);
}
.scene-radio-small label:before{
  content: '';
  display: inline-block;
  position: relative;
  width: 14px;
  height: 14px;
  border-radius: 16px;
  border: 1px solid var(--scene-border-color-dark);
  background: white;
  top: 4px;
  left: -8px;
  cursor: pointer;
  -webkit-transition: all 250ms ease;
  transition: all 250ms ease;
}
.scene-radio-small label{
  color: var(--scene-font-color-dark);
  font-size: var(--scene-font-size-small);
  font-family: var(--scene-font-family);
  letter-spacing: var(--scene-letter-spacing);
}
/* size=large */
.scene-radio-large input[type="radio"]{
  position: absolute;
  width: 20px;
  height: 20px;
  margin: 0;
  opacity: 0;
  top: 7px;
  left: -8px;
  z-index: var(--scene-index-heighter);
}
.scene-radio-large label:before{
  content: '';
  display: inline-block;
  position: relative;
  width: 20px;
  height: 20px;
  border-radius: 22px;
  border: 1px solid var(--scene-border-color-dark);
  background: white;
  top: 6px;
  left: -9px;
  cursor: pointer;
  -webkit-transition: all 250ms ease;
  transition: all 250ms ease;
}
.scene-radio-large label{
  color: var(--scene-font-color-dark);
  font-size: var(--scene-font-size-large);
  font-family: var(--scene-font-family);
  letter-spacing: var(--scene-letter-spacing);
}
/* 选中时圆圈样式 */
.scene-radio input[type="radio"]:checked + label:before{
  background: var(--scene-theme-color-main);
  box-shadow: inset 0 0 0 4px #f4f4f4;
}
/* 获取焦点时圆圈样式 */
.scene-radio input[type="radio"]:focus + label:before{
  border-color: var(--scene-theme-color-main);
}
/* 选中时文本样式 */
.scene-radio input[type="radio"]:checked + label{
  color: var(--scene-theme-color-main);
}

.scene-radio input[type="radio"]:disabled + label:before {
  box-shadow: inset 0 0 0 4px #f4f4f4;
  background: var(--scene-color-disabled)
}

.scene-radio-border-true{
    border: var(--scene-theme-color-main) 1px solid;
}
</style>
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sunburst7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值