DTO 里写几行注解,CRUD 页面就出来了——VonaJS 5.1 的声明式全栈思路

2026-05-23 21 预计阅读时间:1 分钟
来源:oschina.net AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:9 分钟

做后台管理系统,最枯燥的环节不是业务逻辑,而是反复写列表页、表单页、详情页。字段定义写一遍,表格列写一遍,表单控件再写一遍——三份几乎相同的配置,散落在不同文件里。VonaJS 5.1.34 的做法是:把渲染元数据直接绑在 Entity 字段上,一份配置驱动整个 CRUD 页面。底层依赖 Tanstack Table / Form / Query 三件套,框架帮你把"字段→列→控件→查询"这条链路串起来。

字段即配置:Entity 是渲染的起点

传统做法里,Entity(或 Model)只管数据结构,渲染逻辑在另一个文件单独维护。VonaJS 把两者合到一处——字段旁边直接写它"怎么显示":

@Entity<IEntityOptionsStudent>('demoStudent')
export class EntityStudent extends EntityBase {
  @Property()
  name: string;

  @Property({
    // 渲染元数据:告诉框架这个字段在表格/表单里怎么呈现
    component: 'input',
    label: '姓名',
    required: true,
  })
  name: string;

  @Property({
    component: 'select',
    label: '班级',
    options: 'classEnum', // 指向枚举或远程数据源
  })
  classId: number;

  @Property({
    component: 'datePicker',
    label: '入学日期',
    format: 'YYYY-MM-DD',
  })
  enrollDate: string;
}

关键变化:@Property 装饰器不再只描述类型,还携带了 UI 渲染指令component 决定控件类型,label 决定列头和表单标签,required 直接映射到表单校验。一份定义,表格列、表单字段、校验规则全部推导出来,不需要再写三份配置文件。

从 Entity 到 CRUD 页面的推导链

框架拿到 Entity 定义后,内部做了几件事:

  1. 表格列推导——每个 @Property 字段变成 Tanstack Table 的一列,label 作为列头,component 决定单元格渲染器(select 字段自动显示枚举文本而非原始 ID)。
  2. 表单控件推导——新增/编辑表单直接从字段列表生成 Tanstack Form 配置,component 映射到对应输入控件,required 变成校验规则。
  3. 查询参数推导——列表页的筛选条件也从 Entity 字段中提取,标记了 filterable 的字段自动生成搜索表单。
  4. API 调用绑定——Tanstack Query 的 queryKey、请求路径根据 Entity 名称自动生成,新增走 POST、列表走 GET、删除走 DELETE,约定优于配置。

整条链路的核心假设是:大部分后台 CRUD 页面的差异只在字段本身,交互模式高度雷同。框架把雷同的部分模板化,你只需要声明字段"是什么、怎么显示"。

实战:用 VonaJS 搭一个学生管理模块

下面是一个可以直接改造使用的最小示例。假设你已经初始化了 VonaJS 项目(npm create vona@latest),我们来定义一个"课程管理"的 Entity 并生成 CRUD 页面。

第一步:定义 Entity

// src/module/demo/entity/entityCourse.ts
import { Entity, Property, EntityBase } from 'vona';
import type { IEntityOptionsCourse } from './types';

@Entity<IEntityOptionsCourse>('demoCourse')
export class EntityCourse extends EntityBase {
  @Property({
    component: 'input',
    label: '课程名称',
    required: true,
    filterable: true, // 列表页可按此字段搜索
  })
  courseName: string;

  @Property({
    component: 'select',
    label: '课程类型',
    options: 'courseTypeEnum', // 枚举名,框架自动加载选项
    filterable: true,
  })
  courseType: number;

  @Property({
    component: 'inputNumber',
    label: '学分',
    required: true,
    min: 1,
    max: 10,
  })
  credit: number;

  @Property({
    component: 'datePicker',
    label: '开课日期',
    format: 'YYYY-MM-DD',
  })
  startDate: string;

  @Property({
    component: 'textarea',
    label: '课程简介',
    span: 24, // 表单中占满整行
  })
  description: string;
}

第二步:注册枚举(框架需要知道 select 的选项)

// src/module/demo/config/enums.ts
export const courseTypeEnum = {
  1: '必修课',
  2: '选修课',
  3: '实验课',
};

第三步:生成 CRUD 页面

VonaJS CLI 提供了生成命令,一条指令产出列表页、新增页、编辑页:

# 在项目根目录执行
vonas generate:crud demoCourse

命令执行后,src/module/demo/pages/ 下会出现:

  • courseList.vue——基于 Tanstack Table 的列表页,含筛选栏、分页、操作列
  • courseForm.vue——基于 Tanstack Form 的新增/编辑表单页
  • 相关的路由配置和 API service 文件自动注入

第四步:微调(如果默认生成不够用)

生成的页面是标准模板,大部分场景直接可用。如果需要定制,改两个地方:

// 覆盖表格列配置——比如隐藏 description 列、给 courseName 加链接
const columnOverrides = {
  description: { enableHiding: true, defaultHidden: true },
  courseName: { cell: (info) => <a href={`/course/${info.row.original.id}`}>{info.getValue()}</a> },
};

// 覆盖表单布局——比如把日期字段和类型字段放同一行
const formLayoutOverrides = {
  courseType: { span: 12 },
  startDate: { span: 12 },
};

覆盖项和 Entity 定义分离,不污染字段声明本身。

这种做法的边界和取舍

声明式 CRUD 生成不是万能的,它有几个明确的前提和限制:

适合的场景: - 后台管理系统的标准增删改查页面,字段以文本、数字、下拉、日期为主 - 同一模块的列表/表单/详情交互模式一致,只是字段不同 - 团队希望快速出页面原型,后续再逐页精调

需要绕道的场景: - 复杂的嵌套表单(比如主子表联动编辑),Entity 装饰器的表达能力有限,还是得手写 Tanstack Form 配置 - 非标准交互(拖拽排序、行内编辑批量保存),模板推导不出来 - 高度定制的数据可视化页面,和 CRUD 模式无关

一个潜在风险:Entity 文件会变"胖"。 字段定义、渲染配置、校验规则、筛选标记全堆在一起,字段多的时候可读性下降。建议的做法是——简单字段直接写装饰器,复杂渲染逻辑抽到单独的 columnOverrides / formOverrides 文件里,保持 Entity 本身只放"最核心的显示指令"。

另一个值得注意的点:框架强绑定 Tanstack 三件套。 如果你团队已经用 Ant Design Table 或 Element Plus Form,迁移成本不小——不是换个组件库的问题,而是整条"字段→渲染→查询"的推导链路都依赖 Tanstack 的 API 设计。新项目可以直接上,存量项目要评估替换范围。

上手清单

如果你打算试一下 VonaJS 5.1 的声明式 CRUD,可以按这个顺序走:

  1. npm create vona@latest 初始化项目,选一个简单模块(比如"分类管理")做试点
  2. 先写一个只有 3-4 个字段的 Entity,跑 generate:crud,看生成的页面是否满足预期
  3. 尝试加 filterablecomponent: 'select'、枚举配置,验证筛选和下拉是否自动生效
  4. columnOverrides / formOverrides 做一次微调,确认覆盖机制好用
  5. 再决定是否把复杂模块也迁移到这套模式

声明式 CRUD 的价值不在"少写代码"本身,而在把重复的配置收敛到一处、让字段定义成为唯一的数据源。如果你的后台系统有大量模式雷同的管理页面,这套思路能省下可观的对齐和维护成本。


相关推荐