t3code实战:用schema驱动类型安全的前后端代码生成
t3code 不是又一个.gitignore生成器也不是那种只帮你搭个空壳就撒手不管的脚手架。我第一次听到这个名字时还以为是某个 CLI 工具的版本号后来真正上手才发现它想做的事情是把 TypeScript 的类型安全从后端一路推到前端的契约层让接口文档、数据库模型、前端请求代码全部长在同一套类型定义上。这篇就聊聊我在实际项目里用它从零搭出一个全栈小应用的全过程包括踩过的坑和最后沉淀下来的一套习惯新手可以直接照抄老手可以拿去对比自己手头的工程化套路。1. 这是干什么的t3code 的核心设计思路1.1 从命名到定位它到底解决什么问题先说结论我把 t3code 理解为一套以类型为契约的全栈项目生成器。它不像 create-react-app 那样只负责前端也不像 Nest CLI 那样只盯着后端而是把两边的公共部分——数据模型、接口出入参、枚举常量、校验规则——集中到一份 schema 里然后通过代码生成器把这份 schema 翻译成前端 TS 类型、后端路由类型、数据库表结构定义和 API 客户端封装。这样做最直接的好处是改动一个字段重新跑一次生成命令前后端所有相关类型同步更新再也不用跑去翻接口文档确认某个字段到底叫createdAt还是create_time。我在自己的项目里试了一周之后最大的感受就是类型定义不再是写的时候顺便标注一下的附属品而是整个项目一开始就要定下来的骨架。它解决的痛点很明确传统前后端分离项目里类型在 API 边界是断的。后端改了字段名前端编译期根本发现不了只有运行的时候接口报错或者页面白屏才意识到出了问题。t3code 给这张容易断的网补上一层从数据模型到前端 UI 全链路可推导的类型约束让前端拿到的不只是某个 JSON而是一份完整的类型说明书。1.2 它和常见脚手架到底差在哪里拿日常的脚手架对比一下就很清楚了。普通的create-xxx-app给你一个能跑的模板里面 src 目录、配置文件、启动命令都齐了但模板和数据模型没有任何关系——你后续写的业务类型还是要自己定义、自己同步、自己维护。t3code 的思路是把模板和数据 schema合并在一起模板负责生成工程结构schema 负责生成业务类型两者缺一不可。我再打一个生活化的比方普通脚手架像给你一套毛坯房户型虽然有了但里面的强弱电点位、水管走向都得你自己重新设计t3code 更像给你一套 BIM 模型你改了一堵墙的位置相关的管线、窗户、承重数据会自动跟着变。对个人项目和中小团队来说这种改一处、全链路跟着改的体验非常值钱因为它把最容易出低级 bug 的环节变成了编译期可见的错误。1.3 适合谁来用不适合谁如果你满足下面任意一条我建议你认真考虑它你是一个人在写前后端全栈项目需要快速把想法落地成可运行的东西你的团队前后端分离但共享一套 schema 文档接口经常变动导致联调成本高你已经受够了手写 API 客户端类型和重复的 zod 校验代码。反过来如果你的项目只是纯前端展示页没有后端交互或者后端是遗留的 Java 系统不方便引入新的 schema 生成链路又或者你极度排斥代码生成这种多一层抽象的思路——那你暂时不需要用它硬上反而增加心智负担。我个人的判断是工具是为了减少重复劳动不是为了让你陷入更复杂的构建流程选择前先掂量一下自己的场景是否匹配。2. 核心模块拆解一次代码生成是怎么串起来的2.1 单一数据源一份 schema 管住所有类型t3code 的整个设计都围绕单一数据源展开。你在schema/index.ts里定义核心模型比如用户、文章、订单然后用类似 zod 的 schema 语法描述字段类型和校验规则。这里有一个非常关键的设计选择它没有发明一套全新的 DSL而是直接用 TypeScript zod理由很简单——新 DSL 需要额外学习成本而且不好做类型推导。我实际写的模型长这样import { z } from zod; export const User z.object({ id: z.string().uuid(), email: z.string().email(), name: z.string().min(2), role: z.enum([admin, editor, reader]), createdAt: z.date(), }); export type User z.infertypeof User;注意到没有这段代码既是运行时校验规则又是静态类型定义还是后续生成数据库迁移和 API 客户端的数据来源。t3code 在t3code.config.ts里读取这些 schema然后走一条完整的生成管线。这里我想强调一个容易被忽略的细节定义 schema 时不要只图省事字段名和约束条件一定要想清楚后再写因为后面所有地方都会引用它改名的成本会随着项目膨胀迅速放大。2.2 生成器链路从 schema 到前后端代码的完整流程代码生成不是魔法本质上是模板 数据的渲染过程。t3code 内部大致分四个阶段解析 schema 文件把所有模型、枚举、输入输出类型读进内存转成一个 AST抽象语法树。生成后端产物数据模型层代码、数据库 migration、路由处理器所需的类型定义、参数校验中间件用的 zod schema。生成前端产物API client封装好的请求函数、React Query 的 hook、页面里直接引用的业务类型。生成文档和联调产物一份 OpenAPI 风格的接口描述文件以及前后端共用的 TypeScript 类型包。整个链条里AST 的稳定性最关键。你写的 schema 千奇百怪但经过解析之后应该变成一棵结构稳定的语法树后面的模板才能可靠地遍历它。如果你自己也想写类似工具一定要把注意力放在这一层的设计上别一上来就忙着写输出模板。2.3 运行时依赖为什么能做到很小很多代码生成工具的通病是产物依赖一堆运行时库生成出来的代码看着挺全实际项目里体积感人。t3code 的做法比较克制生成的前端 client 只依赖zod和fetchReact Query 的 hook 需要你单独安装tanstack/react-query但生成的代码本身不绑定任何重度框架。也就是说你可以把它生成的 client 用在 Vue、Svelte 甚至原生 TS 项目里灵活性很高。我对比过这种做法的实际收益一个只有用户和文章两个模型的小项目生成的前端产物大概 20KB 左右gzip 之后不到 7KB对于页面性能几乎无感。如果你后续不想要 zod 了也可以配置成只生成 TS interface 的轻量模式但这样的话运行时校验就得靠后端二次保证我个人还是建议保留 zod 版本因为前端在拿到数据时多做一次校验往往能在 UI 层提前拦截很多脏数据。3. 实操从零开始用 t3code 搭一个全栈小项目3.1 环境准备与初始化先说环境要求我当前跑通的版本用的是 Node 18包管理器建议直接用 pnpm因为它对 monorepo 场景和依赖安装速度都更友好。如果你机器上还没有 pnpm一行命令装好npm install -g pnpm然后创建一个新项目pnpm create t3codelatest my-first-t3code cd my-first-t3code pnpm install这里你会看到生成出来的目录结构大致长这样apps/ web/ # 前端 React 应用 server/ # 后端 Hono 服务 packages/ api-client/ # 生成的 API 客户端 schema/ # 核心 schema 定义我第一次看到这个结构其实有点惊讶因为它默认就把前后端拆成两个应用而不是全部塞进一个src。这个安排挺合理前端和后端的依赖本质上是隔离的后续分别部署也更方便。初始化过程中它还会问你数据库选型我这边选的是 SQLite因为本地联调不需要额外起服务后面如果上生产再换 PostgreSQL 也基本不费劲。3.2 定义你的第一个模型一个用户加一篇文章进入packages/schema/src/index.ts把我前面提到过的 User 模型贴进去再顺手定义一个文章模型import { z } from zod; export const User z.object({ id: z.string().uuid(), email: z.string().email(), name: z.string().min(2), role: z.enum([admin, editor, reader]), createdAt: z.date(), }); export const Post z.object({ id: z.string().uuid(), title: z.string().min(1), content: z.string().min(10), published: z.boolean().default(false), authorId: z.string().uuid(), createdAt: z.date(), }); export type User z.infertypeof User; export type Post z.infertypeof Post;然后运行pnpm codegen它会自动完成几件事在packages/api-client/src下生成createUser、getPostById、listPosts这类请求函数在apps/server/src下生成数据库表的 seed 文件和路由类型定义在apps/web/src下生成对应的 React Query hook。这里我要特别提醒一个新手容易忽略的点published字段加了.default(false)生成的数据库 migration 也会带默认值但前端类型上这个字段仍然可能为空字符串或 null。也就是说类型层面它依然是可选字段你在渲染时必须处理undefined的情况不能因为它有默认值就在前端当必填用。这种细节不亲自跑一遍真的很难意识到。3.3 把后端 API 跑起来Hono 服务里的真实样子后端默认用的是 Hono一个非常轻量的 Web 框架。生成出来的apps/server/src/index.ts不是空壳它是带了实际 CRUD 路由的import { Hono } from hono; import { prisma } from ./db; import { Post, User } from my-first-t3code/schema; const app new Hono(); app.get(/users/:id, async (c) { const user await prisma.user.findUnique({ where: { id: c.req.param(id) }, }); if (!user) return c.json({ error: not found }, 404); return c.json(User.parse(user)); }); app.post(/posts, async (c) { const body await c.req.json(); const parsed Post.omit({ id: true, createdAt: true }).parse(body); const post await prisma.post.create({ data: parsed }); return c.json(post, 201); }); export default app;注意这里的User.parse(user)它在运行时做了一次校验确保从数据库出来的数据真的符合 schema。如果数据库里混入了脏数据这个 parse 会抛异常而不是让脏数据继续流到前端。实际开发中我遇到过几次因为历史上手写 SQL 导致数据库出现不符合预期的数据靠这一层 parse 拦下来之后排查速度明显变快。启动服务pnpm dev --filter server默认端口是 3001。你可以直接curl一下curl http://localhost:3001/users/test-id应该能拿到 404 的 JSON 响应说明服务已经正常在跑。3.4 前端页面与类型联调生成的 hook 直接能用前端启动pnpm dev --filter web在apps/web/src/App.tsx里可以像下面这样直接用生成的 hookimport { useListPosts, useCreatePost } from my-first-t3code/api-client; function App() { const { data: posts, isLoading } useListPosts(); const createPost useCreatePost(); if (isLoading) return divLoading.../div; return ( div {posts?.map((post) ( article key{post.id} h2{post.title}/h2 p{post.content}/p /article ))} button onClick{() createPost.mutate({ title: Hello, content: t3code works, }) } 创建文章 /button /div ); }从 schema 定义到页面渲染全程没有手写过任何 API 地址、请求函数或返回类型。我当时第一次跑完这个流程最直观的感受就是前端代码好像知道后端长什么样字段名敲错会直接在编辑器里标红。这种感觉会让人上瘾因为省掉的不仅仅是敲代码的时间更是一遍遍对着接口文档校对的精力。4. 常见问题与排查实录4.1 生成的类型和后端实际返回对不上这是我遇到过最多的问题。原因通常是后端在代码里手动修改了 response 结构比如加了分页包装app.get(/posts, async (c) { const posts await prisma.post.findMany(); return c.json({ items: posts, total: posts.length }); });但生成的 client 类型是按照返回 Post 数组推导的于是前端类型显示是数组实际运行拿到的却是一个对象。这种差异在编译期完全发现不了只有在运行时取不到posts.map才会暴露。我的排查思路分两步重新跑一次pnpm codegen确认产物没有因为手动改动而失效在项目和生成器的.json缓存里对比 response schema定位是不是手动修改了路由。重要教训生成出来的代码不要手改要改就改 schema 或路由模板。手改一时爽下次 codegen 一跑直接给你覆盖之前的改动全部白干。如果实在需要自定义返回结构应该在 schema 里用z.object({ items: z.array(Post), total: z.number() })这样的组合类型明确定义。4.2 热更新不生效或者代码被重复生成在 monorepo 模式下热更新偶尔抽风尤其是在修改 schema 之后前端页面不会自动刷新成新类型。这个问题的根源在于代码生成是一次性动作不是持续监听。你需要把 codegen 命令和 dev server 串起来。我试下来最稳的配置是在根目录的package.json里加一条scripts: { dev: concurrently \tsc --watch\ \node --run codegen next dev\ }或者更简单一点的方案写一个watch脚本用chokidar监听 schema 目录文件变化时自动执行pnpm codegen。但要注意别把 codegen 做成循环监听然后又触发 dev server 重启否则 CPU 会狂飙。我自己的习惯还是手动敲命令因为 schema 一般来说不会频繁变动一次改完跑一遍简单直接。4.3 多包仓库里的包互相找不到初始化项目用的是 pnpm workspace如果之前在别处用过 npm 或者 yarn残留的node_modules和 lock 文件可能会干扰依赖解析。现象是 import 一条路径时明明代码写在packages/api-client/src里但别的包就是解析不到。解决方案分四步删掉根目录所有node_modules建议用pnpm -r exec rm -rf node_modules清理干净删除pnpm-lock.yaml重新生成确认根目录pnpm-workspace.yaml里包含packages/*和apps/*两个路径重新pnpm install。另外需要注意如果你在 schema 包里的文件名是.ts而 API client 包引用的是它编译后的产物那需要在生成配置里把tsconfig的paths指准。我也遇到过一种更隐蔽的情况某个包把自己的main字段写成了已删除的dist/index.js导致依赖这个包的应用一直报空引用错误——所以 package.json 里的入口字段一定要核对好。4.4 环境变量的类型推导老是失败因为生成代码会读取.env里的数据库地址和 API 地址如果环境变量没配齐codegen 的时候会直接报错或生成一个奇怪的默认值。我建议在项目根目录用一个env.example维护所有需要的变量t3code 初始化时也会自动生成一份DATABASE_URLfile:./dev.db API_BASE_URLhttp://localhost:3001更进阶一点的做法是给env增加 zod 校验保证项目跑起来之前环境变量就是合法状态。我自己在多个项目里用过zod解析process.env方式是在一个env.ts文件里定义import { z } from zod; const Env z.object({ DATABASE_URL: z.string(), API_BASE_URL: z.string().url(), PORT: z.string().default(3001), }); export const env Env.parse(process.env);这样做的好处是如果你部署到服务器时忘记配某个变量启动瞬间就会得到一个明确的错误信息而不是运行到一半才暴露出怪异的行为。这个问题虽然和相关工具有点远但它恰恰是工程化项目里最常用的保险方法。5. 进阶技巧与我的真实体验5.1 自定义生成模板让代码更贴合团队风格t3code 的模板文件本身是开放的使用基于 Handlebars 的语法存放在node_modules/t3code/templates或者项目根目录的.t3code/templates。我记得它的模板目录大概长这样.t3code/ templates/ client-fetch.hbs hook-react-query.hbs server-route.hbs比如我想让前端生成的请求函数默认带上统一的错误上报逻辑只需要在client-fetch.hbs里加一段 capture 代码export async function {{camelCase name}}(params: {{camelCase name}}Params): Promise{{camelCase name}}Response { try { const res await fetch({{baseUrl}}{{path}}, ...); if (!res.ok) throw new Error(request failed); return await res.json(); } catch (e) { reportError(e); throw e; } }改完模板后重新pnpm codegen所有生成的请求函数都会自动带上错误上报。团队如果用 Sentry 或者其他监控平台这个钩子非常值得加。自定义模板的另一个用途是配置团队内部的代码风格比如函数名用 camelCase 还是 snake_case、文件使用单引号还是双引号、最后一行要不要写分号都能通过模板统一掉省去代码评审口舌之争。5.2 接进 CI/CD 之后的落地效果我在一个真实项目里把 t3code 接进了 CI 流程主要做了两件事第一在pre-commit的 hook 里检查 schema 是否一致如果发现 codegen 之后有文件变更就说明有人改了 schema 但忘了跑生成命令直接拦截提交。第二在 CI 的构建流水线第一步就执行pnpm codegen确保部署的时候用的永远是新鲜的产物。流水线环节大概是这样的build: steps: - run: pnpm install - run: pnpm codegen - run: pnpm build这个流程跑下来之后我发现代码评审里针对前端类型不对的评论几乎消失了。以前每次改接口都要在 PR 描述里附带接口文档链接提醒前端同事看字段变化现在只要 schema 在 PR 里更新了生成的代码变更自然出现在 diff 里评审人一眼就能看出变化影响到了哪些请求函数、哪些页面 hook沟通成本明显降低。5.3 关于是否值得自己造轮子的个人想法最后聊聊我自己的感受。t3code 这类生成式工程化工具网上评价两极分化很严重。反对的人觉得它引入了黑盒生成代码一旦有 bug排查起来非常痛苦支持的人觉得它把重复劳动压缩到了极致能腾出时间写真正的业务逻辑。我站在实操者的角度说句公道话工具的价值取决于团队对抽象层的信任和掌控能力。如果你把 t3code 当成一个不可修改的黑盒遇到问题只能去提 issue那你确实会焦虑但如果你花一个下午把它的模板和生成逻辑读明白把它当作一个可以随意调整的自动化助手那它带来的收益会远远大于学习成本。我第一次尝试改模板时也担心会不会把项目改崩但冷静下来之后发现生成器本质上就是模板和数据的朴素拼接只要 schema 定义正确不管模板怎么改都不会出现无法预期的运行时错误。后面我计划把它用在一个稍微大一点的团队项目里重点验证多模型关联场景下的生成稳定性和团队协作流程。到时候如果发现新的问题或者沉淀出新的经验再单独写一篇记录。

