鸿蒙OS&UniApp制作多选框与单选框组件#三方框架 #Uniapp

发布于:2025-05-16 ⋅ 阅读:(11) ⋅ 点赞:(0)

使用UniApp制作多选框与单选框组件

前言

在移动端应用开发中,表单元素是用户交互的重要组成部分。尤其是多选框(Checkbox)和单选框(Radio),它们几乎存在于每一个需要用户做出选择的场景中。虽然UniApp提供了基础的表单组件,但在实际项目中,我们往往需要根据UI设计稿来定制这些组件的样式和交互效果。

本文将分享如何使用UniApp框架自定义多选框和单选框组件,让它们不仅功能完善,还能适应各种设计风格。通过这篇文章,你将学习到组件封装的思路和技巧,这对提升你的UniApp开发能力会有很大帮助。

为什么要自定义表单组件?

你可能会问,UniApp不是已经提供了<checkbox><radio>组件吗?为什么还要自定义呢?原因主要有以下几点:

  1. 样式限制:原生组件的样式修改有限,难以满足设计师的"奇思妙想"
  2. 跨端一致性:原生组件在不同平台的表现可能不一致
  3. 交互体验:自定义组件可以加入更丰富的交互效果
  4. 功能扩展:可以根据业务需求添加更多功能

多选框组件实现

基本思路

多选框本质上是一个可切换状态的组件,我们可以用一个布尔值来表示选中状态,然后根据状态显示不同的样式。具体实现步骤如下:

  1. 定义组件的props和事件
  2. 设计选中和未选中的样式
  3. 处理点击事件和状态切换
  4. 处理禁用状态

代码实现

首先,创建components/my-checkbox/my-checkbox.vue文件:

<template>
  <view 
    class="my-checkbox" 
    :class="[disabled ? 'my-checkbox-disabled' : '', modelValue ? 'my-checkbox-checked' : '']"
    @click="handleClick"
  >
    <view class="checkbox-box">
      <view v-if="modelValue" class="checkbox-icon">
        <text class="iconfont icon-check"></text>
      </view>
    </view>
    <text v-if="label" class="checkbox-label">{{ label }}</text>
  </view>
</template>

<script>
export default {
  name: 'MyCheckbox',
  props: {
    modelValue: {
      type: Boolean,
      default: false
    },
    label: {
      type: String,
      default: ''
    },
    disabled: {
      type: Boolean,
      default: false
    }
  },
  emits: ['update:modelValue', 'change'],
  methods: {
    handleClick() {
      if (this.disabled) return;
      
      const newValue = !this.modelValue;
      this.$emit('update:modelValue', newValue);
      this.$emit('change', newValue);
    }
  }
}
</script>

<style scoped>
.my-checkbox {
  display: flex;
  align-items: center;
  padding: 6rpx 0;
}

.checkbox-box {
  width: 40rpx;
  height: 40rpx;
  border: 2rpx solid #dcdfe6;
  border-radius: 4rpx;
  display: flex;
  justify-content: center;
  align-items: center;
  box-sizing: border-box;
  transition: border-color 0.3s;
}

.my-checkbox-checked .checkbox-box {
  background-color: #2979ff;
  border-color: #2979ff;
}

.checkbox-icon {
  color: #fff;
  font-size: 28rpx;
  line-height: 1;
}

.checkbox-label {
  margin-left: 10rpx;
  font-size: 28rpx;
  color: #333;
}

.my-checkbox-disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* 引入字体图标(需要自行添加) */
/* @font-face {
  font-family: 'iconfont';
  src: url('~@/static/iconfont.ttf');
} */

.icon-check:before {
  content: '\e645';
}
</style>

注意,这里使用了字体图标作为选中状态的标识。你需要在项目中引入相应的字体文件,或者使用其他图标方案。

如何使用

在页面中使用该组件:

<template>
  <view class="container">
    <my-checkbox v-model="checked1" label="选项1"></my-checkbox>
    <my-checkbox v-model="checked2" label="选项2"></my-checkbox>
    <my-checkbox v-model="checked3" label="禁用选项" disabled></my-checkbox>
  </view>
</template>

<script>
import MyCheckbox from '@/components/my-checkbox/my-checkbox';

export default {
  components: {
    MyCheckbox
  },
  data() {
    return {
      checked1: false,
      checked2: true,
      checked3: false
    }
  }
}
</script>

多选框组(CheckboxGroup)实现

在实际应用中,多选框通常是成组出现的。下面我们来实现一个多选框组组件,用于管理多个选项:

