Vue 开关组件 (ELSwitch) 组件

12 篇文章 2 订阅
3 篇文章 0 订阅
在vue 2.x版本上由于switch 开关不支持异步,我便观看了一下element-ui的switch的源码,发现是作者当时没有考虑到这种情况。
想起vant的源码写的好,于是我看了下vant 官网的switch对异步是支持的,便看了下源码,发现vant-ui 中的 switch 对异步支持是依靠$confirm来实现的,本身代码也不支持异步属性事件。
最后我去element-plus,发现相比于老版本的switch 新添加了几个属性,也移除了部分属性。就将plus版本的对于异步支持的BeforeChange属性事件加到了element-ui的switch上面,就此实现了异步,由于切换比较生硬,我便将plus的loading属性加上去了。

在这里插入图片描述
这样子我们就能通过实际请求或逻辑来判定是否是成功开启或者关闭状态了。

使用组件
<!--
 * Copyright ©
 * #  
 * @author: zw
 * @date: 2022-07-15 
 -->


<template>
  <el-row class="mt-20" :gutter="20">

    <el-col :span="8" :push="8">
      <el-switch v-model="value" :before-change="beforeChange" @change="change" :loading="loading" activeValue="yes" inactiveValue="no" active-color="#13ce66" width="80" active-text="按月付费" inactive-text="按年付费" />
    </el-col>

  </el-row>
</template>

