ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Delphi/C++Builder报表开发:ExpressSpreadSheet v1.38源码级应用与避坑指南

Delphi/C++Builder报表开发:ExpressSpreadSheet v1.38源码级应用与避坑指南 简介DevExpress.ExpressSpreadSheet v1.38 for Delphi/BCB 源代码专为 Delphi 与 C Builder 开发者提供一套完整的电子表格组件实现可在应用程序中集成类似 Microsoft Excel 的表格展示、公式运算、数据绑定与可视化交互。源码开放比二进制发行版更有价值便于深入理解组件内部引擎、界面渲染和事件处理机制也可作为二次开发与性能调优的基础。资源包为 RAR 压缩格式大小约 2.81MB包含 382 个文件以 73 个 pas 单元、38 个 cpp 与 10 个 h 文件为源码主体配合 45 个 dpk、24 个 bpk 及 bdsproj/dproj/cbproj 等工程文件以及 res、dfm、bmp、dcr 等资源与窗体文件可完整还原或重新编译组件。文件列表还包含 CustomFunction、FeaturesDemo、Stocks、Flying 等多个示例项目覆盖自定义函数、功能特性展示、股票数据表格等典型应用场景便于按模块对照学习。目前已有 445 人学习下载。对希望掌握 DevExpress 电子表格体系、或需要定制复杂表格功能的 Delphi/BCB 开发者而言这套源码是极具参考价值的实战资料。1. ExpressSpreadSheet v1.38 到底是什么先把它当黑匣子用起来做 Delphi 和 BCB 的老开发十有八九都遇到过这种场景客户丢过来一个 Excel 报表需求不是简单的几行几列而是带公式、带样式、带合并单元格、还要求不装 Office 也能跑的桌面程序。这时候脑袋里冒出来的第一个名字就是 DevExpress 的 ExpressSpreadSheet。这个 v1.38 听起来像个老版本但它在 Delphi 和 BCB 圈子里的地位很特殊它是一套能在设计期就完全脱离 Excel 环境、用纯 Delphi 代码把电子表格组件拖进窗体、运行期直接读写 xls 文件的源码包。这一版最大的价值不在于它有多新而在于它是「源代码」发布。拿到手以后你看到的不只是一堆 dcld 和 bpl 的编译产物而是真正能打开、能单步、能改的 .pas 文件。对于要长期维护报表模块的团队来说这意味着后悔药意味着黑匣子被打开。这套组件能解决的最核心问题是让 Delphi/CBuilder 程序拥有原生电子表格能力单元格赋值、公式计算、样式渲染、打印预览、文件读写全部不依赖 Excel COM。适合谁两类人最该关注一类是还在用 RAD Studio 旧版本、做进销存或者 ERP 桌面端的开发者因为 v1.38 对老版本 IDE 和旧编译器兼容性很宽另一类是手里有一套商业授权但丢了源码或者压根就是买了源码包准备做二次开发的。这一章先不深入源码内部我们先把组件装上、把最小 Demo 跑起来让黑匣子先亮起来后面再慢慢拆。2. 拿到源码包先干三件事目录结构、编译顺序、最小安装2.1 先看懂源码包的目录别急着双击安装DevExpress 的源码包和编译好的安装包有个明显区别源码包的根目录下通常是按功能模块拆的ExpressSpreadSheet 的 v1.38 在 Delphi/BCB 共用一套源文件但会根据编译器版本分别生成不同后缀的包文件。常见的布局是 Source 目录下放着所有 .pasLibrary 目录下是预编译的 .dcu 和 .objPackages 目录下是每个运行时包和设计期包的 .dpk 工程。拿到手第一件事不是去 IDE 里加路径而是打开 Packages 目录找到 ExpressSpreadSheet 相关的 .dpk 文件清单。你会发现它的包命名有规律dxSpreadSheet** 开头的是运行期包dcl dxSpreadSheet** 开头的是设计期包。运行期包是程序跑起来时候要链接的设计期包只在 IDE 里让你把组件拖到窗体上用的。这个顺序不能反反了你编译出来的 bpl 在 IDE 里注册会报找不到类。这里我建议你先建一个干净的库路径。比如 D:\DevExpress\v138 下面放 Source 和 Packages 的副本而不是直接在解压原目录里乱动。因为后面你可能要改源码改了原包一旦 IDE 崩溃或者路径混乱想恢复就麻烦。备份这件事在源码级组件上不是可选项是必须项。2.2 按依赖关系编译的三个命令编译顺序的核心原则是先编底层公共包再编 ExpressSpreadSheet 运行期包最后编设计期包。DevExpress 的组件族内部有公共依赖比如 dxCore、dxSpreadSheetCore 这些基础单元会被上层组件引用。如果你跳步直接去编 dcl 包报错信息通常是“找不到 dxSpreadSheetCore.dcu”或者“File not found”。常见做法是先把 Packages 下所有不带 dcl 前缀的运行期包按依赖顺序编一遍编完后再编 dcl 开头的设计期包。用命令行编译比在 IDE 里右键 Build 更可控因为你可以在一个批处理里记下当时的编译环境和参数。我一般在 RAD Studio 的命令行环境里这样执行msbuild /t:Build /p:ConfigRelease /p:PlatformWin32 dxSpreadSheetRun.dpk msbuild /t:Build /p:ConfigRelease /p:PlatformWin32 dxSpreadSheetDcl.dpk注意这里的 dxSpreadSheetRun 和 dxSpreadSheetDcl 是示意名你拿到手后要以 Packages 目录里的实际工程名为准。第一个命令编译运行期包生成的是 bpl 和 dcu第二个命令编译设计期包这个包会引用刚才生成的运行期包并在注册表里写入 IDE 组件注册信息。如果第二个命令报错说“无法解析的引用”回头检查第一个命令生成的 bpl 是否在系统 PATH 里或者是否在 Library 路径里。参数说明ConfigRelease 表示编译成发布版不带调试信息体积小而且链接速度快PlatformWin32 是给 32 位 Delphi/BCB 用的v1.38 那个年代还没有官方的 64 位支持硬上 64 位你会遇到一堆指针和内存模型的问题。如果你用的是 BCB记得把编译环境切换成 CBuilder 对应的命令行工具msbuild 的工程文件后缀名也会从 .dpk 变成 .cbpkg 或类似的 C 包工程后缀。2.3 在 IDE 里加 Library Path而不是用 Tools Options 乱填编完之后组件装在 IDE 里有两个入口一个是 Library 路径一个是设计期包注册。前者告诉编译器去哪里找 .dcu/.pas/.obj后者告诉 IDE 在设计期把哪些组件类显示在工具面板上。很多新手只注册了包忘了加 Library 路径导致打开一个已经有组件引用的工程时IDE 报找不到单元。正确姿势是先打开 Tools Options Environment Variables把源码包根目录下 Source 路径加进去再把编译输出的目录也加进去。然后打开 Component Install Packages把刚才编译好的设计期 bpl 加进来。如果一切顺利工具面板里会出现 TdxSpreadSheet、TdxSpreadSheetSheet、TdxSpreadSheetCell 这些组件。如果没有出现回去看 dcl 包编译是否成功以及 IDE 版本和包版本是否匹配。这里有一个很重要的细节v1.38 源码包的 .pas 文件里有条件编译指令通常用 {$IFDEF VERxxx} 或者 {$IF CompilerVersion xx} 来区分不同 IDE 版本。你在新版本 IDE 里打开老源码编译器可能走的是默认分支这时候某些新特性例如 Ribbon 界面相关的接口可能编译不过。解决方式不是去改源码而是先确认你的 IDE 版本是不是 v1.38 官方支持范围一般是 Delphi 7 到 Delphi XE 左右如果太新老老实实再准备一个老 IDE 环境。3. 从拖一个 TdxSpreadSheet 到生成可打印报表最小功能链路3.1 新建一个窗体把电子表格控件放上去TdxSpreadSheet 和普通控件不太一样它本身是一个容器里面管理 Sheet 集合每个 Sheet 才是真正的表格区域。你在窗体上放一个 TdxSpreadSheet默认会自动生成一个 Sheet。要操作这个 Sheet第一种方式是通过对象监视器里的 Sheets 属性打开集合编辑器第二种更推荐在 FormCreate 事件里用代码动态创建。我推荐代码方式因为后面接数据库数据时你要么用数据源赋值要么逐格填代码操作是躲不开的。下面这段代码在窗体加载时初始化表格的基本结构procedure TForm1.FormCreate(Sender: TObject); var ASheet: TdxSpreadSheetSheet; begin // 先清掉默认生成的 Sheet保证从干净状态开始 dxSpreadSheet1.Sheets.Clear; ASheet : dxSpreadSheet1.Sheets.Add(销售报表); ASheet.Cells[0, 0].Value : 订单号; ASheet.Cells[0, 1].Value : 金额; ASheet.Cells[1, 0].Value : SO-1001; ASheet.Cells[1, 1].Value : 12800.50; ASheet.Cells[2, 0].Value : SO-1002; ASheet.Cells[2, 1].Value : 9600; end;这里 Cells[行, 列] 的索引是从 0 开始的这一点和 Excel 的行列号从 1 开始完全不同是我每年都要提醒一次自己的坑。Value 属性的类型是 Variant所以你往里放字符串和浮点数都不用做类型转换但正因如此取出来的时候要自己判断是不是数字否则字符串和数值混在一起公式计算的边界很容易出问题。参数说明Cells 坐标的维度是行在前、列在后写反了不会编译报错只是表格内容完全错位。ASheet 变量指向新添加的 Sheet 对象后续所有单元格操作都要通过这个对象引用不要直接访问 dxSpreadSheet1.Cells那是 Sheet 集合的入口不是单元格集合的入口。3.2 给单元格上公式从给单元格赋一个 SUM 表达式开始ExpressSpreadSheet 的公式引擎是内置的不需要你手动调用计算接口。你只要把公式字符串赋给单元格的 Formula 属性控件在数据变化时自动重算。这是它和普通 StringGrid 之间最大的区别也是报表需求里最值钱的能力。继续上面的代码我们在第三行加一个合计行var ASheet: TdxSpreadSheetSheet; begin ASheet : dxSpreadSheet1.Sheets[0]; ASheet.Cells[3, 0].Value : 合计; ASheet.Cells[3, 1].Formula : SUM(B2:B3); // 强制重算确保取到的值是最新的 dxSpreadSheet1.Recalculate; ShowMessage(ASheet.Cells[3, 1].Value); end;注意这里公式字符串用的是 B2:B3也就是 Excel 风格的行列引用但实际表格里 B2 对应的是 Cells[1, 1]B3 对应 Cells[2, 1]。这种从 0 基到 1 基的转换是拿到手最容易翻车的地方写公式时忘了换算算出来的区域就缺了第一行或者多算一行。Recalculate 的作用是同步刷新所有公式如果你在循环里批量赋值后没调用它最后取到的 Value 很可能是旧缓存这种问题在带公式的报表里极难排查因为值看起来是对的只是滞后了一轮。参数说明公式里的区域引用是字符串不会因为你改了行数自动跟随所以用代码动态生成公式时一定要用 Format 把行号拼进去。如果表格行数会变建议在写公式前先查一下 ASheet.RowCount避免公式引用到空行。3.3 样式和打印输出让报表拿出去不像临时拼的报表不是能算数就完事了客户要的是能直接打印、带边框、带背景色的样子。ExpressSpreadSheet 的样式体系是基于单元格 Style 对象链的你可以给单个单元格设置也可以定义样式后批量套用。最省事的办法是直接操作 Cells[row, col].Style 的几个常用子属性ASheet.Cells[3, 0].Style.Font.Style : [fsBold]; ASheet.Cells[3, 1].Style.Font.Style : [fsBold]; ASheet.Cells[3, 0].Style.Font.Color : clRed; ASheet.Cells[3, 1].Style.Font.Color : clRed; ASheet.Rows[3].Height : 25;这段代码把合计行加粗标红行高加到 25 像素。注意这里修改的是 Style 对象里的属性不能再直接给 Style 赋一个新对象否则会断开样式链。打印输出时通常走两种路子一种是把内容导出成 xls 文件让 Excel 去打印另一种是直接用组件的打印接口。我建议在桌面应用里优先走导出因为打印预览和纸张设置这套东西Excel 本身已经做得很成熟组件自带的预览往往在边距和分页上和你期望的不完全一致。导出代码很简单dxSpreadSheet1.SaveToFile(D:\report.xls, xlsXLS);第二个参数是文件格式枚举xlsXLS 是老版 97-2003 格式xlsXLSX 是新格式。v1.38 对 xlsx 的支持要看具体版本老版本可能只把 xls 作为稳定输出格式所以出正式报表前先测一下目标机器上有没有能打开 xlsx 的程序否则导出了用户打不开等于白做。4. 把 ExpressSpreadSheet 接进数据库查询构造成本与刷新策略4.1 为什么用 DataSource 绑定不是第一选择网上很多 Delphi 教程一讲到表格组件就提 DataSource 属性但 ExpressSpreadSheet 和其他网格组件不一样它不是以 DataSource 为核心设计的。它的设计重心是电子表格模型而不是数据库网格所以强行用 ADO 或 DB 组件去绑定你会发现单元格的 Value 只在数据显示层面生效公式重算和样式设置都得在 AfterScroll 等事件里手工补齐代码写得比逐格赋值还长。我一般做法是数据库查询结果出来后用一个循环把字段值写进 Sheet 单元格同时在内存里维护 一个二维数组或者客户数据集作为缓存。这样做的好处是回写 Excel 模板时完全可控某个字段要加格式化、要拼接文本直接在赋值阶段处理不用依赖组件的字段映射逻辑。这种做法听起来比拖两个组件笨但在出报表时反而是最稳的。原因在于 ExpressSpreadSheet 的单元格 Value 是 Variant 类型如果你直接绑数据库字段日期值和时间值会以浮点序列号的形式出现用户打开报表看到一串 43000 这种数字直呼看不懂。自己逐格写就有机会把日期先 Format 成可见的字符串。4.2 用循环写数据时的两个预处理点下面代码演示从查询结果填充 Sheet并做类型预处理procedure TForm1.LoadDataToSheet(ADS: TDataSet); var Row: Integer; I: Integer; begin Row : 1; // 第 0 行是表头数据从第 1 行开始 while not ADS.Eof do begin dxSpreadSheet1.Sheets[0].Cells[Row, 0].Value : ADS.FieldByName(ORDER_NO).AsString; dxSpreadSheet1.Sheets[0].Cells[Row, 1].Value : ADS.FieldByName(AMOUNT).AsFloat; dxSpreadSheet1.Sheets[0].Cells[Row, 2].Value : FormatDateTime(yyyy-mm-dd, ADS.FieldByName(ORDER_DATE).AsDateTime); Inc(Row); ADS.Next; end; dxSpreadSheet1.Recalculate; end;这个循环做了三件必须做的事第一订单号显式转成字符串防止数据库里某个订单号是数字类型被自动转成科学计数法第二金额用 AsFloat 直接取数值保证后续 SUM 公式能正确累加第三日期先格式化成字符串再赋给单元格避免 Excel 序列号问题。注意 Inc(Row) 放在 ADS.Next 之后的写法如果放在前面表头行会被数据覆盖。这个坑我踩过不止一次因为有些同事会把 Row 初值改成 0 来表示从第一行开始结果双重步进直接把数据间离了。另一个坑是数据量大时逐格赋值很慢上万行的查询在单线程里可能卡几秒。常见做法是把赋值包进 BeginUpdate/EndUpdatedxSpreadSheet1.BeginUpdate; try // 循环赋值 finally dxSpreadSheet1.EndUpdate; end;BeginUpdate/EndUpdate 之间控件不会每次值变化就重绘一遍界面刷新开销大幅降低。这个和 ListView 的 BeginUpdate 是同一个套路在 Excel 组件上效果尤其明显因为电子表格单元格数量大每次重绘都要做样式布局。4.3 刷新数据时先清空还是先覆盖刷新报表数据看起来简单但有一个边界问题第一次加载有 100 行第二次查询只有 20 行第二行的旧内容如果不清空会把没意义的残次数据带到报表里。直接覆盖只会覆盖现有行数范围内的单元格超过 20 行的旧 rows 依然留在表格里。常见的处理方式是在刷新前重新设置 RowCount或者循环清空旧值。我一般用重设 RowCount 的方法因为它直接控容器容量不会留下历史内容dxSpreadSheet1.Sheets[0].RowCount : 2; // 只留表头 dxSpreadSheet1.Sheets[0].RowCount : 1;这里第一次设成 2 是先把数据行清掉第二次设成 1 再清掉表头两步走是因为某些版本里一次性把 RowCount 设成 1 可能不会彻底清掉内部缓存的值。如果你发现清空后单元格还留着旧文本可以再调用一次dxSpreadSheet1.Sheets[0].ClearContents;ClearContents 会清掉内容但保留格式这样如果模板里本来就有表头样式刷新幅度更自然不至于把模板设计好的列宽和边框也一起清掉。如果连样式都要重置再考虑 ClearAll但那样模板里的合并单元格和公式可能一起被抹掉刷新前要把模板设计保护起来。5. 源码级修改的三个常见场景与避坑记录5.1 定制公式函数在源码里找 Calculator 单元ExpressSpreadSheet 的公式引擎不是黑盒源码包里搜索“Function”相关单元你会找到自定义函数的注册接口。常见的做法是写一个函数把它声明成 TdxSpreadSheetCustomFunction 的派生类然后注册到公式引擎里。这在报表领域特别有用比如我们要在公式里直接用 RMB 大写金额转换而不是在代码里先算好。因为 v1.38 的源码里计算公式是独立的单元不依赖界面层所以修改它不会牵一发动全身。我通常先看单元开头有没有接口注释再沿着 Calculate 虚方法往下走。修改后的类要重新编译运行期包再编设计期包这种修改会额外带来一个风险以后升级 DevExpress 时你的修改会被官方新版源码覆盖所以强烈建议把你的自定义单元放在独立目录不要直接改源码文件否则升级时 diff 很痛苦。5.2 坑一设计期包注册失败IDE 报“无法定位程序输入点”现象是安装 dcl 包时弹 Windows 错误框说找不到某个 DLL 的导出函数或者 IDE 直接崩溃。原因通常是设计期包编译时链接到了一个旧版本的 bpl而运行期包已经被新版本覆盖导致动态链接库里的函数签名对不上。解决方法是先彻底卸载旧包删掉系统目录或 IDE 的 BPL 路径里所有 ExpressSpreadSheet 相关 bpl再做一次全量重新编译。另一个常见原因是 IDE 缓存里还有旧编译单元的导入库Windows 7 以后的系统里 BPL 查找顺序还是老套路先当前目录再系统目录再 IDE 配置路径。如果你的系统目录里残留了一个老版本的同名 bpl新版本编译出的 dcl 包无论如何都加载不进去。这个问题的排解方式是在 IDE 的 Tools Options Library 里把所有 DevExpress 相关路径删掉然后手动清除del C:\Windows\System32\dxSpreadSheet*.bpl del C:\Windows\SysWOW64\dxSpreadSheet*.bpl然后再重新编译。注意操作前确认 IDE 没有在运行否则 bpl 文件被 dll 占用删不掉。5.3 坑二导出 xls 后 Excel 提示“文件格式扩展名无效”现象是程序正常保存文件但双击打开时 Excel 弹一个冒泡提示说文件格式和扩展名不一致。原因往往是保存时用了枚举值 xlsXLSX但实际写出的还是老式 BIFF8 格式或者反过来保存的是 xls 扩展名但内部格式是 XML 的。这个现象在 v1.38 和老 IDE 组合下偶尔出现因为新版本的 DevExpress 支持自动按扩展名推断格式而老版本有时候推断得不准确。解决方式是显式匹配格式不要依赖扩展名。你可以在保存前判断文件名的后缀然后指定对应的枚举值if LowerCase(ExtractFileExt(AFileName)) .xlsx then dxSpreadSheet1.SaveToFile(AFileName, xlsXLSX) else dxSpreadSheet1.SaveToFile(AFileName, xlsXLS);顺便验证保存后文件头是不是标准格式最简单的方式是拿十六进制查看工具看文件头字节。老版 XLS 开头固定是 D0 CF 11 E0新版 XLSX 开头是 50 4B 03 04这两个特征在源码包里已有定义你可以直接用这个特征来排错。5.4 坑三大数据量下公式重算导致页面卡死现象是 1 万行数据每行都带 VLOOKUP 或 SUMIF 公式程序在赋值过程中界面无响应甚至看起来像死锁。原因不是死循环而是默认状态下控件每次值变化都会触发重算一万个公式的重算在单线程 UI 上就是卡顿。解决方式很朴素——批量赋值期间关闭公式重算dxSpreadSheet1.OptionsBehavior.AutoRecalc : False; try // 大批量赋值 finally dxSpreadSheet1.OptionsBehavior.AutoRecalc : True; dxSpreadSheet1.Recalculate; end;这个开关在 OptionsBehavior 属性树下面源码里对应的字段是 AutoRecalc。这个做法的精妙之处在于赋值期间不计算最后一次 Recalculate 把全部公式一次算完。需要注意的边界是如果你在赋值过程中要读取某个公式单元格的值这时候拿到的可能是旧数据所以只适用于纯写入然后整体展示的场景。若中间必须读值就手动在读取前调用一次局部重算接口源码里通常有基于区域的重算方法。5.5 坑四在 BCB 环境源码包编译报错现象是同样的 .pas 文件在 Delphi 里编译通过切到 BCB 后报 C 语法错误比如原生类型的别名没识别。原因主要是老版 BCB 对新 Pascal 特性如泛型、匿名方法的支持有限而 v1.38 源码在某些单元里用了 Delphi 特有的构造CBuilder 的 Pascal 前端解析不了。常见做法是先确认工程里 .hpp 头文件是否生成安装包若带了预编译的 obj 库跳过源码编译直接用 import library 链接。如果必须源码编译优先找 BCB 专用的 package 工程不要直接拿 Delphi 的 .dpk 去 C 环境里编。另外检查你的 BCB 版本是不是 v1.38 支持的那一档CBuilder 2010 和 XE 的符号修饰规则差异很大老源码在新编译器上往往会出现链接时符号找不到这种问题就不是改代码能解决的只能换到对应老版本环境。6. 进阶玩法把 ExpressSpreadSheet 嵌进自绘报表预览界面源码包给你最大的自由度是你可以把组件完全当成一个渲染引擎而不是对话框集合。我做过一个比较典型的自定义方案主窗体左边是查询条件右边放一个 TdxSpreadSheet 作为结果区上面放一个预览按钮点击后不直接调组件的打印对话框而是通过画布自绘整个预览界面。自绘预览并不复杂核心是把 TdxSpreadSheet 的内容按缩放比例画到一块 Image 上。角度上你要处理三件事第一件是确定页宽对应的列范围电子表格不像 TCanvas 按像素计数需要把 Sheet 的列宽换算成像素第二件是处理分页默认的打印分页依据是可打印区域自绘预览通常按固定纸张宽度模拟分页第三件是捕获鼠标滚轮做翻页。这种方案比直接用组件自带打印预览的优势在于预览界面可以和整个软件的皮肤、字体、按钮风格统一不需要弹出独立窗口用户体验完全一致。缺点是自绘逻辑要自己维护尤其是当单元格里含嵌入对象或者批注时自绘会遗漏部分细节因为组件的绘制接口只负责规则内容。另一个我常用的进阶点是动态生成多 Sheet 报表。比如一个销售月报第一页放汇总第二页到第五页按区域分 Sheet每个区域 Sheet 的公式和数据源不同但模板来自同一个内存模板。做法是先把模板 Sheet 复制多次再修改每个 Sheet 的标题和数据区。源码包里 Sheet 集合有复制接口复制出来的 Sheet 会连样式和公式一起复制这一点比从空白 Sheet 重新搭要省事得多。复制之后要注意公式引用范围Excel 公式里如果写着 Sheet1!A1 这种跨表引用复制 Sheet 后公式里的表名仍然指向旧 Sheet需要遍历修改。这个坑在报表模板场景很容易踩因为模板通常就包含跨表汇总。Ex 经验做法是复制完后统一把公式字符串里的 Sheet 名用 Replace 替换成新的 Sheet 名称虽然暴力但稳定。验证报表是否正确的最后一道关口我一直认为是打开 Excel 看公式计算结果而不是只看界面显示。组件内置的公式引擎和 Excel 原生公式在财务函数上偶尔有舍入差异比如 ROUND 的银行家舍入规则不同版本表现不同。所以我的习惯是导出 xls 后再用程序或者 Excel 打开核对几个关键单元格的值确认一致后才交付给使用者。源码包这个东西拿到手最大意义不是看它有多少单元、多少行代码而是你能在生产环境里按自己的思路去改它、去验证它。v1.38 虽然老但它的公式引擎、存储架构、打印体系依然是现代 DevExpress 组件的地基。用老版本的时候一定要记住先让黑匣子转起来再考虑开盖维修开盖之前留好备份。希望帮到你。本文还有配套的精品资源点击获取
返回列表