惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News and Events Feed by Topic
S
Security @ Cisco Blogs
S
Secure Thoughts
Attack and Defense Labs
Attack and Defense Labs
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Hacker News - Newest:
Hacker News - Newest: "LLM"
Recent Commits to openclaw:main
Recent Commits to openclaw:main
H
Hacker News: Front Page
博客园 - 叶小钗
H
Heimdal Security Blog
Microsoft Security Blog
Microsoft Security Blog
Forbes - Security
Forbes - Security
AI
AI
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Troy Hunt's Blog
罗磊的独立博客
Application and Cybersecurity Blog
Application and Cybersecurity Blog
爱范儿
爱范儿
GbyAI
GbyAI
The Last Watchdog
The Last Watchdog
TaoSecurity Blog
TaoSecurity Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
D
DataBreaches.Net
Recent Announcements
Recent Announcements
Schneier on Security
Schneier on Security
C
Cisco Blogs
美团技术团队
D
Docker
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
WordPress大学
WordPress大学
月光博客
月光博客
雷峰网
雷峰网
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
H
Hackread – Cybersecurity News, Data Breaches, AI and More
A
Arctic Wolf
B
Blog RSS Feed
Cisco Talos Blog
Cisco Talos Blog
C
Cybersecurity and Infrastructure Security Agency CISA
V
Vulnerabilities – Threatpost
V2EX - 技术
V2EX - 技术
Y
Y Combinator Blog
N
News and Events Feed by Topic
www.infosecurity-magazine.com
www.infosecurity-magazine.com
W
WeLiveSecurity
Security Archives - TechRepublic
Security Archives - TechRepublic
G
GRAHAM CLULEY
Jina AI
Jina AI
Hugging Face - Blog
Hugging Face - Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
The Hacker News
The Hacker News

Node.js

Bun 用 Rust 重写的 PR 已合并到主分支 TanStack 最新版本被投毒,病毒会盗密钥和报复性删除用户目录 Bun 从 Zig 到 Rust 的迁移已经实锤,下个版本可能成为最后一个 Zig 版本 bun 要从 zig 迁移 rust 了, 100 万刀被 claude 收购了,开始影响语言决策了 - V2EX JS/Node 已经是新时代的 Java 了吧 Node.js 服务大部分时候连阿里云 Redis 是正常的,但是又时候会突然报 read ECONNRESET? - V2EX 2026 年, node 写后端你用的 nestjs, fastify, honojs 还是其他? npmmirror 竟然有些包没有更新到最新版本 大家 typescript 下用的最多的是后端框架是哪个? 求助,大家 node.js 是怎么代理的 ai 时代, node.js 成为核心语言 NestJS + Swagger UI:非 200 状态码 Execute 时返回值不显示问题 深入 alova3 服务端能力:分布式 BFF 层到 API 网关的最佳实践 - V2EX Anthropic 收购 Bun 关于 Node.js 中的事件循环问题。 - V2EX Hoa - 一个极简 Web 框架 有没有 v 友遇到 windows10 丢失系统环境变量的问题 Esbuild 进程占用高 有没有推荐的 Nodejs 的 sass 多租户系统 2025 年 node 项目,乱成一锅粥的 typescript ESM import 写法该怎么选? [EvanNav 6.3.1] 如何删除加载页面,让 Nav 更符合你的需求⁉️ 删除 node_modules 文件夹非常耗时 Node.JS 作者 Ryan Dahl 的故事 Vona ORM 文档终于肝完了,欢迎拍砖 Node.js 官网更新了 adonisjs 有没有现成的注册登录库? 现在流行的 Node.js 做后台比传统的 Java .Net 有哪些优势? 现在大家开发 api 都用什么 node 框架?有没有想 rails 一样,功能齐全的框架? Prisma 不能优雅的支持 DTO,可以试试 Vona ORM 比 prisma studio 更好性能的类 studio 数据管理 node 怎么获取剪切板中复制的文件信息 写了个 js 小工具库, 希望大家给点建议 NodeJS 阿里云服务器,经常遇到整个服务器卡死,系统盘读操作被占满 不用继承就能给类加事件系统?这波原型链操作我给满分 做了一个函数式、带类型、超顺手的微型事件库,已发布到 npm 告别 Node.js 工具链地狱: Bun 如何让 MCP Server 开发效率翻 3 倍 - V2EX redis 集群模式支持批量操作库 mget/mset 😂😂浪费了 3 天时间尝试在新项目中使用 prisma,最后还是决定回到 typeorm - V2EX 吐槽一下 bun node.js 有什么比较好用的微信 sdk - V2EX nodejs 后端,怎么比较好的生成接口文档?(排除 nest.js) - V2EX Cherry Studio 只用 bun.exe 怎么绕过去? 把 node.js 程序打包成可执行文件,不过只有 10M - V2EX 前端包管理工具调研 - V2EX 请问下 JavaScript 的 CustomEvent 和自己手搓的发布订阅哪个更快效率更好? 同样是运行 npm run build 打包程序 cursor 比 hbx 慢 1000 倍 从零开始开发一个 MCP Server! 现在还有人 eggjs 吗? - V2EX TypeScript7.0 用 go 重写, 10 倍快,看了两遍确定是真的... - V2EX 做了几个扩展,顺便整理了一下开源了一个浏览器扩展开发模版 prisma 运行 migrate 命令之后,总会把其他不相关的表给删除 问个页面跳转访问方案 求助! v 友们求推荐靠谱的 node.js 学习及面试网站和视频 大家正使用哪个 node 版本,会保持用最新版吗? 请教大家一个在 hono.js 中使用 ts 的类型兼容性问题
告别 Django Admin!这个 NodeJS 全栈框架让你在 DTO 中直接配置 Table/Form 渲染
zhennann · 2026-05-21 · via Node.js

