Releases: jcoder-stack/abp-react-start
Release list
v0.4.0
设计系统换回 shadcn 原版,只保留自家的蓝;SheetForm 重做;修掉一个让 vite dev 下所有管理页停在骨架屏的老问题。
修复
vite dev 管理页不水合(#21)。tls-trust.ts 在 proxy 子路径顶层静态 import "node:fs";starter 的 @/auth 桶文件把路由守卫和服务端 runtime 放在一起导出,于是每个页面都把 proxy 拖进客户端模块图,浏览器里整棵 @/auth 求值失败,根组件永远水合不了。自 0.1.2 起就是这样,SSR 骨架屏掩盖了它。
现在 node 内建模块在调用时经 process.getBuiltinModule 取——installExtraCa 只在服务端被调,没有该 API 的运行时(Node < 22.3)本来也没有 CA API,照旧返回 "unsupported"。CULTURE_COOKIE 挪到 auth 子路径(proxy 继续 re-export,公共 API 不变)。examples/starter 客户端构建的四条 "externalized for browser compatibility" 警告随之清零。
主题:退回 shadcn 原版 + 自家的蓝
app-theme.css 381 → 300 行。shadcn blue preset 原样采用,相对原版每主题只差三个 token:--primary、--primary-foreground、--ring。自家这支蓝在暗色下变亮(0.55 → 0.57),一个值同时能当填充、链接与焦点环,两个主题都过 WCAG AA;preset 那支在暗色下压在近黑页底上只有 2.24:1。
保留的只有原版没覆盖的东西:CJK 字体回退栈、--text-sm 13px(唯一的密度杠杆)、status-* / brand-* 语义色、降低动效偏好块。删掉的:自定义刻度、510/590 字重、逐档负字距、四级 surface 阶梯、五层模态阴影、整个 :lang(zh) 覆盖——它们修的都是自定义排版自己造出来的问题。账在 docs/theme-spike.md。
SheetForm
- 标题领衔记录:新建态给宾语(「新建图书」),编辑/查看态以记录本身为标题、次级标识(邮箱、作者 · 日期)放副标题。
useAbpSheet新增title/subtitle解析器。 - 四条关闭路径(Esc、右上角 ×、遮罩、取消)收敛到一道闸门:有未保存改动先问「放弃未保存的修改?」。
useAbpSheet上报dirty。 - 标「选填」而非红星,由表单层决定(
OptionalMarkscontext):录入表单多数必填、标少数派;查询表单全是选填、不标。红色留给错误。 - 查看态渲染成定义列表(
divide-y rounded-lg border bg-card、值右对齐、行高与表格同源),不再是一排禁用输入框。每个字段给出自己的display;未接的字段回退到禁用控件,不会碎。 - 说明文字放标签下、控件上;控件下方永远留给错误。底栏单行右对齐,取消用 ghost。
表格
- 行高改为声明值(
h-10/ 紧凑h-8),此前由行尾操作按钮撑出,同一档里带菜单的行比纯文本行高 12px。 col.bool默认渲染普通文字(否为 muted),只有传了trueStatus才出徽章。徽章的含义是「处于某状态」,isDefault/isPublic是属性——此前同一个「是」在角色页是蓝色、在用户页是绿色。用户页isActive仍显式传success。
文档
DESIGN.md 785 → 236 行。token 数值只在主题文件里,文档讲「相对 shadcn 原版改了什么、为什么」、原版没覆盖的产品约定、以及每条定制层规则的一行理由。四份分析文档随行:docs/design-review.md、sheetform-review.md、sheetform-design.md、theme-spike.md。
升级注意
useAbpSheet的title/subtitle可选,不传维持「新增 / 编辑 / 查看」。col.bool的默认观感变了:想保留徽章,显式传trueStatus。required-mark.tsx改名optional-mark.tsx;form块新增alert-dialog依赖,jc-abp add form会自动带上。- 若你覆盖过
app-theme.css里的自定义刻度类(text-2xs之外的自定义档)或:lang(zh)规则,它们已不存在。
v0.3.0
代理层现在收得下 multipart 上传与裸字节直传。ABP [FromForm] 的导入端点此前拿到的是一个没有正文的请求。
修复
AbpProxyRequest.body 的类型是 string,而 abpFetch 用 typeof init?.body === "string" ? init.body : undefined 收窄调用方的 body——传 FormData 不报错,静默变成空 body。四层链路(abpFetch → abpRequestFn 的 zod schema → AbpProxyRequest → proxy.send)都把 body 钉在 string 上。
新增
AbpProxyRequest.body 拓宽为 string | Uint8Array | ArrayBuffer | FormData,新导出类型 AbpProxyBody。
判据是可重发而不是「是不是二进制」:代理的核心能力是 401→刷新→重放与幂等重试,两者都要把同一个 body 再发一次。ReadableStream 刻意排除,并在客户端与代理两处显式拒绝——它只能消费一次,收下它会让那两条路径静默退化成「重放一个空正文」。
新增 server function abpUploadFn,二进制以原生 multipart 过桥,不进 JSON 载荷。这是量过之后的选择:
| 走法 | 实测 |
|---|---|
Uint8Array 交给 seroval |
1MB 反序列化抛 SerovalDeserializationError(512KB 尚可) |
| base64 成字符串 | 10MB 能过,但文件变 13.3MB 字符串、两端各解析一次 JSON |
| 原生 multipart | 文件字节全程不进 JSON |
TanStack Start 要求整个 payload 就是 FormData,混不进 JSON 对象,故为第二个 server fn。文本那条路径的 zod schema 一个字未动,所有既有 CRUD 调用不受影响。
createAbpProxy 新增 maxBodyBytes(默认 10MB),在任何网络动作之前拦截。
一个会让人查很久的坑
body 是 FormData 时,调用方的 content-type 现在会被丢弃。它带的是调用方那份 FormData 的 boundary,而传输层重新编码时会生成新的;透传旧值会让上游按错的分隔符解析,一个字段都读不出来——症状是「请求到了、参数全空」,不是报错。content-type 本就在转发白名单内,所以这条不处理必然踩。
使用
调用方无需改动写法,abpFetch 对外仍是 typeof fetch:
const form = new FormData();
form.append("file", file, file.name);
await abpFetch("/api/app/books/import", { method: "POST", body: form });裸字节直传同样可用(content-type 按需自带):
await abpFetch("/api/app/files/1", {
method: "PUT",
headers: { "content-type": "application/octet-stream" },
body: bytes,
});升级须知
@jcoder-stack/registry 的分发物变了:jc-abp add app-shell 装到的 src/api/abp-fetch.ts 是新版,且 jc-abp init 会多铺一个 src/auth/upload-payload.ts。已有项目要吃到这个能力需重新 add / init。AbpProxyRequest.body 是加法拓宽,既有调用无需改动。
验证
97 个测试文件 / 836 用例(新增 14 条)、typecheck(含 @ts-expect-error 类型契约)、lint、三处 build 全通过;registry 分发物已随块源码重建。
v0.2.1
纯依赖升级,跟住 TanStack 上游。零源码改动,无破坏性变更。
依赖升级
| 包 | 前 | 后 |
|---|---|---|
@tanstack/react-table |
9.1.2 | 9.2.4 |
@tanstack/react-router |
1.170.24 | 1.170.32 |
@tanstack/react-start |
1.168.41 | 1.168.49 |
@tanstack/react-query |
5.101.2 | 5.102.8 |
@tanstack/react-form |
1.33.2 | 1.33.5 |
@tanstack/react-router-ssr-query |
1.167.1 | 1.167.2 |
router-plugin / router-cli |
1.168.28 / 1.167.21 | 1.168.35 / 1.167.33 |
react-devtools / devtools-vite |
0.10.8 / 0.8.1 | 0.10.12 / 0.8.5 |
对使用者的实际影响
registry.json 里块依赖清单的 react-table 从 9.1.2 提到 9.2.4——这是 jc-abp add 写进你项目的版本,所以 data-table / abp-table / admin-pages 三个块装出来会带新版。已有项目不受影响,重装或新装才会拿到。
升级真正吃到的是 router 那段,每次导航都走:
- 1.170.28 重访已解析的代码分割路由时复用已加载组件,不再多闪一次 pending;同版避免了 SSR/水合期把可能渲染
<html>的根组件包进 Suspense - 1.170.30 pending UI 跨保留路由不丢
- 1.170.32 reload 期间 context 不被清掉
react-table 9.1.2 → 9.2.4 对本项目是空转:两个补丁只碰层级 flatRows 排序与 worker 行模型,而 table-core.ts 只注册 sorting / pagination / visibility / selection,进不到执行路径。升它是为了跟住上游。
@tanstack/react-query 5.102.0 修了 queryOptions 返回类型泄漏内部 tag symbol 导致声明文件生成失败——对要出 .d.ts 的 @jcoder-stack/abp-react 是白捡的。
检查过但未踩到的上游移除
Link上未文档化的isTransitioning状态与data-transitioning属性(router 1.170.26 移除)——全仓库含 CSS 无引用- Query 的实验性 render-time prefetch 与查询结果上的
promise(5.102.0 移除)——未使用 - router 1.170.26 新增「在 pending/error/not-found 组件里渲染
Outlet会告警」——shell-boundary与section-boundary都没有Outlet
@tanstack/react-form 1.33.5 仍未修复 submit 字段级错误不自清的死锁,useAppForm 的 clearServerSubmitErrors patch 保留;中英两份 forms 指南的版本事实已同步。
维护
scripts/regenerate-example.sh 的重放 pin 表随 starter manifest 同步(docs/releasing.md 列为升级依赖时的必改项);漏改会让下次重放把 TanStack 依赖悄悄打回旧版,而 bun install 与测试都看不见。
v0.2.0
依赖升级到 TanStack Table v9,新增列预设,表格搜索与勾选不再触发整表重渲染。
依赖升级
@tanstack/react-table8.x → 9.1.2:块内部改用useTable+tableFeatures特性注册@tanstack/react-router→ 1.170.24,@tanstack/react-start→ 1.168.41
新增
- 列预设
createAbpColumns<TData>()(components/abp/table/column-presets.tsx):text/date/money/enum/bool五个预设,外加accessor/display/group透传与columnHeader(label)。label 传词条 key 而非译文,列数组可以放到模块级 - 按表声明的
columnMeta槽(table-core.ts的TableColumnMeta+baseFeatureMap),取代全局declare module增广;下游可传columnMeta: {} as TableColumnMeta & { mine: X }追加自己的键 - 表实例暴露
getSelectedRows/clearSelection/keepSelected/SelectedCount/selectable docs/releasing.md/releasing.en.md:发版流程publish-smoke.sh增加跨包依赖区间检查;新增 registry 与 starter 块源码一致性测试
性能
搜索输入与行勾选不再重渲染整表。搜索状态下沉到 toolbar,勾选状态交还给表实例并按行 Subscribe,useTable 宿主选择器排除 rowSelection。100 行实测:
| 交互 | 之前 | 之后 |
|---|---|---|
| 输入搜索 | 42.3 ms/commit | 4.3 ms/commit |
| 勾选一行 | 30.2 ms/commit | 3.7 ms/commit |
两项均不再随行数增长。
修复
- 第 1 页切换查询条件(搜索、排序、筛选)后残留旧勾选,批量删除会作用到上一批数据
- 文档与 TSDoc 不再指向已废弃的
table.store;修正CellTableInstance的 API 指引(该类型上没有state/Subscribe)
破坏性改动
Table 块的勾选与搜索 API 有变化,selectable: true 是所有批量能力的前提。迁移对照见 docs/guides/abp-table.md。
升级
bun add @jcoder-stack/abp-react@0.2.0
bun add -D @jcoder-stack/cli@0.2.0 @jcoder-stack/registry@0.2.0
bunx jc-abp add abp-tablev0.1.3
两个真机实测暴露的 bug,加一整套错误处理收口。全部改动都先在真实浏览器里复现、修复后再验证。
修复:Sheet 里的 combobox 点不中(影响所有抽屉表单的下拉选择)
编辑用户时无法选角色、编辑 book 时无法选作者——下拉打开、选项可见,鼠标点了没反应(键盘反而可用)。根源是 Radix modal(Sheet)打开时把 body 设为 pointer-events: none 且只恢复自己的内容,而 Base UI 的 combobox 弹层 portal 在 dialog 之外,整条链没有一层恢复。主题层一条规则修复:
[data-slot="combobox-content"] { pointer-events: auto; }这类缺陷 jsdom 表达不出来(合成事件直达元素),组件测试全绿——只有真浏览器能抓到。
修复:/forbidden 在生成的应用里是 404
requirePermission 无权限时跳 /forbidden,但这个页面此前只存在于参照应用,从未随 app-shell 分发——权限守卫的跳转目标是个 404。现已分发;403/404 页同时加上注销链接:困在无权限页面的用户通常需要换账号,只给「返回首页」是死路。
新增:控制台首页 /home——控制台入口不再 403
「进入控制台」此前硬编码指向 /identity/users,没有 Identity 权限的用户一进门就 403;侧栏「Home」还会跳出控制台回落地页。现在:
- 新增
_layout/_authed/home.tsx:登录即可见、零权限要求——欢迎语(带用户名)、标语、特性卡,词条全部就位,按提示改成你自己的首页即可 - 「进入控制台」、侧栏「Home」、forbidden 的「返回首页」全部指向
/home - 营销落地页继续独占
/
新增:错误边界成体系
- 路由级默认边界:
createRouter带上defaultErrorComponent/defaultNotFoundComponent——你新增的业务页出错时,错误渲染在页面自己的位置、侧栏保持在位,不必每页手动挂errorComponent(某页要定制时显式声明依然优先) SectionBoundary(新组件):包住页面里的一个区块,渲染抛错只替换该区块,兄弟区块照常;默认 fallback 词条化 + 重试,可自定义- 静态兜底:错误页自身再抛错时(唯一出场时机),白屏降级为零依赖的静态提示,而不是整树卸载
改进:表格工具条
刷新 / 导出 / 密度 / 列 / 排序 / 筛选六颗图标钮都有了 tooltip。刷新的语义顺带说明:带条件刷新(对当前查询含筛选重新请求),重置在筛选面板的 Reset 上。
升级
bun add @jcoder-stack/abp-react@0.1.3
bun add -D @jcoder-stack/cli@0.1.3 @jcoder-stack/registry@0.1.3已初始化的项目按需取块的新版本(会覆盖你对块文件的本地改动,注意先提交):
npx shadcn@4.13 add node_modules/@jcoder-stack/registry/public/r/app-shell.json --overwrite
npx shadcn@4.13 add node_modules/@jcoder-stack/registry/public/r/data-table.json --overwritecombobox 修复在主题 css 里——已有项目在 src/styles.css 追加上面那条 [data-slot="combobox-content"] 规则即可,或整体替换为新版 app-theme.css。router 的默认边界两行参照 README 的 router 说明手动加上(init 只在初始化时接线)。
v0.1.2
这个版本解决一件事:本地起步的头三堵墙,现在每一堵都有清晰的出路——自签证书、后端地址配置、.env 没填全。顺带把 npm 包文档换成英文,并新增全套英文文档。
自签证书:.env 一行解决
本地 ABP 的自签证书在系统钥匙串里受信,但 Node 不读钥匙串——此前 gen 和 dev 都要在启动命令上带 NODE_EXTRA_CA_CERTS(它没法写进 .env,Node 只在启动时读一次)。现在:
dotnet dev-certs https --export-path ~/.aspnet-dev.crt --format PEM# .env
AUTH_EXTRA_CA_FILE=~/.aspnet-dev.crtauth 运行时与 gen 都会在第一笔上游请求前把这张证书追加进进程默认 CA(tls.setDefaultCACertificates,运行时 API,需要 Node ≥ 22.15),其余站点照常校验。旧运行时(更早的 Node、Bun 直跑服务端)自动降级为日志提示回退方案。
init 引导后端配置
$ npx jc-abp init
ABP backend URL (e.g. https://localhost:44316, Enter to skip):
- 一个地址派生三处:
AUTH_ISSUER、AUTH_ABP_BASE_URL、abp.api.config.ts的 swaggerinput .env自动生成(自.env.example派生),AUTH_SESSION_SECRET随机生成——不再手跑openssl rand- 回车跳过、非 TTY 自动跳过、
--backend <url>供脚本/CI - init 从不连接后端,后端没启动不影响初始化;收尾做一次 3 秒探测,只提示不阻断——连不上提醒 gen 前启动后端,证书不受信直接指出该配
AUTH_EXTRA_CA_FILE(顺带证明地址没填错)
报错全部指向下一步
| 场景 | 之前 | 现在 |
|---|---|---|
.env 缺失/留空启动 |
ZodError 堆栈 | auth env is missing or invalid: AUTH_CLIENT_ID (not set). Fill these in the project's .env … |
| 运行时证书不受信 | fetch failed |
指名 AUTH_EXTRA_CA_FILE 的完整说明,且不再徒劳重试 |
| 运行时后端连不上 | fetch failed(重试后) |
cannot reach the ABP backend (ECONNREFUSED) … The backend is not running, or AUTH_ABP_BASE_URL in .env points at the wrong place |
gen 连不上后端 |
fetch failed |
按证书/不可达/404 分类,各给出路 |
CLI 修复与英文化
- 修复:交互终端下 init 会挂在 npx 自己的下载确认上(
Ok to proceed?被进度输出淹没,看起来像挂死;非 TTY 从不触发,所以此前未暴露)。npx 调用现在带-y。 - CLI 的全部用户可见输出(usage、交互提问、进度行、错误信息、收尾指南)改为英文;
app-env.d.ts声明的变量名与真实契约(AUTH_*)对齐。
文档
- npm 三个包的 README 与 description 换成英文(npmjs.com 直接渲染)
- 仓库新增英文全套:
README.en.md+ architecture 与全部四份 guides 的.en.md版本,中英互链 .env.example占位端口与 config 模板统一为 44316
升级
bun add @jcoder-stack/abp-react@0.1.2
bun add -D @jcoder-stack/cli@0.1.2 @jcoder-stack/registry@0.1.2无破坏性改动。已初始化的项目不需要重跑 init;升级 @jcoder-stack/abp-react 即获得运行时的全部新报错与 AUTH_EXTRA_CA_FILE 支持(该变量按需加进你的 .env 即可)。
v0.1.1
本地 ABP 后端用自签证书时,bun run dev 打开首页得到 500,页面上只有一句 fetch failed。这个版本让它说人话,并把文档里缺的那半边补上。
为什么会这样
TLS 那一跳的客户端是 Node,不是浏览器:
浏览器 ──HTTP──> localhost:3000 (TanStack Start / Node)
└──HTTPS──> localhost:44316 (ABP) ← TLS 在这里
dotnet dev-certs https --trust 把证书装进系统钥匙串,浏览器与 curl 因此放行。Node 不读钥匙串,只校验自己内置的 CA 列表,于是拒绝 CN=localhost 这张自签证书。
| CA 来源 | 结果 | |
|---|---|---|
| curl / 浏览器 | 系统钥匙串 | 200 |
| Node | 内置 Mozilla 列表 | DEPTH_ZERO_SELF_SIGNED_CERT |
Node + NODE_EXTRA_CA_CERTS |
内置列表 + 这一张 | 200 |
怎么办
dotnet dev-certs https --export-path ~/.aspnet-dev.crt --format PEM
NODE_EXTRA_CA_CERTS=~/.aspnet-dev.crt npx jc-abp gen
NODE_EXTRA_CA_CERTS=~/.aspnet-dev.crt bun run dev这个变量不能写进 .env。 Node 在进程启动时读它,dotenv 加载时已经太晚——写了不生效,且没有任何提示。放命令上,或 export 进 shell profile。
NODE_TLS_REJECT_UNAUTHORIZED=0 同样能跑通,写进 .env 也有效,但它关掉的是整个进程对所有主机的证书校验。临时试可以,别留在会被复制到服务器的文件里。
改了什么
@jcoder-stack/abp-react —— 代理识别 OpenSSL 的证书信任失败码后:
- 不再重试。 证书不受信是确定性失败,原先的两次重试只是把同一次握手重做两遍,还把原因埋在退避后面。
- 给出可执行的说明,指名
NODE_EXTRA_CA_CERTS与被拒的 origin,原始错误保留在cause上。
@jcoder-stack/registry —— .env.example 补上运行时的这条要求,并写明上面两个变量的差异。
@jcoder-stack/cli —— init 结束时打印的接线提示同样覆盖 dev,不再只提 gen。
文档 —— 初始化指南新增「自签证书的本地后端」一节,排障表补两行,starter README 与 .env.example 同步。此前全部 5 处 TLS 说明都只在 gen 的语境下,运行时只字未提,这正是踩坑的直接原因。
升级
bun add @jcoder-stack/abp-react@0.1.1
bun add -D @jcoder-stack/cli@0.1.1 @jcoder-stack/registry@0.1.1没有破坏性改动。已经装好块的项目不需要重跑 init——想拿新的 .env.example 注释,从 node_modules/@jcoder-stack/registry/auth/root/.env.example 对照即可。
v0.1.0
ABP React Start 的第一个版本。给 ABP 后端配一套现代 React 前端:OIDC 登录经 BFF 代理、加密 cookie 会话、权限、多租户、本地化,以及一组直接读 ABP 协议的 CRUD 原语。
一条命令落地一个能跑的应用,然后它就退场——给你的是源码,不是运行时依赖。UI 全在你自己的仓库里,改它不需要绕过任何抽象。
装什么
npx @tanstack/cli create my-app && cd my-app
bun add @jcoder-stack/abp-react
bun add -D @jcoder-stack/cli @jcoder-stack/registry
npx jc-abp init
# 改 .env 与 abp.api.config.ts 指向你的后端
npx jc-abp gen && bun run devjc-abp init 会播种基线配置与主题、落认证外壳、按依赖序装齐 13 个块、接好 __root.tsx 与 router.tsx、生成路由树。装完是 85 个源码文件在你的 src/ 下。
三个包
| 包 | 作用 |
|---|---|
@jcoder-stack/abp-react |
运行时内核。子路径导出 core / auth / proxy / react / router / permissions / i18n / logger |
@jcoder-stack/cli |
jc-abp init / add / gen(命令名不带 scope) |
@jcoder-stack/registry |
13 个 shadcn 块与认证外壳的 copy-in 源,装成 devDependency |
块:abp-layout、abp-login、app-shell、data-table、form、combobox、date-picker、tree、abp-crud、abp-table、abp-sheet、abp-permission-sheet、admin-pages(用户 / 角色 / 租户 / 设置 / 个人资料五页)。
装一个块会把它的前置块一并带上,不必自己排顺序。
前置
- Node ≥ 18(
gen读.ts配置需要 Bun 或 Node ≥ 22.18) - bun 或 npm。npmrc 里配了
allow-scripts时请用 bun:npx 会把它注入子进程,npm 拒绝项目级安装,shadcn 装块依赖必然失败。init检测到这种情况会拒绝开工并说明原因 - 一个 ABP 后端。
gen读它的 swagger 生成 API 客户端 - peer:
@tanstack/react-router ^1.170.18、react ^18 || ^19、zod ^4
外部 CLI 锁在已验证的 minor:shadcn@4.13、@tanstack/router-cli@1.167。
已知约束
@tanstack/react-table 用的是 9.0.0-beta.59。 data-table 块依赖 v9 才有的 tableFeatures / rowSortingFeature 等 API,块里已钉死这个版本。v9 正式发布前,升级它会编译失败。
@jcoder-stack/registry 必须装在项目根。 块之间用 ./node_modules/@jcoder-stack/registry/public/r/*.json 互相引用,这条路径按消费项目的根解析。npm 与 yarn classic 的 workspace 会把它提升到 workspace 根,成员目录下就找不到——那种项目请按依赖表自己排序逐块装。jc-abp init 不受影响,它自己解析真实路径。bun 与 pnpm 会在成员目录建符号链接,无此问题。
CRUD 相关的块要先跑 jc-abp gen。 abp-crud / abp-table / abp-sheet / abp-permission-sheet / admin-pages 都 import @/api/*,那是 gen 的产物。装完不跑 gen,编译不过。
本地后端用自签证书时 gen 会报 fetch failed。 Node 不读系统钥匙串,证书在钥匙串里受信也没用。加 NODE_TLS_REJECT_UNAUTHORIZED=0 即可。
.env.example 里的 AUTH_CLIENT_ID 与 AUTH_ISSUER 是占位。 client 必须是你后端 OpenIddict 里已注册的那一个,回调地址也要登记在它名下;scope 里还要带后端的 API 资源 scope,漏了登录能过但每个管理页都是空的。.env.example 的注释里给了探测方法。
权限名显示成原始 key 是后端的本地化缺口。 权限树用的是后端返回的 displayName,ABP 查不到词条就原样返回 key。补你后端对应资源的本地化文件即可。
安全
会话密封、OIDC 握手、BFF 代理是本项目自己承担的部分,漏洞请走 GitHub 的私密报告(Security → Report a vulnerability),不要开公开 issue。权限助手只管界面显隐,真正的授权在 ABP——前端能被绕过是设计如此。详见 SECURITY.md。
文档
从零初始化 · 按需装块 · 列表页与 CRUD · 表单四层 · 架构
参照应用 examples/starter 就是这套流程的产物,由脚本重放「脚手架 → 装包 → init → gen」得到,既是活文档也是端到端回归。