<template>
  <view class="checkbox-group">
    <my-checkbox 
      v-for="(item, index) in options" 
      :key="index"
      :model-value="isChecked(item.value)"
      :label="item.label"
      :disabled="item.disabled"
      @change="(val) => handleChange(item.value, val)"
    ></my-checkbox>
  </view>
</template>

<script>
import MyCheckbox from '../my-checkbox/my-checkbox';

export default {
  name: 'CheckboxGroup',
  components: {
    MyCheckbox
  },
  props: {
    modelValue: {
      type: Array,
      default: () => []
    },
    options: {
      type: Array,
      default: () => []
    }
  },
  emits: ['update:modelValue', 'change'],
  methods: {
    isChecked(value) {
      return this.modelValue.includes(value);
    },
    handleChange(value, checked) {
      let newValue = [...this.modelValue];
      
      if (checked) {
        // 如果选中且不在数组中,则添加
        if (!newValue.includes(value)) {
          newValue.push(value);
        }
      } else {
        // 如果取消选中且在数组中,则移除
        const index = newValue.indexOf(value);
        if (index !== -1) {
          newValue.splice(index, 1);
        }
      }
      
      this.$emit('update:modelValue', newValue);
      this.$emit('change', newValue);
    }
  }
}
</script>

<style scoped>
.checkbox-group {
  display: flex;
  flex-direction: column;
}
.checkbox-group :deep(.my-checkbox) {
  margin-bottom: 20rpx;
}
</style>

使用多选框组:

<template>
  <view class="container">
    <checkbox-group v-model="selectedFruits" :options="fruitOptions"></checkbox-group>
    <view class="result">已选择: {{ selectedFruits.join(', ') }}</view>
  </view>
</template>

<script>
import CheckboxGroup from '@/components/checkbox-group/checkbox-group';

export default {
  components: {
    CheckboxGroup
  },
  data() {
    return {
      selectedFruits: ['apple'],
      fruitOptions: [
        { label: '苹果', value: 'apple' },
        { label: '香蕉', value: 'banana' },
        { label: '橙子', value: 'orange' },
        { label: '葡萄', value: 'grape', disabled: true }
      ]
    }
  }
}
</script>

单选框组件实现

单选框与多选框类似,但它通常是成组出现的,并且一个组内只能选中一个选项。

<template>
  <view class="radio-group">
    <view 
      v-for="(item, index) in options" 
      :key="index"
      class="my-radio"
      :class="[item.disabled ? 'my-radio-disabled' : '', modelValue === item.value ? 'my-radio-checked' : '']"
      @click="handleClick(item)"
    >
      <view class="radio-box">
        <view v-if="modelValue === item.value" class="radio-inner"></view>
      </view>
      <text class="radio-label">{{ item.label }}</text>
    </view>
  </view>
</template>

<script>
export default {
  name: 'RadioGroup',
  props: {
    modelValue: {
      type: [String, Number, Boolean],
      default: ''
    },
    options: {
      type: Array,
      default: () => []
    }
  },
  emits: ['update:modelValue', 'change'],
  methods: {
    handleClick(item) {
      if (item.disabled) return;
      if (this.modelValue !== item.value) {
        this.$emit('update:modelValue', item.value);
        this.$emit('change', item.value);
      }
    }
  }
}
</script>

<style scoped>
.radio-group {
  display: flex;
  flex-direction: column;
}

.my-radio {
  display: flex;
  align-items: center;
  margin-bottom: 20rpx;
}

.radio-box {
  width: 40rpx;
  height: 40rpx;
  border: 2rpx solid #dcdfe6;
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
  box-sizing: border-box;
  transition: all 0.3s;
}

.my-radio-checked .radio-box {
  border-color: #2979ff;
}

.radio-inner {
  width: 20rpx;
  height: 20rpx;
  border-radius: 50%;
  background-color: #2979ff;
  transition: all 0.3s;
}

.radio-label {
  margin-left: 10rpx;
  font-size: 28rpx;
  color: #333;
}

.my-radio-disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
</style>

使用单选框组:

<template>
  <view class="container">
    <radio-group v-model="gender" :options="genderOptions"></radio-group>
    <view class="result">性别: {{ getGenderLabel() }}</view>
  </view>
</template>

<script>
import RadioGroup from '@/components/radio-group/radio-group';

export default {
  components: {
    RadioGroup
  },
  data() {
    return {
      gender: 'male',
      genderOptions: [
        { label: '男', value: 'male' },
        { label: '女', value: 'female' },
        { label: '保密', value: 'secret', disabled: true }
      ]
    }
  },
  methods: {
    getGenderLabel() {
      const option = this.genderOptions.find(item => item.value === this.gender);
      return option ? option.label : '';
    }
  }
}
</script>

