Delphi数据库组件源码跨版本编译与实战指南
简介Absolute Database v7.93 源代码包面向 Delphi 开发者尤其是需要在 Delphi 4 至 11含 Delphi 11 Alexandria环境中集成嵌入式关系型数据库的工程师。它提供完整的数据库引擎实现涵盖事务处理、内存与缓存优化、多用户并发、用户权限与数据加密、备份恢复以及对 DBF、Paradox、Access、SQL Server 等格式的迁移支持便于二次开发与性能调优。压缩包共 756 个文件约 18.9MB以 sql 脚本、hpp 与 h 头文件、pas 与 c 源码、dcu 编译单元、dpk 与 bdsproj 工程文件、dfm 窗体、res 资源及 chm 帮助文档为主覆盖从源码到编译产物的完整结构。目前已有 441 人学习下载适合希望深入理解数据库内核、按业务需求定制功能或构建跨平台数据应用的开发者参考。1. 从一份 Delphi 老项目的源码包说起它到底能跑出什么如果你手头正好有一份标注为Absolute_Database_v7.93_sources_for_D4-11的压缩包第一反应大概率是懵的——名字里既有版本号又带sources还挂着D4-11这种像编译器版本区间的后缀。这不是一个可以直接双击运行的成品软件而是一套面向 Delphi 4 到 Delphi 11 的数据库组件源码。换句话说它给你的是“零件”和“图纸”不是“整车”。这类资源在当下的技术圈里属于典型的“考古级但仍有生命力”的资产Delphi 在老牌桌面数据库应用、工业上位机、医疗设备管理软件里依然有大量存量系统在跑而一套能跨 D4 到 D11 编译的数据库访问层源码意味着你可以把它塞进一个二十年前的项目里做维护也能在较新的 RAD Studio 环境里重新编译出 64 位版本。它解决的核心问题很具体当你需要在不引入外部商业组件包的前提下让 Delphi 程序具备连接数据库、执行查询、管理数据集、处理事务的能力时这套源码提供了完整的实现路径。适合谁一是手里有 Delphi 遗留项目、需要替换或修复数据库访问层的维护工程师二是想研究 Pascal 语言下数据库中间件设计模式的学习者三是需要在无第三方依赖环境下快速搭建数据模块的独立开发者。但别指望它像现代 ORM 那样开箱即用它的价值在于“可控”和“可改”而不是“方便”。2. 拆包先看目录D4-11 的兼容层是怎么组织的拿到源码包后别急着往 IDE 里拖。先解压到一个纯英文、无空格的路径下比如D:\work\absdb_src。Delphi 对中文路径和空格路径的容忍度在旧版本里非常差这是血泪经验。解压后你会看到类似Source、Packages、Demos、Lib这样的顶层目录。Source里是核心实现单元Packages里是按不同 Delphi 版本预置的包工程文件Demos是示例程序Lib里通常是预编译的 DCU 或者资源文件。2.1 识别核心单元与编译条件这套源码能横跨 D4 到 D11靠的是大量的条件编译指令。你打开任意一个.pas文件会看到满屏的{$IFDEF VER140}、{$IFDEF VER340}之类的块。VER140 对应 Delphi 6VER340 对应 Delphi 10.3 RioD11 是 VER350。这些数字是编译器版本号不是产品版本号。你需要做的是找到与你的 IDE 版本匹配的{$DEFINE}开关。常见做法是在项目选项的“条件定义”里手动加上ABSDB_D11或者ABSDB_LEGACY这样的自定义符号而不是去改源码里的版本判断。因为源码里的版本判断是给包工程用的你手动改容易漏掉依赖链。{ 在项目选项 - Delphi Compiler - Conditional defines 里添加 } { 假设你用的是 Delphi 11 Alexandria } ABSDB_D11;ABSDB_UNICODE;ABSDB_USE_FIREBIRD上面这段不是代码是你在 IDE 里要填的“条件定义”字符串。分号分隔大小写敏感。ABSDB_UNICODE很关键因为 D2009 之后 Delphi 默认字符串是 UnicodeString而 D4-D7 是 AnsiString。如果不定义这个开关源码里那些PChar和string的转换会在新编译器上直接报类型不匹配。ABSDB_USE_FIREBIRD是示例代表你要连接的数据库类型常见还有ABSDB_USE_MSSQL、ABSDB_USE_ORACLE。选错了不会编译失败但运行时会连不上库。2.2 包工程的加载顺序与依赖关系Packages目录下通常有AbsDB_R.dpk运行时包和AbsDB_D.dpk设计时包。正确的加载顺序是先打开运行时包编译再打开设计时包编译最后安装设计时包到 IDE。如果你反过来设计时包会因为找不到运行时包的 DCU 而报F2063 Could not compile used unit。另一个坑是D4 到 D7 的包文件扩展名是.dpk但从 D2005 开始包工程变成了.dpk加.dproj两个文件。如果你只复制了.dpk而丢了.dproj在新版 IDE 里打开会提示“不是有效的项目文件”。解决办法是新建一个包工程把.dpk里的contains列表手动粘进去。提示编译运行时包之前先检查Library Path里是否包含了源码的Source目录。否则编译器找不到.pas文件会报F1026 File not found。3. 把源码变成可用的数据模块从编译到第一个查询编译通过只是第一步真正要跑起来你得在窗体上放一个TAbsDatabase组件配好连接参数然后挂上TAbsQuery或TAbsTable。这套组件的设计思路是“连接-数据集-字段”三层分离和 Delphi 自带的 BDE 架构类似但去掉了 BDE 的引擎依赖直接走数据库原生客户端库。这意味着你的程序发布时不需要带一堆 BDE 的 DLL但需要带对应数据库的客户端 DLL比如连 Firebird 要带fbclient.dll连 SQL Server 要带sqlncli或msodbcsql。3.1 连接字符串的参数写法与常见错误连接字符串的格式在源码的AbsDBConst.pas里有常量定义但实际拼接是在TAbsDatabase.Connect方法里做的。典型写法是Server127.0.0.1;Port3050;DatabaseD:\data\test.fdb;UserSYSDBA;Passwordmasterkey;CharsetUTF8。注意Charset参数如果你在 D11 里用 Unicode 字符串但数据库字符集是 NONE查询中文会返回乱码。这不是组件的问题是数据库本身的字符集没设对。另一个高频错误是Port写成3051Firebird 默认是 3050但有些安装包会改成 3051连不上时先telnet一下端口。procedure TForm1.ConnectDB; begin AbsDatabase1.DatabaseName : test.fdb; AbsDatabase1.Host : 127.0.0.1; AbsDatabase1.Port : 3050; AbsDatabase1.UserName : SYSDBA; AbsDatabase1.Password : masterkey; AbsDatabase1.Params.Add(CharsetUTF8); AbsDatabase1.Params.Add(ProtocolTCP); try AbsDatabase1.Connected : True; except on E: Exception do ShowMessage(连接失败: E.Message); end; end;这段代码展示了最简连接流程。DatabaseName在本地连接时是文件路径在远程连接时是数据库别名。Params是个字符串列表每行一个参数顺序无所谓但Charset建议放在最前面。Connected : True会触发实际连接如果失败会抛异常。注意try...except里不要只写ShowMessage生产环境要写日志因为E.Message可能包含数据库地址和用户名直接弹窗有安全风险。3.2 查询组件的字段绑定与参数化查询TAbsQuery的SQL属性是TStrings你可以用Add逐行加也可以直接赋值多行字符串。参数化查询用:ParamName占位然后通过Params.ParamByName(ParamName).Value赋值。这里有个坑如果你在SQL里写了:ID但忘记给Params赋值运行时会报Parameter ID not found。更隐蔽的是如果你在循环里反复执行同一个TAbsQuery每次Open之前必须Close否则第二次Open会报Dataset already open。procedure TForm1.QueryData; begin AbsQuery1.Close; AbsQuery1.SQL.Clear; AbsQuery1.SQL.Add(SELECT ID, NAME, CREATED_AT FROM USERS); AbsQuery1.SQL.Add(WHERE AGE :MinAge); AbsQuery1.SQL.Add(ORDER BY CREATED_AT DESC); AbsQuery1.Params.ParamByName(MinAge).AsInteger : 18; AbsQuery1.Open; while not AbsQuery1.Eof do begin Memo1.Lines.Add(AbsQuery1.FieldByName(NAME).AsString); AbsQuery1.Next; end; end;Close再Clear再Add是标准套路不要直接改SQL而不Close。ParamByName比Params[0]安全因为顺序可能变。AsInteger会做类型检查如果字段是字符串类型会抛异常比AsString更早暴露问题。遍历用while not Eof加Next不要用for循环因为数据集是流式的RecordCount在部分数据库上返回 -1。4. 避坑与排查那些编译通过但跑不起来的情况4.1 现象编译报Unit AbsDBUtils was compiled with a different version of AbsDBConst原因你之前编译过旧版本的包DCU 缓存里还有旧的.dcu文件而源码已经更新了。Delphi 的编译器会优先用搜索路径里的 DCU而不是重新编译 PAS。解决在 IDE 里执行Project - Build All或者手动删除Source目录下所有.dcu文件再删除Lib目录下的预编译 DCU。如果还不行检查Library Path里是否有其他路径也包含同名单元。4.2 现象运行时弹窗Cannot load client library原因数据库客户端 DLL 不在系统 PATH 或程序目录下。比如 Firebird 的fbclient.dll默认装在C:\Program Files\Firebird\Firebird_3_0\bin但你的程序在D:\app系统 PATH 里没有 Firebird 的 bin 目录。解决把fbclient.dll复制到程序 EXE 同目录或者把 Firebird 的 bin 目录加到系统 PATH。注意 32 位程序必须用 32 位 DLL64 位程序用 64 位 DLL混用会报Bad Image错误。4.3 现象查询中文返回???或乱码原因数据库字符集、连接字符集、Delphi 字符串类型三者不一致。比如数据库是NONE连接串写了CharsetUTF8Delphi 是 UnicodeString中间转换层会丢字符。解决先确认数据库本身的字符集用gstat -h test.fdb查看。如果是NONE要么重建数据库为UTF8要么在连接串里去掉Charset参数让组件走二进制传输然后在字段的OnGetText里手动转码。最稳妥的做法是统一用UTF8从建库到连接串到 Delphi 源码全部一致。4.4 现象事务提交后数据没落库原因TAbsTransaction组件没有正确关联到TAbsDatabase或者Commit之后没有StartTransaction就继续执行了下一条 SQL。这套组件的默认行为是自动提交但如果你显式用了事务组件自动提交会失效。解决检查TAbsQuery.Transaction属性是否指向了正确的事务对象。如果用了TAbsTransaction每次Commit或Rollback之后必须再StartTransaction否则后续操作不在事务保护下出错不会回滚。4.5 现象在 D11 里编译通过但设计时组件面板看不到图标原因设计时包安装了但组件图标资源没加载。这套源码的图标通常是.dcr文件在 D4-D7 里直接编译进包就行但从 D2005 开始.dcr需要和.pas同名且放在同一目录并且包的Contains列表里要显式包含.dcr。解决打开设计时包的.dpk在contains里加上AbsDBReg in Source\AbsDBReg.pas {AbsDB Components};注意{AbsDB Components}是图标资源名必须和.dcr里的资源名一致。5. 进阶把源码改造成跨版本条件编译的通用数据层这套源码最值钱的地方不是它能连数据库而是它展示了一套“用条件编译抹平 Delphi 版本差异”的工程方法。你可以把这套方法抽出来用到自己的任何 Delphi 项目里。核心思路是把所有版本相关的类型定义、字符串转换、内存管理差异全部收拢到一个AbsDBCompat.pas单元里其他单元只引用这个兼容层不直接写{$IFDEF}。5.1 兼容层单元的设计与实现兼容层要处理的主要差异有字符串类型AnsiString vs UnicodeString、字符指针PAnsiChar vs PWideChar、动态数组语法、泛型支持D2009 之前没有泛型、匿名方法D2009 之前没有。常见做法是定义一套类型别名比如TAbsString {$IFDEF UNICODE} string {$ELSE} AnsiString {$ENDIF}然后在所有接口里用TAbsString。对于泛型如果目标版本不支持就用TObjectList加类型转换来模拟。unit AbsDBCompat; interface type {$IFDEF UNICODE} TAbsString string; TAbsChar Char; {$ELSE} TAbsString AnsiString; TAbsChar AnsiChar; {$ENDIF} TAbsStringArray array of TAbsString; function AbsStringToPChar(const S: TAbsString): PChar; function AbsPCharToString(P: PChar): TAbsString; implementation function AbsStringToPChar(const S: TAbsString): PChar; begin {$IFDEF UNICODE} Result : PChar(S); {$ELSE} Result : PAnsiChar(S); {$ENDIF} end; function AbsPCharToString(P: PChar): TAbsString; begin {$IFDEF UNICODE} Result : string(P); {$ELSE} Result : AnsiString(P); {$ENDIF} end; end.这个单元的关键在于所有版本判断只出现一次其他单元引用AbsDBCompat后代码里看不到任何{$IFDEF}。TAbsString在 D7 里是AnsiString在 D11 里是string即UnicodeString。AbsStringToPChar和AbsPCharToString封装了指针转换调用方不需要关心底层是单字节还是双字节。注意PChar(S)在 Unicode 下是PWideChar在 Ansi 下是PAnsiChar但 Delphi 的PChar类型本身会随版本变化所以这里用条件编译区分。5.2 用构建脚本批量验证多版本编译手动在 IDE 里切换版本编译太慢我一般会写一个build_all.bat用dcc32.exe和dcc64.exe命令行编译器批量跑。前提是你机器上装了多个 Delphi 版本或者至少装了对应版本的命令行编译器。脚本的核心是设置BDS环境变量和Library Path然后对每个目标版本调用一次dcc32。echo off setlocal set SRCD:\work\absdb_src\Source set OUTD:\work\absdb_build if not exist %OUT% mkdir %OUT% echo [1/3] Compiling for Delphi 7 (Win32)... set DCCC:\Program Files\Borland\Delphi7\Bin\dcc32.exe set LIBPATHC:\Program Files\Borland\Delphi7\Lib %DCC% -U%SRC%;%LIBPATH% -I%SRC% -O%OUT% -N%OUT% %SRC%\AbsDBReg.pas if errorlevel 1 goto fail echo [2/3] Compiling for Delphi 11 (Win32)... set DCCC:\Program Files\Embarcadero\Studio\22.0\Bin\dcc32.exe set LIBPATHC:\Program Files\Embarcadero\Studio\22.0\lib\Win32\release %DCC% -U%SRC%;%LIBPATH% -I%SRC% -O%OUT% -N%OUT% %SRC%\AbsDBReg.pas if errorlevel 1 goto fail echo [3/3] Compiling for Delphi 11 (Win64)... set DCCC:\Program Files\Embarcadero\Studio\22.0\Bin\dcc64.exe set LIBPATHC:\Program Files\Embarcadero\Studio\22.0\lib\Win64\release %DCC% -U%SRC%;%LIBPATH% -I%SRC% -O%OUT% -N%OUT% %SRC%\AbsDBReg.pas if errorlevel 1 goto fail echo All builds passed. goto end :fail echo Build failed with errorlevel %errorlevel% exit /b 1 :end endlocal这个脚本里-U是单元搜索路径-I是 include 文件路径-O是输出 DCU 路径-N是输出命名空间路径。errorlevel 1判断编译失败。注意 Delphi 7 的dcc32.exe不支持-N参数所以实际跑的时候要去掉。这个脚本的价值在于你改完兼容层代码后一键验证所有目标版本是否都能编译通过避免“在 D11 里改好了D7 里编译报错”的翻车。从那以后我每次动兼容层都强制走一遍这个脚本哪怕只改了一个类型别名。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

