Radio
Radio.Group是面向 Formily 单值字段的 Vant 单选框组封装,延续dataSource + option slot + readPretty这套现有组件习惯。
使用建议
表单场景优先使用 Radio.Group,而不是单独的 Radio。当前 Radio 对外仍然直接暴露原始 VanRadio,真正和 Formily 字段值、dataSource、阅读态联动的是 Radio.Group。
基础使用
vue
<script setup lang="ts">
import { createForm } from '@formily/core'
import { Form, FormButtonGroup, FormItem, Radio, Submit } from '@silver-formily/vant'
import { Field } from '@silver-formily/vue'
import { showDemoResult } from '../shared'
const form = createForm({
values: {
deliveryType: 'express',
},
})
async function handleSubmit(values: typeof form.values) {
await showDemoResult(values)
}
</script>
<template>
<Form :form="form">
<Field
name="deliveryType"
title="配送方式"
:decorator="[FormItem, { labelAlign: 'top' }]"
:component="[Radio.Group]"
:data-source="[
{ label: '快递寄送', value: 'express' },
{ label: '门店自提', value: 'pickup' },
{ label: '同城闪送', value: 'instant' },
]"
/>
<FormButtonGroup>
<Submit :on-submit="handleSubmit">
查看结果
</Submit>
</FormButtonGroup>
</Form>
</template>排列方向与样式
其中纵向示例改成了 CellGroup + Cell + Radio 组合,视觉上会比纯纵向堆叠更适合移动端列表场景。
vue
<script setup lang="ts">
import { createForm } from '@formily/core'
import { Radio } from '@silver-formily/vant'
import { Field, FormProvider } from '@silver-formily/vue'
import { Cell, CellGroup } from 'vant'
const form = createForm({
values: {
layout: 'grid',
scene: 'commute',
},
})
const layoutOptions = [
{ label: '宫格', value: 'grid' },
{ label: '列表', value: 'list' },
{ label: '瀑布流', value: 'waterfall' },
]
const sceneOptions = [
{
label: '通勤打卡',
value: 'commute',
description: '适合固定地点、固定时间段的移动端签到场景。',
},
{
label: '出差报销',
value: 'travel',
description: '适合行程较长、需要补充票据与审批说明的流程。',
},
{
label: '外勤签到',
value: 'onsite',
description: '适合需要定位、拍照或拜访记录的现场任务。',
},
]
function selectScene(value: string) {
form.setValues({
scene: value,
})
}
</script>
<template>
<FormProvider :form="form">
<div class="demo-panel">
<div class="direction-section">
<div class="direction-section__title">
横向排列
</div>
<Field
name="layout"
:component="[Radio.Group, { direction: 'horizontal', shape: 'dot' }]"
:data-source="layoutOptions"
/>
</div>
<div class="direction-section">
<div class="direction-section__title">
纵向排列
</div>
<div class="direction-section__tip">
纵向场景改成单元格组合,点击整行即可切换,比简单堆叠更适合移动端展示。
</div>
<Field name="scene" :component="[Radio.Group]">
<CellGroup inset>
<Cell
v-for="option in sceneOptions"
:key="option.value"
center
clickable
:title="option.label"
:label="option.description"
@click="selectScene(option.value)"
>
<template #right-icon>
<Radio :name="option.value" />
</template>
</Cell>
</CellGroup>
</Field>
</div>
</div>
</FormProvider>
</template>
<style scoped>
.direction-section + .direction-section {
margin-top: 20px;
}
.direction-section__title {
margin-bottom: 12px;
color: var(--van-text-color);
font-size: 14px;
font-weight: 600;
line-height: 1.4;
}
.direction-section__tip {
margin-bottom: 12px;
color: var(--van-text-color-2);
font-size: 12px;
line-height: 1.6;
}
</style>搭配单元格组件使用
vue
<script setup lang="ts">
import { createForm } from '@formily/core'
import { Form, FormButtonGroup, Radio, Submit } from '@silver-formily/vant'
import { Field } from '@silver-formily/vue'
import { Cell, CellGroup, Tag } from 'vant'
import { showDemoResult } from '../shared'
const form = createForm({
values: {
plan: 'pro',
},
})
const planOptions = [
{
label: '标准版',
value: 'basic',
description: '适合轻量表单,快速接入移动端页面。',
tag: '推荐新项目',
},
{
label: '专业版',
value: 'pro',
description: '适合多步骤提交流程和联动字段较多的场景。',
tag: '最常用',
},
]
function selectPlan(value: string) {
form.setValues({
plan: value,
})
}
async function handleSubmit(values: typeof form.values) {
await showDemoResult(values)
}
</script>
<template>
<Form :form="form">
<Field name="plan" :component="[Radio.Group]">
<CellGroup inset>
<Cell
v-for="option in planOptions"
:key="option.value"
center
clickable
:title="option.label"
:label="option.description"
@click="selectPlan(option.value)"
>
<template #title>
<div class="plan-title">
<span>{{ option.label }}</span>
<Tag plain type="primary">
{{ option.tag }}
</Tag>
</div>
</template>
<template #right-icon>
<Radio :name="option.value" />
</template>
</Cell>
</CellGroup>
</Field>
<FormButtonGroup>
<Submit :on-submit="handleSubmit">
查看结果
</Submit>
</FormButtonGroup>
</Form>
</template>
<style scoped>
.plan-title {
display: inline-flex;
align-items: center;
gap: 8px;
}
</style>配合 Grid 做宫格布局
当选项更像“卡片入口”而不是普通文案时,可以给 Radio.Group 传默认插槽,再在内部用 Grid + Radio 自己组织布局。
vue
<script setup lang="ts">
import { createForm } from '@formily/core'
import { FormItem, Grid, Radio } from '@silver-formily/vant'
import { Field, FormProvider } from '@silver-formily/vue'
const form = createForm({
values: {
scene: 'repair',
},
})
const sceneOptions = [
{
label: '维修申请',
value: 'repair',
description: '适合设备报修、工单登记这类处理时效明确的场景。',
},
{
label: '拜访记录',
value: 'visit',
description: '适合需要沉淀客户跟进内容、拍照和备注的移动流程。',
},
{
label: '补货申请',
value: 'restock',
description: '适合门店巡检后快速提交库存补充需求。',
},
{
label: '请假审批',
value: 'leave',
description: '适合字段较少,但希望入口展示更清晰的轻流程。',
},
]
const GridColumn = Grid.GridColumn
</script>
<template>
<FormProvider :form="form">
<div class="demo-panel">
<Field
name="scene"
title="流程类型"
:decorator="[FormItem, { labelAlign: 'top' }]"
:component="[Radio.Group]"
>
<Grid :columns="2" :column-gap="10" :row-gap="10">
<GridColumn
v-for="option in sceneOptions"
:key="option.value"
>
<Radio
:name="option.value"
class="scene-radio"
label-position="left"
>
<div class="scene-radio__content">
<div class="scene-radio__title">
{{ option.label }}
</div>
<div class="scene-radio__description">
{{ option.description }}
</div>
</div>
</Radio>
</GridColumn>
</Grid>
</Field>
</div>
</FormProvider>
</template>
<style scoped>
:deep(.scene-radio) {
display: flex;
align-items: flex-start;
width: 100%;
min-height: 100%;
margin: 0;
padding: 12px;
box-sizing: border-box;
border: 1px solid var(--van-border-color);
border-radius: 12px;
background: var(--van-background-2);
transition:
border-color 0.2s ease,
background-color 0.2s ease;
}
:deep(.scene-radio .van-radio__label) {
flex: 1;
margin: 0;
}
:deep(.scene-radio .van-radio__icon) {
flex: none;
margin-left: 10px;
}
:deep(.scene-radio.van-radio--checked) {
border-color: var(--van-primary-color);
background: color-mix(in srgb, var(--van-primary-color) 10%, #fff);
}
.scene-radio__content {
display: grid;
gap: 6px;
}
.scene-radio__title {
color: var(--van-text-color);
font-size: 14px;
font-weight: 600;
line-height: 1.4;
}
.scene-radio__description {
color: var(--van-text-color-2);
font-size: 12px;
line-height: 1.5;
}
</style>自定义选项内容
vue
<script setup lang="ts">
import { createForm } from '@formily/core'
import { FormItem, Radio } from '@silver-formily/vant'
import { Field, FormProvider } from '@silver-formily/vue'
const form = createForm({
values: {
plan: 'pro',
},
})
const planOptions = [
{
label: '标准版',
value: 'basic',
description: '适合轻量表单,快速接入移动端页面。',
},
{
label: '专业版',
value: 'pro',
description: '适合多步骤提交流程和联动字段较多的场景。',
},
]
</script>
<template>
<FormProvider :form="form">
<div class="demo-panel">
<Field
name="plan"
title="套餐选择"
:decorator="[FormItem, { labelAlign: 'top' }]"
:component="[Radio.Group, { direction: 'vertical' }]"
:data-source="planOptions"
>
<template #option="{ option }">
<div class="plan-option">
<div class="plan-option__label">
{{ option.label }}
</div>
<div class="plan-option__description">
{{ option.description }}
</div>
</div>
</template>
</Field>
</div>
</FormProvider>
</template>
<style scoped>
.plan-option {
display: grid;
gap: 4px;
}
.plan-option__label {
color: var(--van-text-color);
font-size: 14px;
font-weight: 600;
line-height: 1.4;
}
.plan-option__description {
color: var(--van-text-color-2);
font-size: 12px;
line-height: 1.5;
}
</style>允许再次点击取消
当某个字段本身允许“不选任何项”时,可以给 Radio.Group 打开 cancelable,让用户再次点击当前已选项时直接清空值。
vue
<script setup lang="ts">
import { createForm } from '@formily/core'
import { Form, FormButtonGroup, FormItem, Radio, Submit } from '@silver-formily/vant'
import { Field } from '@silver-formily/vue'
import { showDemoResult } from '../shared'
const form = createForm({
values: {
reminderChannel: 'sms',
},
})
const reminderOptions = [
{
label: '短信提醒',
value: 'sms',
},
{
label: '电话提醒',
value: 'phone',
},
{
label: '站内消息',
value: 'site',
},
]
async function handleSubmit(values: typeof form.values) {
await showDemoResult(values)
}
</script>
<template>
<Form :form="form">
<div class="demo-tip">
当前示例开启了 <code>cancelable</code>,再次点击已选中的选项会把字段值清空。
</div>
<Field
name="reminderChannel"
title="提醒方式"
:decorator="[FormItem, { labelAlign: 'top' }]"
:component="[Radio.Group, { cancelable: true, direction: 'vertical' }]"
:data-source="reminderOptions"
/>
<FormButtonGroup>
<Submit :on-submit="handleSubmit">
查看结果
</Submit>
</FormButtonGroup>
</Form>
</template>
<style scoped>
.demo-tip {
margin: 0 0 12px;
color: var(--van-text-color-2);
font-size: 12px;
line-height: 1.6;
}
</style>禁用状态
vue
<script setup lang="ts">
import { createForm } from '@formily/core'
import { FormItem, Radio } from '@silver-formily/vant'
import { Field, FormProvider } from '@silver-formily/vue'
const form = createForm({
values: {
disabledValue: 'wechat',
},
})
</script>
<template>
<FormProvider :form="form">
<div class="demo-panel">
<Field
name="disabledValue"
title="禁用态"
:decorator="[FormItem, { labelAlign: 'top' }]"
:component="[Radio.Group, { disabled: true }]"
:data-source="[
{ label: '微信通知', value: 'wechat' },
{ label: '短信通知', value: 'sms' },
]"
/>
</div>
</FormProvider>
</template>API
使用约定
Field上的dataSource会自动映射到Radio.Group的optionsreadPretty模式下会自动显示当前选项的label,找不到匹配项时回退显示原始值cancelable开启后,点击当前已选中的选项会清空字段值,适合“可取消”的单选场景- 对象选项推荐写成
{ label, value },同时也兼容直接传字符串 / 数字 / 布尔值数组 - 如果要复刻 Vant 官方“搭配单元格组件使用”的布局,可以给
Radio.Group传默认插槽,内部直接放原始Radio子节点 - 如果要做宫格/卡片式选择器,也可以给
Radio.Group传默认插槽,在内部配合Grid组织布局
Radio.Group 扩展属性
| 属性名 | 类型 | 描述 | 默认值 |
|---|---|---|---|
options | Array<RadioOption | string | number | boolean> | 选项列表,通常由 dataSource 自动映射 | [] |
cancelable | boolean | 是否允许再次点击已选项时取消选中 | false |
labelPosition | enum | 统一控制选项文字相对图标的位置 | - |
labelDisabled | boolean | 是否禁用点击文字切换 | - |
Radio.Group 官方透传属性
以下分组属性会直接透传给 Vant RadioGroup:
| 属性名 | 类型 | 描述 | 默认值 |
|---|---|---|---|
modelValue | unknown | 当前值 | - |
shape | enum | 图标形状 | 官方默认值 |
direction | enum | 排列方向 | 官方默认值 |
disabled | boolean | 是否禁用 | 官方默认值 |
iconSize | number | string | 图标大小 | 官方默认值 |
checkedColor | string | 选中颜色 | 官方默认值 |
RadioOption
对象选项除了 label / value 之外,也支持透传 Vant 单个 Radio 的常见属性:
| 属性名 | 类型 | 描述 | 默认值 |
|---|---|---|---|
label | any | 选项文案 | - |
value | unknown | 选项值 | - |
disabled | boolean | 单个选项禁用 | - |
shape | enum | 单个选项图标形状 | - |
iconSize | number | string | 单个选项图标大小 | - |
checkedColor | string | 单个选项选中颜色 | - |
labelPosition | enum | 单个选项文案位置 | - |
labelDisabled | boolean | 单个选项是否禁用点击文案 | - |
Radio.Group Slots
| 插槽名 | 描述 | 插槽参数 |
|---|---|---|
default | 完全自定义分组内容,适合搭配 Cell / CellGroup | - |
option | 基于 options 自定义每个选项的渲染内容 | object |
Events
| 事件名 | 描述 | 回调参数 |
|---|---|---|
update:modelValue | 选中值变化时触发 | Function |
参考
属性命名和交互能力主要参考 Vant Radio 官方文档(正式站)。