WWW.YOUINFO.SITE
标签聚合 django

/tag/django

LinuxDo 最新话题 · 2026-06-08 17:42:56+08:00 · tech

目前有腾讯云4核4G、5M带宽云服务器,部署有小程序django后端程序,受限与内存大小,服务器经常拉满内存,如果在升级内存,费用太高。 手头上还有一款N100小主机,4核4线程,内存可支持到16G。现在就想能不能让本地小主机承载小程序的业务。 家里安装的是移动宽带,没有免费的ipv6; 查了下豆包可以按以下架构进行部署 各位帮忙看看按这个方案实施行不行?或者有没有其他更好的实现方式? 15 个帖子 - 11 位参与者 阅读完整话题

IT之家 · 2026-05-26 12:47:32+08:00 · tech

IT之家 5 月 26 日消息,标致摩托昨天正式推出了全新入门级复古踏板 ——Django Compact(或称姜戈 125)。新车提供三个版本:CBS 版售价 9980 元,ABS 版 10980 元,ABS 尾箱版 11380 元。 作为 Django 家族的新成员,该车定位紧凑型都市通勤踏板,其外观延续了 Django 家族经典的复古风格,但在细节上进行了现代化改造。 车头采用了更为紧凑的六边形 LED 大灯,配合标志性的“狮盾”日间行车灯,在保留复古韵味的同时增添了现代科技感。车身尺寸较 Django 150 有所缩减,整体线条更加简洁、干练。 新车提供冰雪白、樱桃红、奇异棕、闪光银等多种配色方案,以满足不同用户的个性化审美需求。 动力方面,这款新车搭载了一台排量为 125cc 的单缸风冷 EasyMotion 发动机,最大功率为 7.8kW(约 10.6 匹)/8000rpm,最大扭矩为 9.5N·m/6500rpm,足以应对日常城市通勤中的起步、超车等场景。 新车整备质量为 119kg,油箱容量为 7L,综合工况油耗约为 2.5L/100km,续航里程接近 300 公里。 在安全与实用配置方面,Django Compact 前后均配备碟刹制动系统,中高配版本提供了双通道 ABS 防抱死制动系统,这在同级别合资 125 踏板车型中较为少见。 此外,Django Compact 还采用了前后 12 寸轮毂,有助于提升车辆在中高速行驶时的稳定性和通过性。其他便利性配置包括 760mm 的亲民座高、宽大的平踏板、可容纳四分之三盔的座桶空间,ABS 尾箱版还附带 42 升的原厂尾箱。 在智能与便利功能上,Django Compact 也紧跟时代步伐。ABS 版及以上配备了无钥匙启动系统,遥控钥匙的造型和手感与标致汽车钥匙类似。IT之家注意到,这款车配备了彩色高清 LCD 仪表,可显示油耗等信息,车把下方还设有 USB+Type-C 双快充接口,为移动设备充电提供了便利。

V2EX - 技术 · 2026-05-21 12:44:30+08:00 · tech

一、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 等。 四、DTO 组装页面:声明式定义页面结构 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 同时承担四项职责: 参数校验 :Zod 原生能力 OpenAPI 文档 :自动从 Schema 生成 Swagger 文档 渲染配置 :通过 ZovaRender 声明字段在 Table/Form 中的渲染方式 数据序列化 :Response 的脱敏处理、字段过滤 一份 Schema 定义,四处复用,从根源上消除了前后端字段定义不一致的问题。 七、总结 VonaJS 的核心价值很明确: 用 DTO 驱动 CRUD 动态渲染,把中后台开发从"写页面"变成"配字段"。 过去你需要在 Entity 里定义字段、在 DTO 里定义校验、在前端组件里定义表格列和表单控件——三个地方维护同一组字段。现在,Entity 中一行 ZovaRender.cell() 或 ZovaRender.field() 就能同时搞定显示和交互,DTO 中几个 block 就能组装出完整的页面结构。 如果你的项目是中后台系统、管理后台、数据驱动的 Web 应用,VonaJS 值得一试。 在线演示( Web ): https://cabloy.com 在线演示( Admin ): https://cabloy.com/admin GitHub: https://github.com/vonajs/vona

V2EX - 技术 · 2026-05-21 12:44:30+08:00 · tech