一、CRUD 页面的重复劳动,该结束了

做中后台的同学一定不陌生这个流程:建一张表,然后写列表页、搜索栏、新建表单、编辑表单、详情页……几乎每个字段都要在前端和后端各写一遍——后端定义字段类型和校验规则,前端再对应写一遍表格列、表单控件、搜索条件。

Django Admin 的出现缓解了这个问题,它能根据 Model 自动生成增删改查页面。但代价也不小:服务端模板渲染、前端技术栈老旧、界面定制困难、不支持 SSR 。

如果能在定义数据结构的同时,直接声明这个字段在表格里怎么显示、在表单里用什么控件,岂不是一步到位?

VonaJS 做的就是这件事。

二、VonaJS 是什么

VonaJS 是一款全栈框架,支持在同一个代码库中构建 SSR/SPA/Web 网站/Admin 中后台。它的核心能力有两点:

  • DTO 动态推断与生成:基于 Zod4 的统一 Schema ,一份定义同时用于参数校验、OpenAPI 文档生成、Table/Form 渲染、数据序列化与脱敏
  • CRUD 动态渲染:根据 DTO 中声明的渲染元数据,自动生成列表页、条目页、搜索表单,底层基于 Tanstack Table / Tanstack Form / Tanstack Query 的最佳实践

技术栈方面,后端是 Koa + Knex + Zod4 + Redis ,前端是 Vue3 + Vite8 + Tanstack 全家桶,UI 层可搭配 Daisyui/Tailwindcss/Quasar/Vuetify 。

一句话:用 DTO 驱动 CRUD 渲染,让中后台开发从"写页面"变成"配字段"。

三、Entity:字段渲染的起点

VonaJS 的渲染配置从 Entity 开始。每个字段的渲染元数据直接写在字段定义旁边,一目了然:

@Entity<IEntityOptionsStudent>('demoStudent', {
  openapi: { title: $locale('Student') },
  fields: {
    id: $makeMetadata(ZovaRender.order(1, 'core')),
    iid: $makeMetadata(ZovaRender.visible(false)),
    deleted: $makeMetadata(ZovaRender.visible(false)),
    createdAt: $makeMetadata(
      ZovaRender.order(-2, 'max'),
      ZovaRender.field('basic-date:formFieldDate'),
      ZovaRender.cell('basic-date:date'),
    ),
    updatedAt: $makeMetadata(
      ZovaRender.order(-1, 'max'),
      ZovaRender.field('basic-date:formFieldDate'),
      ZovaRender.cell('basic-date:date'),
    ),
  },
})
export class EntityStudent extends EntityBase {
  @Api.field(
    v.title($locale('Name')),
    v.required(),
    v.min(2),
    ZovaRender.order(1),
    ZovaRender.cell('basic-table:actionView'),
  )
  name: string;

