Radio
Radio.Group是面向 Formily 单值字段的 Vant 单选框组封装,延续dataSource + option slot + readPretty这套现有组件习惯。
提示
表单场景优先使用 Radio.Group,dataSource / options 统一使用 { label, value } 对象数组。
基础使用
vue
<script setup lang="ts">
import { createForm } from '@silver-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 @submit="handleSubmit">
查看结果
</Submit>
</FormButtonGroup>
</Form>
</template>排列方向与样式
其中纵向示例改成了 CellGroup + Cell + Radio 组合,视觉上会比纯纵向堆叠更适合移动端列表场景。
vue
<script setup lang="ts">
import { createForm } from '@silver-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 '@silver-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 @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 '@silver-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 '@silver-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;
text-align: left;
}
.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 '@silver-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 @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 '@silver-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
Radio.Group 扩展属性
| 属性名 | 类型 | 描述 | 默认值 |
|---|---|---|---|
options | RadioOption[] | 选项列表,通常由 dataSource 自动映射 | [] |
cancelable | boolean | 是否允许再次点击已选项时取消选中 | false |
labelPosition | enum | 统一控制选项文字相对图标的位置 | - |
labelDisabled | boolean | 是否禁用点击文字切换 | - |
单个 Radio 以及 Radio.Group 透传的官方属性、插槽、事件直接参考 Vant Radio 官方文档。
RadioOption
统一使用 { label, value } 格式来描述选项。
除 label / value 之外,RadioOption 支持直接透传 Vant 单个 Radio 可用属性,具体请直接参考 Vant Radio 官方文档。
额外说明:
- 通过
option作用域插槽自定义渲染时,可以继续在选项对象上挂description、tag这类业务字段。 - 这类额外业务字段会保留在插槽参数
option上,但不会透传到内部VanRadioDOM。