实际案例:问卷调查表单

下面是一个结合多选框和单选框的问卷调查表单案例:

<template>
  <view class="survey-form">
    <view class="form-title">满意度调查问卷</view>
    
    <view class="form-item">
      <view class="item-title">1. 您的年龄段:</view>
      <radio-group v-model="survey.age" :options="ageOptions"></radio-group>
    </view>
    
    <view class="form-item">
      <view class="item-title">2. 您对我们的产品满意吗?</view>
      <radio-group v-model="survey.satisfaction" :options="satisfactionOptions"></radio-group>
    </view>
    
    <view class="form-item">
      <view class="item-title">3. 您希望我们改进哪些方面?(可多选)</view>
      <checkbox-group v-model="survey.improvements" :options="improvementOptions"></checkbox-group>
    </view>
    
    <button class="submit-btn" type="primary" @click="submitSurvey">提交问卷</button>
  </view>
</template>

<script>
import RadioGroup from '@/components/radio-group/radio-group';
import CheckboxGroup from '@/components/checkbox-group/checkbox-group';

export default {
  components: {
    RadioGroup,
    CheckboxGroup
  },
  data() {
    return {
      survey: {
        age: '',
        satisfaction: '',
        improvements: []
      },
      ageOptions: [
        { label: '18岁以下', value: 'under18' },
        { label: '18-25岁', value: '18-25' },
        { label: '26-35岁', value: '26-35' },
        { label: '36-45岁', value: '36-45' },
        { label: '45岁以上', value: 'above45' }
      ],
      satisfactionOptions: [
        { label: '非常满意', value: 'very-satisfied' },
        { label: '满意', value: 'satisfied' },
        { label: '一般', value: 'neutral' },
        { label: '不满意', value: 'unsatisfied' },
        { label: '非常不满意', value: 'very-unsatisfied' }
      ],
      improvementOptions: [
        { label: '产品功能', value: 'feature' },
        { label: '用户界面', value: 'ui' },
        { label: '性能速度', value: 'performance' },
        { label: '售后服务', value: 'service' },
        { label: '价格', value: 'price' }
      ]
    }
  },
  methods: {
    submitSurvey() {
      // 表单验证
      if (!this.survey.age) {
        uni.showToast({
          title: '请选择您的年龄段',
          icon: 'none'
        });
        return;
      }
      
      if (!this.survey.satisfaction) {
        uni.showToast({
          title: '请选择产品满意度',
          icon: 'none'
        });
        return;
      }
      
      if (this.survey.improvements.length === 0) {
        uni.showToast({
          title: '请至少选择一项需要改进的方面',
          icon: 'none'
        });
        return;
      }
      
      // 提交数据
      console.log('提交的问卷数据:', this.survey);
      uni.showLoading({
        title: '提交中...'
      });
      
      // 模拟提交
      setTimeout(() => {
        uni.hideLoading();
        uni.showToast({
          title: '提交成功',
          icon: 'success'
        });
      }, 1500);
    }
  }
}
</script>

<style scoped>
.survey-form {
  padding: 30rpx;
}

.form-title {
  font-size: 40rpx;
  font-weight: bold;
  text-align: center;
  margin-bottom: 50rpx;
}

.form-item {
  margin-bottom: 40rpx;
}

.item-title {
  font-size: 32rpx;
  margin-bottom: 20rpx;
}

.submit-btn {
  margin-top: 50rpx;
}
</style>

总结与思考

通过自定义多选框和单选框组件,我们不仅解决了原生组件样式定制的限制,还提升了组件的可复用性和扩展性。这种组件封装的思路,其实可以应用到各种UI组件的开发中。

在实现过程中,有几点值得注意:

  1. 组件通信:使用v-model结合update:modelValue事件实现双向绑定,这是Vue3推荐的做法
  2. 样式隔离:使用scoped样式避免样式污染,对于需要修改子组件样式的情况,可以使用:deep()
  3. 状态管理:清晰地定义组件状态,并通过props传递给子组件
  4. 交互优化:添加过渡效果提升用户体验

希望这篇文章对你在UniApp中自定义表单组件有所帮助。记住,组件开发的核心是复用抽象,好的组件设计可以大大提高开发效率和代码质量。

进阶提示

如果你想进一步完善这些组件,可以考虑:

  1. 添加表单验证功能
  2. 实现不同风格的主题
  3. 支持更多的配置选项,如自定义图标
  4. 添加无障碍访问支持
  5. 优化移动端的触摸体验

最后,别忘了测试你的组件在不同平台的表现,确保它们在各种环境下都能正常工作。


网站公告

今日签到

点亮在社区的每一天
去签到