  @Api.field(
    v.title($locale('Description')),
    v.optional(),
    ZovaRender.order(2),
    ZovaRender.field('basic-select:formFieldSelect', {
      placeholder: 'Please Select',
      items: [
        { title: 'Male', value: 1 },
        { title: 'Female', value: 2 },
      ],
    }),
    ZovaRender.cell('basic-select:select', {
      items: [
        { title: 'Male', value: 1 },
        { title: 'Female', value: 2 },
      ],
    }),
  )
  description?: string;
}

逐行看关键的渲染配置:

配置 含义
ZovaRender.order(1) 字段排在第 1 位
ZovaRender.visible(false) 隐藏该字段(不渲染)
ZovaRender.cell('basic-table:actionView') 表格中渲染为可点击查看的链接
ZovaRender.cell('basic-date:date') 表格中渲染为日期格式
ZovaRender.field('basic-select:formFieldSelect', {...}) 表单中渲染为下拉选择框,并传入选项数据
ZovaRender.field('basic-date:formFieldDate') 表单中渲染为日期选择器

核心思路:渲染配置紧跟字段定义,改一个字段时校验规则和渲染行为一起调整,不用再去前端组件里翻找对应位置。

ZovaRender.cell() 控制表格列怎么显示,ZovaRender.field() 控制表单用什么控件。配置格式统一为 模块名:组件名,并可通过第二个参数传入组件 props ,比如下拉框的选项列表、class 、style 等。

Entity 定义了字段级的渲染元数据,DTO 则负责把这些字段组装成完整的页面。一个 DTO 就是一个页面,页面结构通过 blocks 声明式定义。

1. 列表页

@Dto<IDtoOptionsStudentSelectResItem>({
  blocks: [
    ZovaRender.block('basic-page:blockPage', {
      blocks: [
        ZovaRender.block('basic-page:blockFilter'),
        ZovaRender.block('basic-page:blockToolbarBulk', {
          actions: [ZovaRender.tableActionBulk('basic-table:actionCreate')],
        }),
        ZovaRender.block('basic-page:blockTable'),
        ZovaRender.block('basic-page:blockPager'),
      ],
    }),
  ],
})
export class DtoStudentSelectResItem extends $Dto.get(() => ModelStudent) {
  @Api.field(
    v.title($locale('Operations')),
    ZovaRender.order(1, 'max'),
    ZovaRender.cell('basic-table:actionOperationsRow', {
      actions: [
        ZovaRender.tableActionRow('basic-table:actionUpdate'),
        ZovaRender.tableActionRow('basic-table:actionDelete'),
      ],
    }),
  )
  _operationsRow?: unknown;
}

这个列表页由四个 block 组成:搜索区 → 批量操作栏 → 数据表格 → 分页器,从上到下依次排列。操作栏里放了一个"新建"按钮,表格行末尾自动追加"编辑"和"删除"操作列。

DTO 继承自 $Dto.get(() => ModelStudent),这意味着列表的字段直接从 Model (进而从 Entity )继承,不需要重复定义。

2. 搜索条件

@Dto<IDtoOptionsStudentSelectReq>({
  openapi: { filter: { table: 'demoStudent' } },
  fields: {
    name: $makeSchema(v.optional(), z.string()),
    createdAt: $makeSchema(
      ZovaRender.field('basic-date:formFieldDateRange'),
      v.filterTransform('a-web:dateRange'),
      v.optional(),
      z.string(),
    ),
  },
})
export class DtoStudentSelectReq extends $Dto.queryPage(EntityStudent, ['name', 'createdAt']) {}

搜索条件的 DTO 独立于列表数据。这里 name 是普通文本搜索,createdAt 渲染为日期范围选择器(formFieldDateRange),并通过 v.filterTransform 自动将前端选择的日期范围转换为后端查询格式。

3. 新建/编辑页

@Dto<IDtoOptionsStudentCreate>({
  blocks: [
    ZovaRender.block('basic-pageentry:blockPageEntry', {
      blocks: [
        ZovaRender.block('basic-pageentry:blockForm'),
        ZovaRender.block('basic-pageentry:blockToolbarRow', {
          actions: [
            ZovaRender.formActionRow('basic-form:actionSubmit', {
              permission: { actionInherit: 'update', formScene: ['create', 'edit'] },
            }),
            ZovaRender.formActionRow('basic-form:actionBack', { permission: { public: true } }),
          ],
        }),
      ],
    }),
  ],
})
export class DtoStudentCreate extends $Dto.create(() => ModelStudent) {}

