资讯动态

VFP数据库系统开发实例:从建表到增删改查完整指南

发布时间:2026/10/15 17:16:25 来源:尧图企业网站定制
简介一份面向VFP初学者的数据库系统开发实例文档以公共计算机成绩管理系统为主线完整展示从需求分析、系统设计、项目创建、数据库物理设计到界面实现的全过程。文档基于Visual FoxPro环境先梳理学生信息、管理员信息、成绩与课程等数据需求再明确录入、查询、维护、审核、打印等功能需求继而给出功能模块图、关系模型表结构以及启动表单等关键界面的事件代码适合高校学生、课程设计及自学VFP的开发者参考。文档压缩包内共有1个doc文件整包大小约902KB图文并茂便于在Word中按章节对照阅读。该文档已有七百余人学习浏览可作为毕业设计或期末项目的起步范例。读者可借此理解VFP项目管理器的官方推荐用法、数据库规范化建模思路以及将多个数据表与界面模块集成为可运行应用系统的完整方法实操参考价值较强。1. 一份VFP数据库系统开发实例为什么到今天还值得打开拿到《VFP数据库系统开发实例(附图)》这类文档时很多人的第一反应是“Visual FoxPro都退出主流多少年了还有必要看吗”。真实情况是数据库课程设计、中小型MIS系统、老单位内部管理工具到现在仍然存在大量 VFP 存量项目每年也还有一批新人被要求用 VFP 快速交出一个能跑的表单系统。这份实例的价值不在语言多新而在于它把“建库 → 建表 → 做界面 → 写增删改查 → 截图验收”的完整链路压缩成了一套可复现的步骤。你不需要先啃完整本手册只要按着实例把表和表单搭起来就能在一个晚上交付一个能演示、能打分、能维护的小系统。这篇文章就按同样的路径拆给你看先讲清楚 VFP 的数据模型和建表逻辑再落到表单和增删改查代码最后把最容易翻车的五处细节单独拎出来。2. VFP做数据库开发的底层逻辑库、表、索引的关系2.1 从DBC到DBF数据库容器与自由表不是一回事VFP 里最容易被新手混淆的概念是“数据库”和“表”。这里说的数据库不是 MySQL 里那个逻辑库而是一个叫数据库容器的东西扩展名是 .DBC。真正的数据存放在 .DBF 表文件里索引文件是 .CDX。一个 .DBC 可以管理多个 .DBF它本身不存业务数据只存数据字典信息表之间的永久关系、字段默认值、字段级规则、存储过程、本地视图、索引标识。换句话说DBC 是表的管理层DBF 是数据存储层。这个区分会直接影响你的实例文档怎么写。常见的数据库系统开发实例里第一件事一定是创建数据库容器再把表加进去。如果只建了几个自由表不挂在任何 DBC 下的 DBF也能完成增删改查但你会丢掉不少重要能力字段级规则在自由表上不生效参照完整性做不了日期和空值校验都得靠程序代码硬写。我在实际项目里一般建议只要你的系统超过两张表就用 CREATE DATABASE 建库别贪图省事用自由表。* 创建数据库容器testdata.dbc CREATE DATABASE testdata * 打开数据库容器 OPEN DATABASE testdata * 在打开的数据库容器里创建学生表 student.dbf CREATE TABLE student ; (sno C(10) PRIMARY KEY, ; sname C(8) NOT NULL, ; sex C(2) DEFAULT 男, ; birth D, ; score N(5,1), ; memo M)这段代码可以单独保存成一个 .PRG 程序文件运行也可以直接在命令窗口逐行执行。CREATE DATABASE 后面不能带分号数据库名不要用中文和空格CREATE TABLE 最后的括号里定义字段PRIMARY KEY 会同时创建主索引并且这个字段自动变成非空。这里我用了三组参数来说明建表时的常见选型C(10) 表示宽度为 10 的字符型字段学号用字符型而不是数值型因为学号不需要参与加减运算而且很多学号带前置零N(5,1) 表示数值型总长度 5 位其中小数占 1 位能存 999.9 以内的成绩D 是日期型内部按年月日排序。这些长度在实例文档的设计阶段就要定好因为后面 ALTER TABLE 改字段长度会重建表结构耗时且容易触发文件损坏。2.2 字段类型与默认值让建表语句一次写对VFP 的字段类型比主流关系数据库少但每个类型都有脾气。字符型 C、数值型 N、浮点型 F、日期型 D、日期时间型 T、逻辑型 L、备注型 M、通用型 G、货币型 Y。课程设计里最常见的组合就是 C、N、D、L、M。很多新手分不清 N 和 F其实 N 是 VFP 内部用整数保存的十进制数F 是浮点数对成绩、数量这种精度要求不高的数据用 N 就够了。M 备注字段在表里的实际值是一个指针内容存在同名的 .FPT 文件里所以当你复制 DBF 文件时必须连 .FPT 一起复制否则备注字段就会读取失败。字段默认值和字段级规则应该在建表时一并设置而不是等表单写完之后再用代码硬校验。DEFAULT 的作用是插入记录时自动填值比如性别默认填“男”出生地默认填“北京”。字段级规则用 CHECK 和 ERROR 定义CHECK 写条件ERROR 写违反条件时的提示信息。下面是一个带规则的建表示例CREATE TABLE student ; (sno C(10) PRIMARY KEY, ; sname C(8) NOT NULL, ; sex C(2) CHECK (sex $ 男女) ERROR 性别只能填男或女 DEFAULT 男, ; birth D CHECK (EMPTY(birth) OR birth DATE()) ERROR 出生日期不能晚于今天, ; score N(5,1) CHECK (score 0 AND score 100) ERROR 成绩必须在0到100之间)这里用了 $ 运算符判断字符串是否属于某个集合比写成 sex男 OR sex女 简洁得多。birth 字段的 CHECK 里先用 EMPTY(birth) 放行空日期因为空日期既不是 .NULL. 也不是具体的日期如果不放行用户不输入生日也过不了规则。这是 VFP 字段级规则里最容易被忽视的细节日期字段的空值必须单独处理。一个实际的坑在于当你通过表单输入数据时字段级规则默认在字段失去焦点时触发而不是保存按钮单击时触发。如果你把校验逻辑全压在 SQL UPDATE 上界面层反而会因为规则弹出一堆原生提示框界面风格和你自己写的提示完全不一致。所以我在做这类系统时建表规则只保留字典级底线比如性别取值、成绩范围页面友好提示由表单控件完成。2.3 索引类型怎么选主索引、候选索引、普通索引的边界VFP 的索引分四种主索引、候选索引、普通索引、唯一索引。主索引只允许在数据库容器内的表上创建每个表最多一个并且字段值不能重复候选索引和主索引约束一样但一个表可以建多个普通索引不限制重复值主要用来加速查找和排序唯一索引这个名字有误导性它不是约束重复而是同一索引键值只保留第一条记录参与显示。实例文档里如果出现“重复学号也能插入成功”的现象你首先要查的就不是代码而是索引类型。* 在 student 表的学号字段上建立一个候选索引 ALTER TABLE student ADD UNIQUE TAG xh ON sno * 在姓名字段上建立一个普通索引允许重名 INDEX ON sname TAG xmINDEX ON 命令创建的默认是普通索引不需要 ALTER TABLE。TAG 参数是索引标识名也就是这个索引在 .CDX 结构索引文件里的称呼。查询时用 SET ORDER TO xh 就能让表按学号顺序移动指针。注意ADD UNIQUE 这里的 UNIQUE 创建的是候选索引不是唯一索引这是 VFP 一个非常容易踩的术语坑SQL 标准里 UNIQUE 就是唯一约束VFP 的命令里 UNIQUE 却表示压缩重复值。所以实例文档里如果出现“UNIQUE 索引”这个说法你要先确认作者的意图。关于索引还有一个隐藏规则普通索引在打开表后是默认不生效的除非上一次会话里用 SET ORDER 设置过。而主索引和候选索引如果是通过 ALTER TABLE 建的表打开后会按主索引排序。这个默认排序的差异会让新手困惑为什么同样是带条件查询有的表返回顺序稳定有的表顺序乱跳。原因就是主索引被自动设置普通索引没有。解决方法是每次执行查询前有意识地写上一句 SET ORDER TO TAG不要指望默认状态。3. 搭出第一个可运行的维护表单从数据环境到按钮事件3.1 表单与数据环境先绑定再写代码实例文档的附图里最常出现的两张图一张是“数据环境设计器”一张是“表单运行效果图”。数据环境是 VFP 表单特有的概念它不是一个数据库也不是一个表而是表单运行时需要打开的表、视图和表之间关系的容器。你把表拖进数据环境后表单运行时会自动按设计时的顺序打开表关闭表单时自动关闭表。这样做的最大好处是你不必在每个按钮的 Click 事件里写 USE 一堆表也不会因为忘记关闭表导致文件被占用。* 表单数据环境的 Init 事件手动指定要打开的表和索引 OPEN DATABASE testdata USE student IN 0 EXCLUSIVE ORDER TAG xhEXCLUSIVE 表示独占打开只有当前用户访问适合课程设计这类单机系统。ORDER TAG xh 让表的数据环境打开后直接按学号索引排序这样后续执行 GO TOP 或 SEEK 时表本身就是有序的避免了因为索引没生效而查不到数据。如果这一步不写表默认按物理记录顺序访问SEEK 会直接在未索引状态下报“表未排序”。注意USE 命令里的 ORDER TAG 跟在表名和打开方式之后顺序不能反。3.2 按钮事件Click、Valid、InteractiveChange 分别该放什么表单界面最核心的交互是“新增”“保存”“删除”“退出”。按钮命令放哪个事件里是 VFP 实例文档里经常讲不透的地方。命令按钮一般只处理 Click也就是单击触发。但文本框的校验不能放在 Click 里因为 Click 是鼠标事件用户敲完字直接点按钮时文本框的 Valid 会先触发。Valid 被设计成在控件失去焦点时执行返回 .T. 表示允许离开返回 .F. 表示光标不能移走。这是 VFP 表单最实用的输入校验点。* 保存按钮的 Click 事件把表单控件的值写回当前记录 SELECT student IF EMPTY(ALLTRIM(ThisForm.txtName.Value)) MESSAGEBOX(姓名不能为空, 48, 提示) ThisForm.txtName.SetFocus() RETURN ENDIF REPLACE sname WITH ALLTRIM(ThisForm.txtName.Value), ; sex WITH ThisForm.cboSex.Value, ; birth WITH ThisForm.txtBirth.Value, ; score WITH VAL(ThisForm.txtScore.Value) ThisForm.Refresh()这段代码里ALLTRIM 去掉字符两侧空格避免用户误输入空格导致姓名看似有值VAL() 把文本框里的字符串转成数值因为文本框的 Value 默认是字符型MESSAGEBOX 的第二个参数 48 表示警告图标加确定按钮。保存时最忌讳的就是无条件写库至少要做一次空值兜底。这属于“有界面层的校验但仍然在保存事件里二次把关”的做法和数据库字段级规则形成两层防护。有一个细节值得注意REPLACE 只能修改当前指针指向的记录如果用户先点“新增”再点“保存”插入逻辑必须自己处理。常见做法是保存按钮只负责 REPLACE新增按钮负责 APPEND BLANK 并清空界面。两类职责拆开代码不容易互相干扰。3.3 用代码创建表单实例附图的对照依据你说你在做一个 VFP 数据库系统开发实例肯定绕不开可视化表单设计器。但可视化设计器生成的界面文件是 .SCX它是一个表文件没法用文本 diff 查看改动。对提交课程设计和审计代码的人来说更友好的方式是用代码创建表单再引用到 .SCX 里。这里我给出一个最小可用的代码建表示例它在命令窗口执行后就能看到一个带输入框和按钮的窗口frmMyForm CREATEOBJECT(Form) WITH frmMyForm .Caption 学生信息维护 .Width 420 .Height 260 .AutoCenter .T. ENDWITH txtScore CREATEOBJECT(TextBox) txtScore.Parent frmMyForm txtScore.Left 120 txtScore.Top 40 txtScore.Width 80 cmdSave CREATEOBJECT(CommandButton) cmdSave.Parent frmMyForm cmdSave.Caption 保存 cmdSave.Left 220 cmdSave.Top 40 cmdSave.Click [REPLACE student.score WITH VAL(ThisForm.txtScore.Value)] frmMyForm.Show(1)CREATEOBJECT(Form) 在内存里建立一个表单对象不使用 .SCX 文件。控件创建后必须设置 Parent 属性否则它只是孤儿对象不会显示在表单上。Show(1) 表示模态显示用户关掉窗口前不能操作其他窗口。cmdSave.Click 后面直接跟一个字符串表达式这是 VFP 里给对象方法赋代码块的简便写法。这种方式在实例文档里很难用截图表达所以绝大多数附图展示的还是设计器界面但你要复现一个实例时反而应该先看图中表单的 Caption、按钮 Caption 和输入框位置再用代码把这些属性还原出来。这个过程能帮你厘清每个控件挂在哪一层容器上比照图拖鼠标更接近“开发”而不是“拼图”。4. 把增删改查做扎实SQL命令与表操作的对照4.1 SQL增删改查的VFP写法VFP 支持一套兼容 SQL-92 的语法核心命令是 SELECT、INSERT、UPDATE、DELETE。和其他数据库最大的区别是VFP 的 DELETE 默认只做逻辑删除在记录上打删除标记不真正移除数据要做物理删除必须再执行 PACK。这个设计对实例开发有利也有弊利在于误删可以后悔药式恢复弊在于新手不知道 PACK 时表文件越来越大统计时用 COUNT 还会把已删除记录算进去。* 插入一条记录 INSERT INTO student (sno, sname, sex, birth, score) ; VALUES (2025001, 张明, 男, {^2025-03-01}, 88.5) * 按学号更新成绩 UPDATE student SET score score 1 WHERE sno 2025001 * 逻辑删除性别为女的记录 DELETE FROM student WHERE sex 女 * 物理删除已经被逻辑删除的记录需要独占打开表 USE student EXCLUSIVE PACKINSERT INTO 的 VALUES 部分字符型用单引号日期型用花括号加脱字符号 {^2025-03-01}这是 VFP 严格的日期字面量写法。如果你写 {2025-03-01}它会被当成日期表达式而不是日期字面量在部分语言设置下会解析错误。UPDATE 的 SET 支持表达式赋值score score 1 是合法写法。DELETE 命令在事务里可以回滚但 PACK 属于物理文件操作不能在事务里回滚所以 PACK 一定要在事务结束后单独执行。如果实例文档里要求保留原始数据以便复查就不要做 PACK而是用 SET DELETED ON 把所有读取命令自动屏蔽删除记录。* 查询女生成绩大于等于60的记录 SELECT s.sno, s.sname, s.score ; FROM student s ; WHERE s.sex 女 AND s.score 60 ; ORDER BY s.score DESC INTO CURSOR myresult这段查询用了表别名 sFROM 后面紧跟别名这是多表联查的标准写法。INTO CURSOR myresult 把查询结果放到一个临时表里这个临时表可以直接赋给表单的 Grid 控件作为数据源。注意ORDER BY DESC 表示降序。另一个参数是 INTO TABLE它会生成一个真实的 DBF 文件大多数实例里不需要持久化查询结果所以用 CURSOR 就够了。VFP 的 SELECT 和 SQL Server、MySQL 还有个明显差别字符串比较默认不区分大小写除非用 SET EXACT ON。这点在做登录名去重时要格外小心。4.2 不用SQL的老写法APPEND、REPLACE、SEEK虽然 SQL 已经够用但存量 VFP 系统里仍然大量存在老式表操作代码。原因很简单老项目的作者从 FoxBase 过来习惯用指针定位加 REPLACE 处理业务性能上在小数据量下表现也不差。如果你拿到一份实例文档里面的代码全是 GO TOP、SKIP、LOCATE、REPLACE没必要强行改成 SQL但要能看懂。USE student EXCLUSIVE GO TOP LOCATE FOR sno 2025001 IF FOUND() REPLACE score WITH 90 ENDIFLOCATE FOR 是顺序扫描从当前指针位置一直扫到文件尾。FOUND() 判断是否查到了记录。这套写法适合单表单文件、记录数几千条以内的场景。一旦表超过几万条LOCATE 的耗时就会线性增加换成 SEEK 才是正解。SEEK 要求表有激活索引并且 SET ORDER 指定的索引键必须与查找值类型一致。USE student IN 0 SET ORDER TO TAG xh SEEK 2025001 IF FOUND() REPLACE score WITH 91 ENDIFSET ORDER TO TAG xh 把学号索引设为当前有效索引SEEK 按索引键值查找。字符型学号用单引号数值型关键字段就不带引号。这里最常见的错误是建了索引但没 SET ORDER直接 SEEK 只能得到错误结果或者索引建在 sno 上却用 sname 去找同样查不到。VFP 里还有一个“玄学”问题就是 SET EXACT 状态会影响字符串等值比较。默认 OFF 时2025 能匹配上 2025001 的前四位如果实例里的学号字段宽度不固定你会莫名查出多条记录。排查时先执行 SET EXACT ON强制要求完全匹配。4.3 事务给错误操作留一剂后悔药VFP 支持本地事务命令是 BEGIN TRANSACTION 和 END TRANSACTION回滚用 ROLLBACK。事务内的表更新包括 INSERT、UPDATE、DELETE 和 REPLACE都可以在 ROLLBACK 时撤销。这在你做一个多步操作时尤其重要比如批量给学生加分时第 10 条记录触发字段级规则错误如果不用事务前 9 条已经改掉了后面想整体取消做不到。USE student EXCLUSIVE BEGIN TRANSACTION UPDATE student SET score score 5 WHERE score 60 IF RECCOUNT() 100 ROLLBACK MESSAGEBOX(记录数异常操作已回滚, 48, 事务提示) ELSE END TRANSACTION ENDIF这段代码模拟了一个批量操作的后置校验。因为 UPDATE 不会逐条报错所以只能在事务结束后用 RECCOUNT() 判断影响的行数是否符合预期不符合就 ROLLBACK。事务对 DBC 结构操作的约束要特别清楚BEGIN TRANSACTION 之后不要执行 ALTER TABLE、MODIFY STRUCTURE、PACK、ZAP 这类命令它们不受事务保护而且可能直接报错。实例文档里如果出现“先 PACK 再事务回滚”的写法基本可以断定是无效演示。5. 避坑VFP实例开发中最常见的五处翻车点5.1 关窗口后数据丢了一半现象在表单里录入了好几条记录点退出按钮时没有提示重新打开表单后最后几条记录不见了表里留下的是空白记录。原因表单数据环境里把 BufferModeOverride 设成了缓冲模式但没有在保存时调用 TABLEUPDATE()。缓冲模式下所有表操作的记录不是立即写入原表而是缓存在内存里必须显式提交才能落盘。解决在“保存”按钮的 Click 事件里调用表格对应游标的 TABLEUPDATE()并用返回值判断是否提交成功如果提交失败用 TABLEREVERT() 放弃当前修改。比较稳妥的做法是统一在表单的 Unload 事件里做一次兜底提交* 表单 CheckOut 按钮提交并关闭 IF TABLEUPDATE(.T.) THISFORM.Release() ELSE MESSAGEBOX(数据没有保存成功请检查后重试, 48, 提示) ENDIFTABLEUPDATE(.T.) 的 .T. 表示强制提交所有修改过的记录。使用缓冲模式时我的血泪经验是宁可多做一次提交也不要只依赖控件失去焦点时的隐式保存。5.2 日期字段显示成空串判断却总是不对现象birth 字段没填值时表单里显示 00/00/00 或者空白代码里用 ISNULL(birth) 判断却永远返回 .F.。原因VFP 的日期字段空值有两种NULL 和空日期 { / / }。绝大多数表单输入不写 NULL而是保留空日期。ISNULL 只能判断前者。解决所有针对日期字段的空值判断都用 EMPTY(birth)。写入空日期时用 {}不建议用 CTOD()因为不同系统日期格式下 CTOD 解析可能翻车。IF EMPTY(student.birth) REPLACE student.birth WITH {} ENDIF实例文档附带的源码里凡是出现“日期不显示”“日期对不上”的反馈十有八九都是空日期类型混用造成的。统一用 EMPTY 判断后这类问题基本绝迹。5.3 备注字段在表格里只显示“Memo”现象用表单的 Grid 显示表时备注字段那一列全部显示为固定的“Memo”字样业务文本根本看不到。原因Grid 列默认绑定字段名时备注型字段被当成类型标志展示要显示内容必须把列里的文本框控件绑定到具体的字段属性。解决在 Grid 的列对象里找到 Text 控件把它的 ControlSource 设置为表名.备注字段名例如 student.memo。如果不想让用户直接编辑备注再把列的 ReadOnly 设为 .T.。这个操作不在设计器里做时只改列标题内容永远是“Memo”。5.4 SEEK 查到了完全不相干的记录现象用 SEEK 按学号查找返回的第一条记录学号完全不一样或者同一个 SEEK 语句在两次运行里结果不同。原因表有多个索引当前活动索引不是你想用的那个。VFP 会用最近一次 SET ORDER 指定的索引如果没指定则可能沿用上次会话的残留状态。解决每条使用 SEEK 的程序段前先显式 SET ORDER TO 目标索引标识。对不信任的现场还可以加一句 SET ORDER TO 0 取消索引强制回到物理顺序再用 LOCATE 做慢速查找排障。5.5 换台电脑打开库提示找不到表所属的数据库现象把项目文件夹整个复制到另一台电脑双击表单或执行 DO FORM 时报错提示表 xxxx 所属的数据库不存在或无法打开。原因表文件 DBF 头里记录了它所属数据库 DBC 的相对路径或文件名。直接复制 DBF 而没复制对应的 DBC 时表被检测到“所属库丢失”。解决连同 .DBC、.DCT、.DCX 三个数据库容器相关文件一起复制如果只要表数据可以先执行 FREE TABLE把表从数据库中移除让它变成自由表这样就不再依赖 DBC。但要注意FREE TABLE 后字段规则和永久关系全部失效只适合导出数据做临时分析。FREE TABLE student这是一个不可逆操作执行前备份原库。实例文档里的“附图”如果是在你本机正常换台机器就报错第一条检查路径就是这句话。6. 提交前的一个自检技巧写一段短程序验证增删改查6.1 用 RECCOUNT 和 SQL 做冒烟验证表单能打开、能点按钮不代表增删改查全部正常。最常见的坑是“保存按钮能点但数据没进表”。为了不靠肉眼逐条核对我习惯在交付前写一段自检程序用记录数的变化和查询结果来断言功能是否正常。这段程序不是正式功能只是开发期验证用的脚手架但它能在两分钟内覆盖增删改查四条路径。* check.prg交付前冒烟验证 CLEAR OPEN DATABASE testdata USE student EXCLUSIVE oldCount RECCOUNT() * 增插入一条测试记录 INSERT INTO student (sno, sname, sex, score) ; VALUES (test001, 自检, 男, 60) IF RECCOUNT() oldCount 1 ? 插入失败 ELSE ? 插入通过 ENDIF * 改把测试记录成绩改为80 UPDATE student SET score 80 WHERE sno test001 * 查验证修改结果 SELECT score FROM student WHERE sno test001 INTO CURSOR v IF v.score 80 ? 修改与查询通过 ENDIF * 删逻辑删除并物理清理 DELETE FROM student WHERE sno test001 PACK ? 自检完成 CLOSE DATABASERECCOUNT() 返回表里的物理记录数注意在 SET DELETED ON 状态下逻辑删除的记录仍被计算所以插入验证要用物理记录数。INTO CURSOR v 后用 v.score 读取查询结果前提是查询结果只有一条记录如果查到多条应该用 SELECT v.score 或 v.score[1] 取第一条。这段自检程序每次都在正式功能开发完后跑一遍能在 10 秒内告诉我是哪一步出了问题。6.2 养成提交前看文件依赖的习惯除了跑自检程序我还会检查一遍项目文件清单是否有 .DBC、.DCT、.DCX、.DBF、.CDX、.FPT 全部就位数据库容器路径是否被写死表单是否引用了临时表。很多 VFP 实例“交到老师手里打不开”的问题根源不是代码逻辑而是漏交了 .DCT 或 .FPT 文件。你可以在自检程序里加一个 FILE() 判断来兜底IF FILE(testdata.dbc) AND FILE(student.dbf) AND FILE(student.cdx) ? 主文件完整 ENDIF这套习惯不是我一开始就有的是吃过几次“明明本机能跑换个目录就崩”的亏之后长出来的。做 VFP 数据库系统开发实例最容易被忽略的不是怎么写代码而是怎么保证代码所依赖的表、索引、库容器和备注文件始终在一起。先把自检程序跑绿再打包目录交活时能省掉大半解释工作。希望这个判断清单和自检程序能帮到你让你第一次在别人机器上运行实例时也能一次通过。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