Vue3 + Ant Design Vue 实现 Table 表格嵌套 Radio 单选框
在后台管理系统开发中,表格(Table)是核心展示组件,而在表格中嵌套单选框(Radio)实现行内评分 / 选择是非常常见的需求。本文将基于你的实际业务代码,详细讲解如何在 Vue3 + TypeScript + Ant Design Vue 技术栈下,实现表格单元格嵌套 Radio 单选框,并完成评分联动、总分计算等核心交互逻辑。
核心需求拆解
核心需求拆解
基于你的业务场景,我们需要实现:
- 在 Table 表格中,为指定列嵌套 Radio 单选框,实现行内分值选择
- 单选框选中值实时绑定到表单状态,支持表单校验
- 表格底部展示总分汇总,提升数据展示完整性
完整实现代码
1. 基础组件结构(Vue3 + TS + Ant Design Vue)
vue
<template>
<div class="w-[80%] p-8">
<!-- 表单容器:用于数据绑定和校验 -->
<Form
name="osiForm"
:model="formState"
:label-col="{ span: 0 }"
:wrapper-col="{ span: 22 }"
ref="formRef"
scroll-to-first-error
>
<!-- 核心表格组件 -->
<Table
:columns="situationColumns"
:data-source="situation"
:pagination="false"
bordered
class="score-table"
>
<!-- 表格单元格自定义插槽:核心嵌套逻辑 -->
<template #bodyCell="{ column, record }: any">
<!-- 1. 问题文本列:仅展示文本 + 表单校验 -->
<template v-if="column.key === 'situation'">
<FormItem
:name="record.value"
:rules="[{ required: true, message: `请选择${record.label}` }]"
>
<div>{{ record.label }}</div>
</FormItem>
</template>
<!-- 2. Y/N联动列:禁用的下拉框,展示联动结果 -->
<template v-if="column.key === 'yn'">
<Select
:disabled="true"
v-model:value="selectValue[record.value]"
placeholder="请选择"
style="width: 80px"
:options="[
{ label: '是/Y', value: '1' },
{ label: '否/N', value: '2' }
]"
/>
</template>
<!-- 3. 分值列:核心 - 嵌套Radio单选框 -->
<template v-if="column.key !== 'yn' && column.key !== 'situation'">
<div class="flex items-center justify-center">
<RadioGroup
v-model:value="formState[record.value]"
:style="{ display: 'flex', gap: '8px' }"
@change="handleRadioChange(record.value)"
>
<Radio :value="column.value">{{ column.value }}分</Radio>
</RadioGroup>
</div>
</template>
</template>
<!-- 表格汇总行:展示总分 -->
<template #summary>
<TableSummary>
<TableSummaryRow>
<TableSummaryCell style="background: #fafafa" :index="0">
总分
</TableSummaryCell>
<TableSummaryCell :col-span="6" :index="1">
{{ formState?.situationScore ?? '-' }}
</TableSummaryCell>
</TableSummaryRow>
</TableSummary>
</template>
</Table>
</Form>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
// 引入Ant Design Vue组件
import {
Form,
FormItem,
Radio,
RadioGroup,
Select,
Table,
TableSummary,
TableSummaryCell,
TableSummaryRow,
} from 'ant-design-vue';
// ========== 数据定义 ==========
// 表格数据源:评估问题列表
const situation = [
{ value: 'exceedExpectedFrequency', label: 'a.自伤行为发生的频率超过了你的预期?' },
{ value: 'growingSeriousness', label: 'b.自伤行为导致的后果越来越严重(例如,伤口更深,面积更大)?' },
{ value: 'mustIncreaseTolerance', label: 'c.需要更频繁或强度更大的自伤行为才能达到最初自伤所达到的效果?' },
{ value: 'consumeMuchTime', label: 'd.自伤行为或想法会消耗你大量的时间(例如,计划和思考,收集和隐藏利器等)?' },
{ value: 'outOfSelfControl', label: 'e.尽管有想要减少或控制这种行为的愿望,但却无法做到?' },
{ value: 'knowHarmfulStillDo', label: 'f.尽管已经意识到自伤行为对你的身体和/或情绪危害,仍继续该行为?' },
{ value: 'abandonImportant', label: 'g.因为自伤行为的发生导致放弃或减少了重要的社交、家庭、学校或创造性活动?' },
];
// 表格列配置:包含分值映射
const situationColumns = [
{ title: '情况选项', dataIndex: 'situation', key: 'situation', width: '29%' },
{ title: '从来没有', dataIndex: 'never', key: 'never', value: '0', width: 120, align: 'center' },
{ title: '偶尔', dataIndex: 'occasionally', key: 'occasionally', value: '1', width: 100, align: 'center' },
{ title: '有时', dataIndex: 'sometimes', key: 'sometimes', value: '2', width: 100, align: 'center' },
{ title: '经常', dataIndex: 'often', key: 'often', value: '3', width: 100, align: 'center' },
{ title: '总是', dataIndex: 'always', key: 'always', value: '4', width: 100, align: 'center' },
{ title: 'Y/N', dataIndex: 'yn', key: 'yn', width: 120, align: 'center' },
];
// ========== 状态管理 ==========
const formRef = ref<any>(); // 表单引用
const formState = ref<any>({}); // 表单状态(存储单选框选中值、总分)
const selectValue = ref<any>({}); // Y/N列联动值
// ========== 核心方法 ==========
/**
* Radio单选框变更事件:计算总分 + 联动Y/N状态
* @param keys 当前行的唯一标识
*/
const handleRadioChange = (keys: string) => {
// 1. 联动Y/N状态:≥2分显示"是/Y",否则显示"否/N"
selectValue.value[keys] = Number(formState.value[keys]) >= 2 ? '1' : '2';
// 2. 计算所有行的总分
const situationKeys = situation.map((item) => item.value);
let total: number | undefined = 0;
situationKeys.forEach((key: string) => {
if (formState.value[key] !== null && formState.value[key] !== undefined) {
total += Number(formState.value[key]);
}
});
// 3. 更新总分到表单状态
formState.value.situationScore = total;
// 4. 清除当前行的表单校验提示(可选)
formRef.value?.clearValidate(keys);
};
</script>
<style scoped>
.score-table {
--ant-table-header-text-color: #333;
--ant-table-body-text-color: #666;
}
/* 优化Radio单选框的展示样式 */
:deep(.ant-radio-group) {
display: flex;
gap: 8px;
}
</style>
关键技术点详解
1. Table 表格嵌套 Radio 的核心逻辑
Ant Design Vue 的 Table 组件提供了#bodyCell插槽,用于自定义单元格内容,这是实现嵌套的核心:
vue
<template #bodyCell="{ column, record }: any">
<!-- 只在分值列渲染Radio -->
<template v-if="column.key !== 'yn' && column.key !== 'situation'">
<RadioGroup v-model:value="formState[record.value]">
<Radio :value="column.value">{{ column.value }}分</Radio>
</RadioGroup>
</template>
</template>
column:当前列的配置(包含value字段,即分值 0-4)
record:当前行的数据(包含value字段,即行唯一标识)
v-model:value="formState[record.value]":将 Radio 选中值绑定到表单状态,实现 "行 + 列" 维度的精准绑定
2. Radio 选中值的绑定与联动
-
值绑定
:通过
formState[record.value]将每一行的 Radio 选中值存储到表单状态中,例如:
- 第一行(exceedExpectedFrequency)选中 "2 分" →
formState.exceedExpectedFrequency = 2 - 第二行(growingSeriousness)选中 "3 分" →
formState.growingSeriousness = 3
- 第一行(exceedExpectedFrequency)选中 "2 分" →
-
联动逻辑
:
handleRadioChange方法在 Radio 值变更时触发,完成两个核心操作:
- 根据分值自动设置 Y/N 列的状态(≥2 分为 "是",否则为 "否")
- 遍历所有行的分值,累加计算总分并展示在表格汇总行
3. 表单校验与体验优化
- 为每个问题行添加必填校验:
FormItem绑定record.value作为校验字段,确保用户必须选择分值 - 单选框变更时清除校验提示:
formRef.value.clearValidate(keys),避免选中后仍显示校验错误 - 表格汇总行:使用
TableSummary组件展示总分,提升数据可视化效果