| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- <template>
- <view class="async-switch"
- :class="{
- 'async-switch--active': value,
- 'async-switch--disabled': disabled,
- 'async-switch--loading': innerLoading
- }"
- @click="handleClick"
- >
- <!-- 开关轨道 -->
- <view class="async-switch__track"></view>
- <!-- 开关按钮 -->
- <view class="async-switch__thumb"></view>
- <!-- 加载状态遮罩 -->
- <view v-if="innerLoading" class="async-switch__loading-mask">
- <u-loading mode="circle"></u-loading>
- </view>
- </view>
- </template>
- <script>
- export default {
- name: 'AsyncSwitch',
- props: {
- // 开关当前状态(双向绑定)
- value: {
- type: Boolean,
- default: false
- },
- // 是否禁用
- disabled: {
- type: Boolean,
- default: false
- },
- // 加载状态(外部可控制)
- loading: {
- type: Boolean,
- default: false
- },
- // 开关激活色
- activeColor: {
- type: String,
- default: '#016BF6'
- },
- // 开关未激活色
- inactiveColor: {
- type: String,
- default: '#ffffff'
- },
- // 异步变更处理函数(必须返回Promise)
- asyncChange: {
- type: Function,
- required: true
- }
- },
- data() {
- return {
- // 内部加载状态(避免外部直接覆盖)
- innerLoading: this.loading
- };
- },
- watch: {
- // 监听外部传入的loading属性变化,同步到内部状态
- loading(newVal) {
- this.innerLoading = newVal;
- }
- },
- methods: {
- /**
- * 处理开关点击事件(Vue2 异步方法写法)
- */
- async handleClick() {
- // 禁用/加载中时不响应点击
- if (this.disabled || this.innerLoading) return;
- // 目标状态(当前状态取反)
- const targetValue = !this.value;
- try {
- // 开启加载状态
- this.innerLoading = true;
- // 执行外部传入的异步操作
- const result = await this.asyncChange(targetValue);
- // 异步操作成功且返回值不为false时,更新开关状态
- if (result !== false) {
- // Vue2 触发自定义事件,实现v-model双向绑定
- this.$emit('input', targetValue); // v-model 底层依赖input事件
- this.$emit('change', targetValue); // 额外的change回调
- }
- } catch (error) {
- // 异步操作失败,触发error事件
- this.$emit('error', error);
- console.error('Switch异步更新失败:', error);
- } finally {
- // 无论成功/失败,关闭加载状态
- this.innerLoading = false;
- }
- }
- }
- };
- </script>
- <style scoped>
- .async-switch {
- position: relative;
- width: 52px;
- height: 32px;
- border-radius: 16px;
- background-color: #ffffff;
- transition: background-color 0.3s ease;
- cursor: pointer;
- box-sizing: border-box;
- border: 1px solid #eee;
- }
- /* 激活状态 */
- .async-switch--active {
- background-color: #016BF6;
- }
- /* 禁用状态 */
- .async-switch--disabled {
- opacity: 0.5;
- cursor: not-allowed;
- }
- /* 加载状态 */
- .async-switch--loading {
- opacity: 0.8;
- cursor: wait;
- }
- .async-switch__track {
- width: 100%;
- height: 100%;
- border-radius: 16px;
- }
- .async-switch__thumb {
- position: absolute;
- top: 1px;
- left: 1px;
- width: 28px;
- height: 28px;
- border-radius: 50%;
- background-color: #fff;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
- transition: left 0.3s ease;
- }
- .async-switch--active .async-switch__thumb {
- left: 22px;
- }
- /* 加载遮罩 */
- .async-switch__loading-mask {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- border-radius: 16px;
- background-color: rgba(0, 0, 0, 0.15);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 10;
- }
- </style>
|