新建页和编辑页结构相同:表单区 + 操作栏(提交/返回)$Dto.create` 和 `$Dto.update 分别继承自 Model ,自动带上 Entity 中定义的字段渲染配置。区别在于 formScene 控制提交按钮的权限——创建和编辑时显示,查看时隐藏。

4. 详情页

@Dto<IDtoOptionsStudentView>({
  blocks: [
    ZovaRender.block('basic-pageentry:blockPageEntry', {
      blocks: [
        ZovaRender.block('basic-pageentry:blockForm'),
        ZovaRender.block('basic-pageentry:blockToolbarRow', {
          actions: [
            ZovaRender.formActionRow('basic-form:actionBack', { permission: { public: true } }),
          ],
        }),
      ],
    }),
  ],
})
export class DtoStudentView extends $Dto.get(() => ModelStudent) {}

详情页只比编辑页少了一个提交按钮,继承自 $Dto.get,表单自动为只读模式。

总结一下 DTO 的页面组装模式:用 blocks 声明页面由哪些区域组成,用 actions 声明操作按钮,字段渲染则自动继承 Entity 的配置。整个过程不需要写 Vue 组件、不需要拼模板,一个 DTO 文件就是一个完整的 CRUD 页面。

五、与 Django Admin 对比:为什么值得换

特性 VonaJS Django Admin
后端技术栈 NodeJS + TypeScript Python + 服务端模板语言
前端技术栈 Vue3 + Vite8 + TypeScript HTML + CSS + JS
渲染机制 同构 SSR 服务端模板渲染
双层页签导航 支持 不支持
界面定制 自由定制,组件级可控 定制成本高,需覆盖模板
SSR 支持(含侧边栏、主题等) 不支持

Django Admin 的核心问题是:它用服务端模板渲染页面,前端技术栈停留在传统 HTML/CSS/JS 时代。想定制一个下拉框的样式、加一个自定义交互,就得去覆盖模板文件,维护成本随业务复杂度急剧上升。

VonaJS 采用前后端分离架构,前端是完整的 Vue3 应用,渲染配置通过 DTO 声明、组件按需替换,定制一个字段控件只需要换一个 ZovaRender.field() 的组件名。同时,Admin 中后台也支持 SSR ,刷新页面时侧边栏、多语言、主题等不会闪烁跳动。

六、与 NestJS 对比:DTO 不再只是校验

在 NestJS 中,DTO 的职责比较单一——参数校验。你需要用 class-validator 装饰器定义校验规则,再用 class-transformer 或手动方式生成 Swagger 文档。至于前端页面怎么渲染?那是另一个项目的事。

VonaJS 基于 Zod4 的统一 Schema ,让 DTO 同时承担四项职责:

  1. 参数校验:Zod 原生能力
  2. OpenAPI 文档:自动从 Schema 生成 Swagger 文档
  3. 渲染配置:通过 ZovaRender 声明字段在 Table/Form 中的渲染方式
  4. 数据序列化:Response 的脱敏处理、字段过滤

一份 Schema 定义,四处复用,从根源上消除了前后端字段定义不一致的问题。

七、总结

VonaJS 的核心价值很明确:用 DTO 驱动 CRUD 动态渲染,把中后台开发从"写页面"变成"配字段"。

过去你需要在 Entity 里定义字段、在 DTO 里定义校验、在前端组件里定义表格列和表单控件——三个地方维护同一组字段。现在,Entity 中一行 ZovaRender.cell()ZovaRender.field() 就能同时搞定显示和交互,DTO 中几个 block 就能组装出完整的页面结构。

如果你的项目是中后台系统、管理后台、数据驱动的 Web 应用,VonaJS 值得一试。

  • dto
  • 渲染
  • 声明式

    4 条回复    2026-06-10 10:52:34 +08:00

    indexError

    1

    indexError      5 月 21 日 via Android

    AI 想用哪个就用哪个

    zhennann

    2

    zhennann      5 月 21 日

    @indexError AI 确实能写任何框架的代码,但框架决定的是你让 AI 写多少。VonaJS 的 DTO 驱动渲染,一个文件配完增删改查,AI 少写 5 个页面组件,你也少审查 5 个页面组件。框架省的不是写代码的力气,是维护代码的成本。

    humbass

    3

    humbass      5 天前

    JS 写成这样也是够傻逼的,这种能流行就怪了。