Skip to content

字段渲染

表格里的每一格、表单里的每一项、详情里的每一行,都是「一个字段 + 一个场景」交给渲染引擎选出来的组件。本篇讲清楚这套选择机制,以及内置了哪些组件——这是自定义与覆盖的基础。

两个场景,两个入口组件

字段渲染只有两个场景:

  • input(表单态):可编辑,对应组件 FieldInput,用在表单页与高级搜索区。
  • cell(展示态):只读,对应组件 FieldCell,用在列表单元格与详情行。

FieldInput / FieldCell 本身几乎不含逻辑——它们只做一件事:调用 selectFieldWidget(scene, field, model) 拿到真正的组件,再把 field/value 透传下去:

vue
<!-- FieldInput.vue 几乎全部逻辑 -->
<script setup lang="ts">
const comp = computed(() => selectFieldWidget('input', props.field, props.model))
</script>
<template>
  <component :is="comp" :field="field" :value="value"
    @update:value="(v) => emit('update:value', v)" />
</template>

选组件的依据:FieldSchema 的两个字段

selectFieldWidget 主要看字段 schema 的两个字段:

  • component:后端推导出的中立组件名(text / select / money …)——这是选组件的首要依据
  • dataType:字段数据类型(STRING / ENUM / BOOLEAN …)——component 没匹配上时的次要依据。

「中立」的意思是:后端不下发「用 antd 的 a-select」,只下发 component: 'select',由前端决定 select 用什么实现。这层解耦让换 UI 库、换组件成为可能。

内置组件清单

输入组件(scene = input)

component组件渲染
text(兜底)TextInputa-input 单行文本
textareaTextareaInput多行文本域
numberNumberInput数字输入
moneyMoneyInput金额输入
switchSwitchInput开关
selectSelectInput下拉选择(用 field.options
dateDateInput日期选择
datetimeDatetimeInput日期时间选择

未匹配到任何注册项时,input 场景兜底回退到 TextInput

单元格组件(scene = cell)

component组件渲染
默认(兜底)DefaultCell纯文本展示
moneyMoneyCell金额格式化
selectTagCell语义色 tag(取 option.label,按枚举序循环配色)
switchTagCell是/否 tag

未匹配到时,cell 场景兜底回退到 DefaultCell

为什么单元格比输入组件少

展示态大量字段共享「纯文本」表现(DefaultCell 兜住),只有金额、枚举/布尔需要特殊呈现(金额格式化、tag 着色)。输入态则每种数据类型都要专门的录入控件,所以输入组件更细分。

组件的 Props 契约

无论内置还是自定义,字段组件都遵循统一的 props 契约——这正是自定义与覆盖能即插即用的前提:

ts
// 输入组件(scene = input)
defineProps<{ field: FieldSchema; value: any; model?: string }>()
defineEmits<{ 'update:value': [any] }>()   // 双向绑定靠它

// 单元格组件(scene = cell)
defineProps<{ field: FieldSchema; value: any; record?: any; model?: string }>()
// 只读,无 emit
  • field:当前字段的完整 schema(含 label / options / length / readonly 等)。
  • value:当前值。
  • model:当前模型编码(用于按模型维度匹配,自定义时常用)。
  • record(仅 cell):整行记录,需要联动其他字段时可用。

一个内置组件长这样(看完你就能照着写自定义的):

vue
<!-- inputs/TextInput.vue 全文 -->
<script setup lang="ts">
defineProps<{ field: FieldSchema; value: any }>()
const emit = defineEmits<{ 'update:value': [any] }>()
</script>
<template>
  <a-input :value="value" :placeholder="`请输入${field.label}`"
    :maxlength="field.length || undefined" :disabled="field.readonly"
    allow-clear @update:value="(v) => emit('update:value', v)" />
</template>

下一步

理解了「字段如何选到组件」,下一步就是改变这个选择——