HarmonyOS 7重建结果缓存策略:LRU 与按场景分组

HarmonyOS 7重建结果缓存策略:LRU 与按场景分组

重建结果缓存策略:LRU 与按场景分组 商品列表有 50 个 3DGS 场景,用户翻来翻去看。第一次进商品详情要重建(或下载预重建结果),耗时 600-1200 ms。第二次进同一个商品如果还要重建,用户会觉得"我都看过…

2026/10/15 15:43:46 阅读更多 →
老鼠走迷宫课设:用栈与队列吃透DFS和BFS

老鼠走迷宫课设:用栈与队列吃透DFS和BFS

简介:一份面向数据结构课程设计场景的C迷宫求解项目,适合计算机相关专业学生完成课设或练习图、栈、DFS/BFS等算法时参考;项目把迷宫抽象为二维数组表示的图,用0/1标记通路与墙体,以老鼠走迷宫为线索,完整演…

2026/10/15 15:43:46 阅读更多 →
道路检测与整车控制:从解压到实车验证的完整落地链路

道路检测与整车控制:从解压到实车验证的完整落地链路

简介:这是一份面向自动驾驶与车辆工程方向学习者的C综合项目压缩包,围绕道路检测与整车控制两大主题,涵盖传感器数据处理、车道线/斑马线识别、控制策略等实现思路,适用于正在研究ADAS、车辆动力学或Visual Studio工程实践的开发者…