一、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 等。 四、DTO 组装页面:声明式定义页面结构 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 同时承担四项职责: 参数校验 :Zod 原生能力 OpenAPI 文档 :自动从 Schema 生成 Swagger 文档 渲染配置 :通过 ZovaRender 声明字段在 Table/Form 中的渲染方式 数据序列化 :Response 的脱敏处理、字段过滤 一份 Schema 定义,四处复用,从根源上消除了前后端字段定义不一致的问题。 七、总结 VonaJS 的核心价值很明确: 用 DTO 驱动 CRUD 动态渲染,把中后台开发从"写页面"变成"配字段"。 过去你需要在 Entity 里定义字段、在 DTO 里定义校验、在前端组件里定义表格列和表单控件——三个地方维护同一组字段。现在,Entity 中一行 ZovaRender.cell() 或 ZovaRender.field() 就能同时搞定显示和交互,DTO 中几个 block 就能组装出完整的页面结构。 如果你的项目是中后台系统、管理后台、数据驱动的 Web 应用,VonaJS 值得一试。 在线演示( Web ): https://cabloy.com 在线演示( Admin ): https://cabloy.com/admin GitHub: https://github.com/vonajs/vona

V2EX - 技术 · 2026-05-21 11:59:23+08:00 · tech

一、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 等。 四、DTO 组装页面:声明式定义页面结构 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 同时承担四项职责: 参数校验 :Zod 原生能力 OpenAPI 文档 :自动从 Schema 生成 Swagger 文档 渲染配置 :通过 ZovaRender 声明字段在 Table/Form 中的渲染方式 数据序列化 :Response 的脱敏处理、字段过滤 一份 Schema 定义,四处复用,从根源上消除了前后端字段定义不一致的问题。 七、总结 VonaJS 的核心价值很明确: 用 DTO 驱动 CRUD 动态渲染,把中后台开发从"写页面"变成"配字段"。 过去你需要在 Entity 里定义字段、在 DTO 里定义校验、在前端组件里定义表格列和表单控件——三个地方维护同一组字段。现在,Entity 中一行 ZovaRender.cell() 或 ZovaRender.field() 就能同时搞定显示和交互,DTO 中几个 block 就能组装出完整的页面结构。 如果你的项目是中后台系统、管理后台、数据驱动的 Web 应用,VonaJS 值得一试。 在线演示( Web ): https://cabloy.com 在线演示( Admin ): https://cabloy.com/admin GitHub: https://github.com/vonajs/vona

v2ex.com · 2026-04-27 10:52:02+08:00 · tech

一款 Python 语言 Django 框架 DDD 脚手架,适合快速搭建项目 一个开箱即用的 DDD (领域驱动设计) Python 脚手架,基于 Django 5 + DRF + drf-spectacular ,包含双数据库、统一响应、全局异常与事件驱动示例。 这是什么 Django-DDD 是一个精心打造的 Python 语言 DDD 工程脚手架,帮你快速搭建符合 DDD 精髓的 Web 服务。项目内置用户与订单示例、领域事件与内存总线、多数据库路由、统一响应与全局异常处理,适合作为团队工程模板,给 AI 提供代码规范参考。 为什么要用 DDD ? 很多人认为 Python 没必要用 DDD ,毕竟它和 Ruby 、JS 一样轻巧灵活,Django 自带的 MTV ( Model-Template-View )拿来就能写。确实,大多数场景下"数据驱动"的 Django 模式完全够用。 工程化无非是把接口处理、业务逻辑、数据处理区分开,让各部分各司其职,方便维护和扩展。DDD 相对更适合中大型项目:如果项目有几十个模块、上百个接口,用 DDD 设计会更合适;模块少、接口不多的话,简单分层就够了。 项目做大以后,会遇到三个常见问题: 业务规则散落在各处 :View 里判断状态,Model 里写校验,Service (如果有)里再来一次 Model 太胖 :既承担持久化,又承担业务逻辑,测试必须起 Django 才能跑 强耦合 Django :业务代码离不开 django.db.models ,替换存储引擎几乎是重构 本脚手架遵循 务实 DDD :抓住精髓(分层 + 领域模型 + 仓储抽象 + 领域事件),不死守概念(不做 CQRS 、不强制事件溯源、不要求每个业务都建聚合)。总之,是否采用 DDD 和语言无关,只跟业务规模有关。 源码地址: https://github.com/microwind/design-patterns/tree/main/practice-projects/django-ddd