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

news2025/5/16 10:55:56

使用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. 优化移动端的触摸体验

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2376792.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

康谋分享 | 自动驾驶仿真进入“标准时代”:aiSim全面对接ASAM OpenX

目录 一、OpenDRIVE&#xff1a;兼容多版本地图标准 &#xff08;1&#xff09;Atlas 工作流 &#xff08;2&#xff09;UE Plugin 工作流 二、OpenSCENARIO&#xff1a;标准化动态行为建模 三、OpenCRG&#xff1a;还原毫米级路面细节 四、OpenMATERIAL&#xff1a;更真…

GPUGeek云平台实战:DeepSeek-R1-70B大语言模型一站式部署

随着人工智能技术的迅猛发展&#xff0c;特别是在自然语言处理领域&#xff0c;大型语言模型如DeepSeek-R1-70B的出现&#xff0c;推动了各行各业的变革。为了应对这些庞大模型的计算需求&#xff0c;云计算平台的普及成为了关键&#xff0c;特别是基于GPU加速的云平台&#xf…

【C语言】初阶数据结构相关习题(二)

&#x1f386;个人主页&#xff1a;夜晚中的人海 今日语录&#xff1a;知识是从刻苦劳动中得来的&#xff0c;任何成就都是刻苦劳动的结果。——宋庆龄 文章目录 &#x1f384;一、链表内指定区间翻转&#x1f389;二、从链表中删去总和值为零的节点&#x1f680;三、链表求和&…

嵌入式学习--江科大51单片机day7

我们在听课的过程中&#xff0c;可能对老师讲的有疑问&#xff0c;或者有些自己的理解&#xff0c;我们可以去问豆包&#xff0c;包括在写博客的时候我也是&#xff0c;不断去问豆包保证思考的正确性。&#xff08;有人感觉豆包很low啊&#xff0c;其实这些基础性的东西豆包一般…

Element Plus 取消el-form-item点击触发组件,改为原生表单控件

文章目录 问题&#xff1a;方法一&#xff1a;使用全局样式覆盖&#xff08;推荐&#xff09;方法二&#xff1a;自定义指令&#xff08;更灵活&#xff09;方法三&#xff1a;封装高阶组件方法四&#xff1a;运行时DOM修改&#xff08;不推荐&#xff09; 问题&#xff1a; 描…

Git-学习笔记(粗略版)

前言 很多人都听过git&#xff0c;github这些名词,但是它们是什么&#xff0c;怎么使用&#xff1f;git和github是一个东西吗&#xff1f;本文将详细解答这些问题&#xff0c;彻底弄懂git。 1.Git是啥❓ 有一天&#xff0c;我们的插画师小王接到一个绘画订单&#xff0c;但奈…

专项智能练习(定义判断)

1. 单选题 热传导是介质内无宏观运动时的传热现象&#xff0c;其在固体、液体和气体中均可发生。但严格而言&#xff0c;只有在固体中才是纯粹的热传导&#xff0c;在流体&#xff08;泛指液体和气体&#xff09;中又是另外一种情况&#xff0c;流体即使处于静止状态&#xff…

【iOS安全】Dopamine越狱 iPhone X iOS 16.6 (20G75) | 解决Jailbreak failed with error

Dopamine越狱 iPhone X iOS 16.6 (20G75) Dopamine兼容设备 参考&#xff1a;https://www.bilibili.com/opus/977469285985157129 A9 - A11&#xff08;iPhone6s&#xff0d;X&#xff09;&#xff1a;iOS15.0-16.6.1 A12-A14&#xff08;iPhoneXR&#xff0d;12PM&#xf…

对心理幸福感含义的探索 | 幸福就是一切吗?

注&#xff1a;机翻&#xff0c;未校。 Happiness Is Everything, or Is It? Explorations on the Meaning of Psychological Well-Being 幸福就是一切吗&#xff1f;对心理幸福感含义的探索 Journal of Personality and Social Psychology 1989, Vol. 57, No. 6,1069-1081 …

【高频面试题】LRU缓存