2026/10/15 15:42:41 阅读更多 →

最新新闻

STM32+华为云IoT+Qt:智能家居全栈开发实战

STM32+华为云IoT+Qt:智能家居全栈开发实战

1. 项目缘起与整体架构拆解智能家居这个词已经被说烂了,但真正自己动手从零搭一套完整系统的人并不多。大部分人要么停留在用现成模组点个灯的阶段,要么卡在设备上云这一步就放弃了。我当初做这个项目的出发点很简单:家里有几台老式空调和加湿…

2026/10/15 16:28:41 阅读更多 →
储能EMS社招面试全攻略:核心考点与避坑指南

储能EMS社招面试全攻略:核心考点与避坑指南

1. 储能EMS社招面试到底在考什么储能EMS,全称Energy Management System,直译过来就是能量管理系统。放在储能行业里,它是一套负责调度、监控、优化储能电站充放电策略的软件系统。社招面试和校招面试最大的区别在于:校招可以容忍你…

2026/10/15 16:28:41 阅读更多 →
Oppia 测试模块映射体系全解析:core/tests/test-modules-mappings 如何驱动 GitHub CI 精准测试

Oppia 测试模块映射体系全解析:core/tests/test-modules-mappings 如何驱动 GitHub CI 精准测试

教育后端前端 【免费下载链接】oppia A free, online learning platform to make quality education accessible for all. 项目地址: https://gitcode.com/gh_mirrors/op/oppia 点击查看 免费下载 在 Oppia 的 CI 流程中,每一次 Pull Request 并不会盲目…