相关新闻

回溯算法进阶:组合总和与复原IP地址的递归去重实战

回溯算法进阶:组合总和与复原IP地址的递归去重实战

如果你正在刷回溯,基本组合、子集之后大概率会撞上两道绕不开的题:组合总和和复原IP地址。前者是“同一个元素能不能重复用”的去重问题,后者是“字符串怎么切才合法”的分割问题。这两个问题表面上一类推数组、一类推字符串,实际…

2026/10/9 9:07:57 阅读更多 →
Agent-Reach 实战指南:从环境搭建到稳定运行的工程化落地

Agent-Reach 实战指南:从环境搭建到稳定运行的工程化落地

1. 从"Agent-Reach"这个名字说起:它到底想解决什么问题第一次看到 Agent-Reach 这个项目名,我的直觉是:这大概率是一个围绕 AI Agent 能力边界做文章的工具。Reach 这个词在工程语境里通常有两层含义,一层是"触达&…

2026/10/9 9:07:57 阅读更多 →
pstack-claude 工程化实践:Claude Code 安装、MCP 配置与多模型接入

pstack-claude 工程化实践:Claude Code 安装、MCP 配置与多模型接入

1. 从 pstack-claude 这个标题说起:它到底想解决什么问题第一次看到pstack-claude这个项目名,我脑子里冒出来的第一个念头是:这大概率是一个把 Claude 系列模型能力“栈化”封装的工具集,或者是一套围绕 Claude 做本地化调用、任务…

