uniapp下单选框实现
lsjweiyi 人气:0uniapp官方虽然提供了uni-data-checkbox
,含括了单选和多选框功能。但是它功能实在不能满足需求:
- 单选框不支持再次点击取消
- 无法与父组件的数据源进行联动,无法实现如多规格选择的那种联动
- 源码每次点击都是对数据源进行拷贝,然后再进行json解析等操作,看着就很不靠谱,数据量大必然有性能问题。
其实我放弃uni-data-checkbox
,选择自己实现也是因为商品规格展示是比较复杂的,不自己实现的话无法达到目的:
看图中,三组规格选项是要相互联动的,选择了其中一个后,就得判断其余的是否可选。然后我认为也可以将已选中的取消。所以得自己实现,好根据业务定制。
代码如下:
<template> <!-- uniapp内置的单选组件,见https://uniapp.dcloud.io/component/radio.html --> <radio-group class="checklist-group" @change="change"> <label class="checklist-box is--tag" v-for="item in radioData.option" :class="[radioData.selected === item.id ? 'is-checked' : '', item.disable ? 'is-disable' : '']"> <radio class="hidden" :disabled="item.disable" :value="String(item.id)" :checked="radioData.selected === item.id" /> <view class="checklist-content"> <text class="checklist-text">{{ item.text }}</text> </view> </label> </radio-group> </template> <script setup lang="ts"> const props = defineProps({ // 该id设计的目的是为了应对数组,记录数组的下标,这样父类就不需要遍历查找了。当然也可以根据业务用于其他方面,不需要就不用即可。 id: { type: [Number, String], }, /*数据源,它的数据结构应该:{selected:,option:[{id:,disable:,text:,}...]} 其中selected 的值应取自option的id。 */ radioData: { type: Object, required: true, }, }); // 点击后回调父类的change方法 const emit = defineEmits(["change"]); // 点击后触发 function change(e: any) { // 参数:tag的id;props.id emit("change", e.detail.value, props.id); } </script> <style lang="scss"> $checked-color: #2979ff; $border-color: #dcdfe6; $disable: 0.4; @mixin flex { /* #ifndef APP-NVUE */ display: flex; /* #endif */ } .checklist-group { @include flex; flex-direction: row; flex-wrap: wrap; .checklist-box { @include flex; flex-direction: row; align-items: center; position: relative; margin: 5px 0; margin-right: 25px; .hidden { position: absolute; opacity: 0; } // 文字样式 .checklist-content { @include flex; flex: 1; flex-direction: row; align-items: center; justify-content: space-between; .checklist-text { font-size: 14px; color: #666; margin-left: 5px; line-height: 14px; } } // 单选样式 .radio__inner { @include flex; /* #ifndef APP-NVUE */ flex-shrink: 0; box-sizing: border-box; /* #endif */ justify-content: center; align-items: center; position: relative; width: 16px; height: 16px; border: 1px solid $border-color; border-radius: 16px; background-color: #fff; z-index: 1; .radio__inner-icon { width: 8px; height: 8px; border-radius: 10px; opacity: 0; } } // 标签样式 &.is--tag { margin-right: 10px; padding: 5px 10px; border: 1px $border-color solid; border-radius: 3px; background-color: #f5f5f5; .checklist-text { margin: 0; color: #666; } // 禁用 &.is-disable { /* #ifdef H5 */ cursor: not-allowed; /* #endif */ opacity: $disable; } &.is-checked { background-color: $checked-color; border-color: $checked-color; .checklist-text { color: #fff; } } } } } </style>
其实代码本身内容很少,是样式的代码多,我样式是直接照抄uni-data-checkbox
的。
传入的数据结构应该是:
interface radio{ selected: number; option: { id: number; text: string; disable: boolean; }[]; }
selected
和id
可以是别的类型,但selected
是取值于id
这里得注意,数据源必须是响应式的,考虑这里肯定是个对象,那么就是要用reactive
去包围数据,使其具有响应性,否则页面不会更新,例如下面:
const radioData= reactive(radio);
PS1:由于vue3规范建议:子组件不修改父组件的数据源,否则会导致数据的变化难以理解。所以change方法中没有做任何修改数据的动作。比如将selected直接修改也是完全可以的,但是我这里还是交由父组件去决定如何修改。
PS2:咋一看change方法仅传递了当前选择的选项,并没有告知之前的选项是什么,如果要对比前后的时候不是没有办法?其实selected就是存储的之前的选项,在修改它之前用它作比较即可。
PS3:由于radio本身是不支持选中之后再取消的,我们这里采用将selected
赋值为一个不存在的id,这样就会取消选择了。但是会报错:
uni-shared.es.js:470 Uncaught TypeError: Cannot destructure property 'id' of 'el' as it is null. at normalizeTarget (uni-shared.es.js:470:13) at createNativeEvent (uni-h5.es.js:1260:13) at $nne (uni-h5.es.js:1234:15) at HTMLElement.invoker (vue.runtime.esm.js:9397:19)
但是不影响功能哈。
PS4:目前仅在H5下测试功能时正常的。
总结
加载全部内容