文章目录 1 相关前置知识&#xff08;OS&#xff09;2 面试题 16.25. LRU 缓存2.1 题面2.2 示例2.3 解法1 &#xff08;双端队列哈希表&#xff09;思路 2.4 解法2思路 3 参考 1 相关前置知识&#xff08;OS&#xff09; 为什么需要页面置换算法&#xff1a;当进程运行时&…

讯联云库项目开发日志(二)AOP参数拦截

目录 利用AOP实现参数拦截: 一、​​HTTP请求进入Controller​&#xff08;发送邮件验证码&#xff09; 二、AOP切面触发 1. 切面拦截&#xff08;GlobalOperactionAspect.class&#xff09; method.getAnnotation()​​ null interceptor 判断​​ 2.参数校验注解 3. 参…

龙虎榜——20250515

上证指数缩量收阴线&#xff0c;个股跌多涨少&#xff0c;上涨波段4月9日以来已有24个交易日&#xff0c;时间周期上处于上涨末端&#xff0c;注意风险。 深证指数缩量收阴线&#xff0c;日线上涨结束的概率在增大&#xff0c;注意风险。 2025年5月15日龙虎榜行业方向分析 一…

卡洛诗,将高端西餐的冗余价值转化为普惠体验

西餐市场正经历一场结构性变革&#xff0c;一二线城市的高端西餐陷入内卷&#xff0c;而下沉市场却因品质与价格断层陷入选择困境——消费者既不愿为高价西餐的面子溢价买单&#xff0c;又难以忍受快餐式西餐的粗糙体验。这一矛盾催生了万亿级的市场真空地带&#xff0c;萨莉亚…

Flutter在键盘的上方加一个完成按钮

有些情况下&#xff0c;输入框在输入键盘弹出后&#xff0c; 需要在键盘的上方显示一个toolbar &#xff0c; 然后 toolbar 上面一个完成按钮&#xff0c;点完成按钮把键盘关闭。 如图&#xff1a; 直接上代码&#xff0c;这样写的好处是&#xff0c;把 TextField 给封装了&…

SQL注入---05--跨站注入

1 权限说明 select * from mysql.user; 这里的Y表示我前面的命令权限为root&#xff0c;n表示不支持root权限 导致结果&#xff1a; 如果为root的话&#xff0c;我就可操作这些命令并且可以进行跨数据库攻击&#xff0c;但是如果不是高权限root就无法执行这些操作 2 root权限…

【免费分享】虚拟机VM(适用于 Windows)17.6.3

—————【下 载 地 址】——————— 【​本章下载一】&#xff1a;https://drive.uc.cn/s/7c4da5cd2af64 【​本章下载二】&#xff1a;https://pan.xunlei.com/s/VOQDkRRKc5OUVTauZezaiDEHA1?pwdpybg# 【百款黑科技】&#xff1a;https://ucnygalh6wle.feishu.cn/wiki/…

2025 后端自学UNIAPP【项目实战:旅游项目】5、个人中心页面:微信登录,同意授权,获取用户信息

一、框架以及准备工作 1、前端项目文件结构展示 2、后端项目文件结构展示 3、登录微信公众平台&#xff0c;注册一个个人的程序&#xff0c;获取大appid&#xff08;前端后端都需要&#xff09;和密钥&#xff08;后端需要&#xff09; 微信公众平台微信公众平台&…

蓝桥杯算法题 -蛇形矩阵(方向向量)

&#x1f381;个人主页&#xff1a;工藤新一 &#x1f50d;系列专栏&#xff1a;C面向对象&#xff08;类和对象篇&#xff09; &#x1f31f;心中的天空之城&#xff0c;终会照亮我前方的路 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 文章目录 P…

配置VScodePython环境Python was not found;

Python was not found; run without arguments to install from the Microsoft Store, or disable this shortcut from Settings > Manage App Execution Aliases. 候试试重启电脑。 在卸载重装python后会出现难以解决的局面&#xff0c;系统变量&#xff0c;命令行&#…

ollama 重命名模型

ollama 重命名模型 ollama list# 查看列表 ollama list # 生成原模型的Modelfile文件 ollama show --modelfile qwen3:32b > Modelfile # 从Modelfile文件创建新的模型 ollama create qwen3 -f Modelfile # 删除原模型 ollama rm qwen3:32b