2026/10/9 9:07:57 阅读更多 →

最新新闻

ARM架构本质:不是指令集背诵,而是硬件契约与系统权衡

ARM架构本质:不是指令集背诵,而是硬件契约与系统权衡

1. 为什么“搞懂ARM架构”这件事,90%的人从一开始方向就错了很多人点开一篇叫《一文深入搞懂ARM处理器架构》的文章,心里想的是:“我只要记住Cortex-A78比A55快、Neoverse是服务器用的、Thumb指令集更省电”——然后合上页面,觉得…

2026/10/9 9:53:15 阅读更多 →
构建 Agent Ready 的企业云基础设施:TaoToken 统一 Key 通道的私有部署实践

构建 Agent Ready 的企业云基础设施:TaoToken 统一 Key 通道的私有部署实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 9:53:15 阅读更多 →
PCA降维实战指南:从原理误区到工程落地

PCA降维实战指南:从原理误区到工程落地

1. 这不是数学课,是降维实战手册:为什么你总在PCA上卡壳?“PCA:详解主成分分析”——光看标题,很多人第一反应是:又来一个教科书式推导?矩阵转置、协方差、特征向量……一连串符号砸下来&#x…

2026/10/9 9:53:15 阅读更多 →
组态王连接S7-200 SMART TCP通信实操指南