<script>
export default {
  name: 'demo',
  data() {
    return {
      value: 'no',
      loading: false,
    };
  },

  methods: {
    async beforeChange(currentVal) {
      this.loading = true;
      return this.$confirm(`此操作将改变为<span style="color: #${!currentVal ? '409EFF' : 'F56C6C'};"> ${!currentVal} </span>状态,是否继续?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', dangerouslyUseHTMLString: true }).catch(() => {
        this.loading = false;
        return Promise.reject("cancel");
      });
    },
    change(val) {
      setTimeout(() => {
        this.$message.success('switch success' + val);
        this.loading = false;
      }, 1500)
    }
  //  End
}

</script>
组件源码 1 (继承)
<!--
 * Copyright ©
 * #  
 * @author: zw
 * @date: 2022-07-15 
 -->

<script>
import { Switch } from 'element-ui'
const isFunction = (val) => typeof val === 'function'
const isObject = (val) => val !== null && typeof val === 'object'
const isPromise = (val) => isObject(val) && isFunction(val.then) && isFunction(val.catch)
const isBool = (val) => typeof val === 'boolean'
export default {
  extends: Switch,
  props: {
    width: { type: [String, Number], default: 40 },
    beforeChange: Function,
  },
  methods: {
    switchValue() {
      if (this.switchDisabled) return
      const { beforeChange, checked } = this
      if (!beforeChange) {
        return this.handleChange()
      }
      const shouldChange = beforeChange(checked)
      const isExpectType = [isPromise(shouldChange), isBool(shouldChange)].some((i) => i)
      if (!isExpectType) {
        error(this.$options.name, 'beforeChange must return type `Promise<boolean>` or `boolean`')
      }
      if (isPromise(shouldChange)) {
        shouldChange
          .then(() => {
            this.handleChange()
          })
          .catch((e) => {
            if (process.env.NODE_ENV !== 'production') {
              console.warn(this.$options.name, `some error occurred: ${e}`)
            }
          })
      } else if (shouldChange) {
        this.handleChange()
      }
    },
  },
}
</script>

<style lang="scss" scoped></style>

组件源码 2 (完整)
<!--
 * Copyright ©
 * #  
 * @author: zw
 * @date: 2022-07-15 
 -->


<template>
  <div class="el-switch" :class="{ 'is-disabled': switchDisabled, 'is-checked': checked }" role="switch" :aria-checked="checked" :aria-disabled="switchDisabled" @click.prevent="switchValue">
    <input class="el-switch__input" type="checkbox" ref="input" :id="id" :name="name" :true-value="activeValue" :false-value="inactiveValue" :disabled="switchDisabled">

    <span :class="['el-switch__label', 'el-switch__label--left', !checked && 'is-active']" v-if="inactiveIconClass || inactiveText">
      <i :class="[inactiveIconClass]" v-if="inactiveIconClass"></i>
      <span v-if="!inactiveIconClass && inactiveText" :aria-hidden="checked">{{ inactiveText }}</span>
    </span>

    <span class="el-switch__core" ref="core" :style="{ 'width': coreWidth + 'px' }">
      <div class="el-switch__action">
        <i v-if="loading" class="el-icon-loading" />
      </div>
    </span>

    <span :class="['el-switch__label', 'el-switch__label--right', checked && 'is-active']" v-if="activeIconClass || activeText">
      <i :class="[activeIconClass]" v-if="activeIconClass"></i>
      <span v-if="!activeIconClass && activeText" :aria-hidden="!checked">{{ activeText }}</span>
    </span>

  </div>
</template>

<script>
const kebabCase = (str, hyphenateRE = /([^-])([A-Z])/g) => str.replace(hyphenateRE, '$1-$2').replace(hyphenateRE, '$1-$2').toLowerCase();
const isFunction = (val) => typeof val === 'function';
const isObject = (val) => val !== null && typeof val === 'object';
const isPromise = (val) => isObject(val) && isFunction(val.then) && isFunction(val.catch);
const isBool = (val) => typeof val === 'boolean';
class ElementPlusError extends Error {
  constructor(m) {
    super(m);
    this.name = 'ElementPlusError';
  }
}
var error = (scope, m) => {
  throw new ElementPlusError(`[${scope}] ${m}`);
};
function warn(scope, m) {
  console.warn(new ElementPlusError(`[${scope}] ${m}`));
}
export default {
  name: 'ElSwitch',
  data() {
    return {
      coreWidth: this.width
    };
  },
  props: {
    value: { type: [Boolean, String, Number], default: false },
    disabled: { type: Boolean, efault: false },
    width: { type: [String, Number], default: 40 },
    activeIconClass: { type: String, default: '' },
    inactiveIconClass: { type: String, default: '' },
    activeText: String,
    inactiveText: String,
    activeColor: { type: String, default: '' },
    inactiveColor: { type: String, default: '' },
    activeValue: { type: [Boolean, String, Number], default: true },
    inactiveValue: { type: [Boolean, String, Number], default: false },
    name: { type: String, default: '' },
    validateEvent: { type: Boolean, default: true },
    id: String,
    loading: { type: Boolean, default: false },
    beforeChange: Function
  },
  created() {
    if (!~[this.activeValue, this.inactiveValue].indexOf(this.value)) {
      this.$emit('input', this.inactiveValue);
    }
  },
  mounted() {
    this.checkMigratingConfig();
    this.coreWidth = this.width || 40;
    if (this.activeColor || this.inactiveColor) {
      this.setBackgroundColor();
    }
    this.$refs.input.checked = this.checked;
  },
  methods: {
    handleChange() {
      const val = this.checked ? this.inactiveValue : this.activeValue;
      this.$emit('input', val);
      this.$emit('change', val);
      this.$nextTick(() => {
        // set input's checked property
        // in case parent refuses to change component's value
        if (this.$refs.input) {
          this.$refs.input.checked = this.checked;
        }
      });
    },
    setBackgroundColor() {
      let newColor = this.checked ? this.activeColor : this.inactiveColor;
      this.$refs.core.style.borderColor = newColor;
      this.$refs.core.style.backgroundColor = newColor;
    },
    switchValue() {
      if (this.switchDisabled) return;
      const { beforeChange, checked } = this;
      if (!beforeChange) {
        return this.handleChange();
      }
      const shouldChange = beforeChange(checked);
      const isExpectType = [isPromise(shouldChange), isBool(shouldChange)].some((i) => i);
      if (!isExpectType) {
        error(this.$options.name, "beforeChange must return type `Promise<boolean>` or `boolean`");
      }
      if (isPromise(shouldChange)) {
        shouldChange.then(() => {
          this.handleChange();
        }).catch((e) => {
          if (process.env.NODE_ENV !== "production") {
            warn(this.$options.name, `some error occurred: ${e}`);
          }
        });
      } else if (shouldChange) {
        this.handleChange();
      }
    },
    dispatch(componentName, eventName, params) {
      var parent = this.$parent || this.$root;
      var name = parent.$options.componentName;

      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;

        if (parent) {
          name = parent.$options.componentName;
        }
      }
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    },
    checkMigratingConfig() {
      if (process.env.NODE_ENV === 'production') return;
      if (!this.$vnode) return;
      const { props = {}, events = {} } = this.getMigratingConfig();
      const { data, componentOptions } = this.$vnode;
      const definedProps = data.attrs || {};
      const definedEvents = componentOptions.listeners || {};

      for (let propName in definedProps) {
        propName = kebabCase(propName); // compatible with camel case
        if (props[propName]) {
          console.warn(`[Element Migrating][${this.$options.name}][Attribute]: ${props[propName]}`);
        }
      }

      for (let eventName in definedEvents) {
        eventName = kebabCase(eventName); // compatible with camel case
        if (events[eventName]) {
          console.warn(`[Element Migrating][${this.$options.name}][Event]: ${events[eventName]}`);
        }
      }
    },
    getMigratingConfig() {
      return {
        props: {
          'on-color': 'on-color is renamed to active-color.',
          'off-color': 'off-color is renamed to inactive-color.',
          'on-text': 'on-text is renamed to active-text.',
          'off-text': 'off-text is renamed to inactive-text.',
          'on-value': 'on-value is renamed to active-value.',
          'off-value': 'off-value is renamed to inactive-value.',
          'on-icon-class': 'on-icon-class is renamed to active-icon-class.',
          'off-icon-class': 'off-icon-class is renamed to inactive-icon-class.'
        }
      };
    },
    focus() {
      this.$refs['input'].focus();
    }
  },
  computed: {
    checked() {
      return this.value === this.activeValue;
    },
    switchDisabled() {
      return this.disabled || this.loading || (this.elForm || {}).disabled;
    }
  },
  watch: {
    checked() {
      this.$refs.input.checked = this.checked;
      if (this.activeColor || this.inactiveColor) {
        this.setBackgroundColor();
      }
      if (this.validateEvent) {
        this.dispatch('ElFormItem', 'el.form.change', [this.value]);
      }
    }
  },
  //  End
}

</script>

<style lang='css' scoped>
.el-switch {
  display: -webkit-inline-box;
  display: -ms-inline-flexbox;
  display: inline-flex;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  position: relative;
  font-size: 14px;
  line-height: 20px;
  height: 20px;
  vertical-align: middle;
}

.el-switch.is-disabled .el-switch__core,
.el-switch.is-disabled .el-switch__label {
  cursor: not-allowed;
}

.el-switch__core,
.el-switch__label {
  display: inline-block;
  cursor: pointer;
  vertical-align: middle;
}

.el-switch__label {
  -webkit-transition: 0.2s;
  transition: 0.2s;
  height: 20px;
  font-size: 14px;
  font-weight: 500;
  color: #303133;
}

.el-switch__label.is-active {
  color: #409eff;
}

.el-switch__label--left {
  margin-right: 10px;
}

.el-switch__label--right {
  margin-left: 10px;
}

.el-switch__label * {
  line-height: 1;
  font-size: 14px;
  display: inline-block;
}

.el-switch__input {
  position: absolute;
  width: 0;
  height: 0;
  opacity: 0;
  margin: 0;
}

.el-switch__core {
  margin: 0;
  position: relative;
  width: 40px;
  height: 20px;
  border: 1px solid #dcdfe6;
  outline: 0;
  border-radius: 10px;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  background: #dcdfe6;
  -webkit-transition: border-color 0.3s, background-color 0.3s;
  transition: border-color 0.3s, background-color 0.3s;
}
.el-switch__core .el-switch__action {
  position: absolute;
  top: 1px;
  left: 1px;
  z-index: 9999;
  border-radius: 100%;
  -webkit-transition: all 0.3s;
  transition: all 0.3s;
  width: 16px;
  height: 16px;
  background-color: #fff;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  justify-content: center;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  color: #303133;
}
.el-switch__core:after {
  content: "";
  position: absolute;
  top: 1px;
  left: 1px;
  z-index: 9998;
  border-radius: 100%;
  -webkit-transition: all 0.3s;
  transition: all 0.3s;
  width: 16px;
  height: 16px;
  background-color: #fff;
}

.el-switch.is-checked .el-switch__core {
  border-color: #409eff;
  background-color: #409eff;
}
.el-switch.is-checked .el-switch__core .el-switch__action {
  left: 100%;
  margin-left: -17px;
  color: #409eff;
}
.el-switch.is-checked .el-switch__core::after {
  left: 100%;
  margin-left: -17px;
}

.el-switch.is-disabled {
  opacity: 0.6;
}

.el-switch--wide .el-switch__label.el-switch__label--left span {
  left: 10px;
}

.el-switch--wide .el-switch__label.el-switch__label--right span {
  right: 10px;
}

.el-switch .label-fade-enter,
.el-switch .label-fade-leave-active {
  opacity: 0;
}
</style>

main.js 中需要覆盖掉默认注册的ELSwitch
import ElementUI from "element-ui"
import "element-ui/lib/theme-chalk/index.css";
Vue.use(ElementUI);
import ElSwitch from '@/components/switch.vue';
Vue.component('el-switch', ElSwitch); // 这个步骤同等于对象重新赋值的操作
严谨转载。
此处不生产代码,只做代码的搬运工。
Vue.js是一个流行的JavaScript框架,用于构建用户界面。在Vue中创建跷跷板开关按钮可以通过使用组件和条件渲染来实现。 首先,创建一个Vue组件来表示跷跷板开关按钮。在组件中,你可以使用`data`选项来定义按钮的状态。例如,你可以使用一个布尔值来表示按钮开关状态。 ```html <template> <button @click="toggleSwitch" :class="{ 'on': isOn, 'off': !isOn }"> <span>{{ isOn ? 'ON' : 'OFF' }}</span> </button> </template> <script> export default { data() { return { isOn: false, // 初始化按钮状态为关闭 }; }, methods: { toggleSwitch() { this.isOn = !this.isOn; // 切换按钮状态 }, }, }; </script> <style> button { width: 100px; height: 50px; } .on { background-color: green; } .off { background-color: red; } </style> ``` 在上面的示例中,我们创建了一个按钮组件,其中使用了Vue的条件渲染和事件绑定。通过点击按钮,`toggleSwitch`方法会被调用,从而切换按钮的状态。 在组件的模板中,我们使用了动态类绑定`:class`来根据按钮状态来设置不同的样式。当按钮状态为开启时,添加`on`类,当按钮状态为关闭时,添加`off`类。 这样,你就可以在Vue应用中使用这个跷跷板开关按钮组件了。只需在父组件中引入该组件,并将其添加到模板中的适当位置。 ```html <template> <div> <h1>跷跷板开关按钮示例</h1> <switch-button></switch-button> </div> </template> <script> import SwitchButton from './SwitchButton.vue'; export default { components: { SwitchButton, }, }; </script> ``` 这样,你就可以看到一个可以切换状态的跷跷板开关按钮了!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值