2026/10/15 16:28:41 阅读更多 →
编译原理基础:从语言定义到文法分类与语法分析

编译原理基础:从语言定义到文法分类与语法分析

1. 项目概述语言的定义是整个编译原理的起点,也是最容易被初学者一带而过的部分。很多人学编译原理一上来就啃词法分析、语法分析,结果看到自动机、文法推导就懵了,根子就在于没有真正理解“语言”到底是个什么东西。哈工大这套课程把语言的定…

2026/10/15 16:28:41 阅读更多 →
Qt低功耗蓝牙开发实战:从GATT通信到MTU协商与Notify订阅

Qt低功耗蓝牙开发实战:从GATT通信到MTU协商与Notify订阅

简介:本资源是一套基于Qt框架的低功耗蓝牙(BLE)特征值读写测试例程,面向嵌入式开发、物联网应用及跨平台桌面端BLE通信初学者与进阶开发者,解决BLE设备调试中服务发现、GATT特征交互、异步读写响应等核心实践难点。压缩…

2026/10/15 16:28:41 阅读更多 →
AP-0316语音处理模组实战:回声消除与波束成形调试指南

AP-0316语音处理模组实战:回声消除与波束成形调试指南

1. 这块模组到底解决了什么问题语音交互这件事,看起来简单,做起来处处是坑。你对着设备喊一句话,设备听到的可能是:你自己的声音、隔壁房间的电视声、空调出风口的呼呼声、桌面共振的低频嗡嗡声,以及电路本身产生的底噪…

2026/10/15 16:27:40 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 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/15 0:14:36 阅读更多 →
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/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练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/15 9:49:14 阅读更多 →