组态王连接S7-200 SMART TCP通信实操指南

1. 项目概述:为什么这个连接案例值得花时间吃透?组态王——国内工业自动化领域绕不开的上位机软件,尤其在中小型产线、教学实训、设备改造场景里,它几乎是电气工程师和自动化调试人员的“默认选项”。而S7-200 SMART,则…

2026/10/9 9:53:15 阅读更多 →
激活函数实战选型指南:Sigmoid、ReLU、SiLU工程避坑手册

激活函数实战选型指南:Sigmoid、ReLU、SiLU工程避坑手册

1. 为什么今天还要深挖这三个激活函数?——不是为了怀旧,而是为了选对你可能已经见过太多“激活函数对比图”:Sigmoid画成一条平滑的S形曲线,ReLU是直角折线,SiLU则像被温柔拉长的ReLU尾巴。但这些图背后真正决定模型成…

2026/10/9 9:53:15 阅读更多 →
t3code 深度解析:Electron + CLI + Homebrew/winget 跨平台工具链实战

t3code 深度解析:Electron + CLI + Homebrew/winget 跨平台工具链实战

1. 从 t3code 这个标题说起:它到底想解决什么问题第一次看到 “t3code” 这个标题,我脑子里蹦出来的第一反应是:这大概率是一个围绕命令行工具链做整合的项目,而且名字里的 “t3” 很可能对应着某种技术栈缩写或者版本代号。结合热…

2026/10/9 9:52:12 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 10:10:36 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →