推荐手写基于view+v-model的自定义单选卡片组件,全端一致、可动画、可主题化;放弃uni-radio,用view包裹内容并控制激活态类名,通过emit update:modelvalue实现v-model双向绑定,角标用伪元素+transform实现跨平台兼容,主题色通过css变量和内联样式动态注入。

直接用 uni-radio 很难做到真正统一、美观的卡片式单选,尤其在微信小程序里它会生成不可控的原生节点,样式覆盖成本高、平台差异大。推荐手写一个基于 view + v-model 的轻量级自定义单选卡片组件,全端一致、可动画、可主题化。
用 view 替代 uni-radio 实现可完全控制的卡片结构
放弃官方 uni-radio,改用语义清晰的 view 包裹内容 + 自定义状态标记。关键点是:不依赖任何内置表单组件渲染逻辑,避免小程序平台对 radio 标签的强制样式劫持。
- 每个卡片用
<view class="card-radio"></view>包裹,内部含文字、图标、角标等自由布局区域 - 通过
:class="{ 'card-radio--active': modelValue === item.value }"控制激活态类名 - 点击时只触发
@click="$emit('update:modelValue', item.value)",不操作 DOM 或事件冒泡干扰 - 禁用态直接加
pointer-events: none; opacity: 0.5;,比disabled属性更可靠(部分平台对uni-radio的disabled支持不全)
v-model 双向绑定与父子通信的正确写法(Vue 2 / Vue 3 通用)
必须严格遵循 v-model 协议,否则 H5 和小程序行为会不一致。Vue 2 和 Vue 3 的写法略有不同,但核心逻辑一致:子组件 emit update:modelValue,父组件用 v-model 接收。
- 子组件 props 定义:
modelValue: { type: [String, Number], default: '' } - 子组件 emit:
this.$emit('update:modelValue', newValue)(Vue 2)或emit('update:modelValue', newValue)(Vue 3 setup) - 父组件使用:
<card-radio v-model="selectedValue" :options="list"></card-radio>,不要混用:value+@input - 注意:如果用
v-model:value(如 Vue 3 的v-model:xxx语法),子组件需声明emits: ['update:xxx'],否则 HBuilderX 编译可能静默失败
右上角 √ 角标和选中态动效的跨平台实现
角标不能靠 position: absolute 硬塞,因为 nvue 和某些小程序渲染层对绝对定位支持不稳定。推荐用伪元素 + transform 组合,兼容性更好。
- 角标用
::after伪元素生成,内容为"\2713"(√ 的 Unicode) - 激活态才显示:
.card-radio--active::after { opacity: 1; transform: scale(1); } - 过渡动画写在基类:
.card-radio::after { transition: opacity 0.2s, transform 0.2s; } - H5 和 App 端可加
backface-visibility: hidden防止 iOS 闪屏;微信小程序需额外加z-index: 1确保角标压在卡片内容上方
颜色、圆角、阴影等主题变量如何安全注入
不要在组件内写死 #3498db 这类值,否则换主题要全局搜替换。用 CSS 自定义属性(--card-primary)+ :style 动态传入最稳妥。
- 父组件传入主题对象:
:style-obj="theme",其中theme = { primary: '#3498db', radius: '12rpx', shadow: '0 2rpx 8rpx rgba(0,0,0,0.08)' } - 子组件用
<view :style="styleObj"></view>绑定,CSS 中用var(--card-primary, #9B59B6)回退 - 特别注意:微信小程序不支持 CSS 变量在伪元素中使用,所以角标颜色需单独用
:style注入内联样式,不能靠var() - 圆角单位统一用
rpx,但 nvue 下rpx不生效,需条件编译:/* #ifdef APP-NVUE */ border-radius: 12px; /* #endif */
真正麻烦的不是画出那个 √,而是让它的位置、颜色、显隐逻辑在 H5、微信小程序、App、nvue 四种环境里都刚好对齐、不跳动、不闪烁。每次改一个 transform 值,都得四端真机连跑验证 —— 这才是跨端开发最耗神的地方。










