资讯动态

学生成绩管理数据库系统:从E-R建模到SQL落地的完整设计指南

发布时间:2026/10/9 22:22:32 来源:尧图企业网站定制
简介这份数据库实验大作业完整呈现学生成绩管理系统的设计过程适合高校计算机、信息安全等专业学生在完成数据库课程设计或实验报告时参考。文档以需求分析为主线覆盖编写目的、信息需求、系统功能框架、运行环境、用户特点及按管理员、教师、学生三类角色划分的功能分解并给出了系统管理、信息管理、成绩管理等核心模块的业务流程与输入输出说明可作为撰写同类管理系统需求规格说明书的结构范本。资源为单个docx文档全文约928KB内容排版规范便于直接查阅或按需修改复用。已有907人学习下载适用于需要快速梳理数据库系统设计思路、补全实验报告或了解角色权限与功能模块划分的读者。1. 学生成绩管理数据库系统为什么说它是数据库实验的最佳练手对象打开数据库实验大作业的题目清单学生成绩管理系统几乎是出镜率最高的一个。老师喜欢它是因为学生、课程、成绩三个实体能把关系模型里的 1:1、1:N、M:N 三种关系全部覆盖还牵涉外键约束、级联策略、统计查询、视图和存储过程一门课里九成的考点都能塞进这个小系统学生也喜欢它是因为业务足够直观不需要任何行业背景就能理解需求和建模。但恰恰是这种看起来简单的题目最容易在细节上翻车成绩表主键怎么定、外键级联怎么配、AVG 遇到 NULL 会不会把平均分算错、视图建完别的用户能不能访问每一个都是答辩现场的高频追问。这篇文章就按需求建模 → 建表落地 → 数据操作 → 踩坑复盘这条完整实验路径给出一套可以直接交作业的数据库设计方案也会告诉你哪些地方值得在报告里多写两笔。2. E-R 模型到关系模式成绩表该不该单独建模2.1 三个实体的边界学生、课程与成绩的联系学生成绩管理系统的核心业务很透明一个学生可以选多门课程一门课程可以被多个学生选修所以学生实体和课程实体之间是典型的多对多M:N关系。在 E-R 图里要画出学生实体、课程实体以及在两者之间用菱形框表示的选修联系联系上挂着一个重要属性 grade成绩。注意成绩不是学生的属性也不是课程的属性它是选修这个动作产生的结果。这个判断会影响后面每一次 SQL 设计。如果把 grade 塞进学生表一个学生选十门课就要存十行同一行的姓名、性别、系别就得跟着重复十次数据冗余一眼可见如果塞进课程表一门课几百个学生几行记录就把这张表撑得没法看。所以 grade 作为联系的属性必须单独落到一张成绩表里。很多人会在这一步纠结联系到底要不要单独建表这里有一个可以反复使用的判断标准联系是否携带自己的独立属性。学生和课程之间的联系携带了 grade说明这个联系本身有存在价值必须独立成表如果联系不携带任何属性比如班主任管理班级这种一对一的联系那就完全可以并入实体表。把这个标准写进实验报告导师会觉得你是真的理解了 E-R 建模的本质而不是照着课件抄图。字段类型的选择同样值得多写几句。学号一般用 CHAR(10) 而不是 INT原因有三个学号可能包含字母比如研究生学号常带学位代码后缀INT 类型会吃掉前导零不少学校学号就是 0 开头的学号没有数值计算的语义不该用数值类型。年龄用 TINYINT UNSIGNED 就够一个字节存不了 255 岁也不会浪费存储。性别用 CHAR(1) 加 CHECK 约束或者干脆用 ENUM(男,女)。这一层为什么选这个类型的理由写进文档是数据库设计和随便建张表的重要区别。2.2 关系模式转换规则M:N 联系如何拆成三张表E-R 图确认后接下来要把它转成关系模式。三个实体加一个联系我一般会这样转学生表 student学号 sno 作为主键姓名 sname、性别 sex、年龄 age、系别 dept 作为普通属性。 课程表 course课程号 cno 作为主键课程名 cname、学分 credit、开课学期 semester 作为普通属性。 成绩表 sc把选修联系转成一张独立表学号 sno 和课程号 cno 分别作为外键引用 student 和 course 的主键。在 sc 表里sno 和 cno 的联合构成了候选键。这里必须做一个设计决策为什么用联合主键而不是单独加一个自增 id理由是业务规则——同一名学生同一门课程最多只能有一条成绩记录联合主键能从数据库层面直接阻止重复选课记录的写入。如果换成自增 id应用层判断漏掉一次数据导入时重复记录就会悄悄进来同一个人同一门课出现两行成绩统计平均分时数据直接翻倍。这个点老师特别喜欢问答上来基本等于这道设计题拿到高分。关系模式的规范化也建议顺手检查一遍。student 表里如果学号 sno 决定系别 dept而系别 dept 又决定系主任 dean那么 dean 就是传递依赖不满足第三范式。实验课程里为了控制表数量通常会把系主任这个属性省掉这是合理的简化但报告里要主动写一句本设计为降低复杂度未拆分 dept 实体比被老师追问时支支吾吾要体面得多。课程实验的评分点往往不在你建了几张表而在于你知不知道为什么不建那张表。2.3 成绩表主键设计联合主键 vs 自增主键的取舍联合主键不是没有代价。如果成绩表未来要支持补考记录、重修刷分这类场景同一名学生同一门课需要出现多条记录联合主键就会挡住合法数据。常见做法是在应用层设计里保留联合唯一索引 UNIQUE KEY uk_sno_cno把主键换成自增 id这样既保留业务上的唯一性约束又给未来扩展留了余地。写实验报告时我会把两种方案的取舍对比列成一个表格主键方案优点缺点适用场景联合主键 (sno, cno)阻止重复记录、语义直观、建表简单将来要存重修记录会被主键约束挡住课程实验一门课对应一条最终成绩自增主键 唯一约束扩展性好插入不依赖业务字段需要额外维护唯一约束否则重复数据会进入生产系统或需要保留多轮成绩的场景课程实验一般只要求记录本学期最终成绩联合主键够用且更好讲解我也建议交作业时直接用联合主键。但可以在报告末尾留一句话若后续需要支持重修记录可将主键改为自增 id并保留 (sno, cno) 唯一约束。这一句话就能显示你考虑过扩展性很多同学的报告缺的正是这个。3. 建库建表脚本把数据库设计落到 SQL3.1 建库与字符集utf8mb4 和 InnoDB 为什么是默认答案开始建库前有两个前置决策字符集和存储引擎。我的建议是直接以 utf8mb4 InnoDB 起步这是绝大多数实验环境的默认组合也最不容易在后续踩坑。CREATE DATABASE IF NOT EXISTS student_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;为什么是 utf8mb4因为 MySQL 里的 utf8 最多只能存 3 个字节遇到 emoji 或某些生僻字直接报错或变乱码utf8mb4 是完整的四字节字符集兼容所有输入。utf8mb4_unicode_ci 是排序规则ci 表示大小写不敏感适合成绩管理这种不需要区分大小写的业务。如果实验环境要求学生姓名按拼音排序可以换成 utf8mb4_zh_0900_as_cs但普通实验没必要折腾。建库时顺手带这两个参数并在报告里解释一句这个分就拿到了。存储引擎方面InnoDB 是唯一支持外键约束的常用引擎也支持事务。课程实验如果要求必须用外键MyISAM 可以直接排除。原因很硬MyISAM 不支持 FOREIGN KEY 语法即使建表时不报错外键也不会真正生效数据完整性完全靠应用层自觉维护。第 3.3 节讲到的所有级联策略都必须建立在 InnoDB 之上否则就是空中楼阁。3.2 三张核心表的 DDL约束到底加在哪一层下面是三张表的完整建表脚本每段都带注释。建议粘贴时保留注释后面写实验报告直接能抄。-- 学生表学号为主键性别加 CHECK 约束 CREATE TABLE IF NOT EXISTS student ( sno CHAR(10) NOT NULL COMMENT 学号, sname VARCHAR(20) NOT NULL COMMENT 姓名, sex CHAR(1) NOT NULL DEFAULT 男 COMMENT 性别, age TINYINT UNSIGNED COMMENT 年龄, dept VARCHAR(30) COMMENT 系别, PRIMARY KEY (sno), CHECK (sex IN (男, 女)) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学生基本信息表;逻辑说明sno 用 CHAR(10) 是因为学号长度固定且前导零有意义sname 用 VARCHAR(20) 是因为姓名长度不固定用变长类型省空间sex 的 CHECK 约束在 MySQL 8.0.16 之后才真正强制旧版本写 CHECK 会被静默忽略这一点会在第 5 章展开说明。PRIMARY KEY (sno) 声明主键后InnoDB 会自动为主键建立聚簇索引按学号查询不需要额外索引。-- 课程表课程号为主键学分范围约束MySQL 8.0.16 生效 CREATE TABLE IF NOT EXISTS course ( cno CHAR(6) NOT NULL COMMENT 课程号, cname VARCHAR(40) NOT NULL COMMENT 课程名, credit DECIMAL(3,1) NOT NULL DEFAULT 2.0 COMMENT 学分, semester TINYINT NOT NULL COMMENT 开课学期 1-8, PRIMARY KEY (cno), CHECK (credit 0 AND credit 10) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT课程信息表;逻辑说明credit 用 DECIMAL(3,1) 而不是 FLOAT是因为学分可能出现 0.5、1.5 这种半分而 FLOAT 在比较时会暴露二进制精度误差DECIMAL 是精确类型适合学分这种需要精确等值比较的字段。semester 用 TINYINT 存 1 到 8对应本科四年的八个学期也方便后面按学期筛选开课情况。cno 长度定为 6 位一般课程编号加院系代码足够。-- 成绩表联合主键 双外键成绩默认先存 NULL CREATE TABLE IF NOT EXISTS sc ( sno CHAR(10) NOT NULL COMMENT 学号, cno CHAR(6) NOT NULL COMMENT 课程号, grade DECIMAL(5,2) COMMENT 成绩未录入时为空, PRIMARY KEY (sno, cno), KEY idx_cno (cno), CONSTRAINT fk_sc_sno FOREIGN KEY (sno) REFERENCES student (sno) ON DELETE CASCADE, CONSTRAINT fk_sc_cno FOREIGN KEY (cno) REFERENCES course (cno) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT选课成绩表;逻辑说明联合主键保证同一个人同一门课最多一行记录这是第 2 章设计决策的落地。KEY idx_cno 是额外加的二级索引因为按课程号查成绩是高频操作而联合主键 (sno, cno) 的索引最左前缀从 sno 开始单独查 cno 时索引派不上用场会走全表扫描。grade 默认给 NULL 表示已选课但成绩未录入这在业务上比给 0 更准确0 分会被平均分统计吃掉NULL 会被 AVG() 自动忽略。至于忽略还是纳入统计要看场景第 5 章细说。3.3 外键级联策略CASCADE、SET NULL 与 RESTRICT 怎么选很多同学建 sc 表时照抄课件写了两个 ON DELETE CASCADE没有想过后果。CASCADE 的含义是删除 student 表中的某个学号时sc 表中该学号的所有选课记录会自动删除。对学生退学要清空成绩这个需求是合理的但对成绩归档来说就是灾难——你只想清理一条账号结果把一堆历史成绩全清掉了。我的习惯是分三种场景处理。第一种实验要求严格控制数据完整性、以简洁为主学生删除成绩跟随删除课程删除成绩也跟随删除那就全部 CASCADE交作业最省事。第二种课程取消了但成绩需要保留在档案里课程侧外键改成 ON DELETE RESTRICT让数据库拒绝删除仍有成绩引用的课程记录或者用逻辑删除、加一个 is_deleted 字段代替物理删除。第三种学生转系但成绩要保留外键改成 ON DELETE SET NULL前提是 sc.sno 允许为空这跟联合主键天然冲突实际很少能落成但设计讨论里值得写一句。级联策略删除父表记录时子表的行为适合场景主要风险CASCADE子表关联记录一并删除学生退学、清空测试数据误删成绩数据删除前必须确认RESTRICT子表有关联记录时拒绝删除父表课程档案、成绩归档删除顺序必须先删子表再删父表SET NULL子表外键字段置空解除归属但不删数据外键列需允许 NULL联合主键场景基本不适用这三张表加级联策略是整套系统的骨架。答辩时老师基本都会从外键开始问建议把建表脚本连着演示一遍先插入父表数据再插入子表数据然后现场试一次删除操作看级联效果这样的演示比任何PPT都有说服力。4. 插入与查询成绩统计的常用 SQL 写法4.1 测试数据插入顺序与外键依赖建完表的第一件事是造数据。如果直接往 sc 表插入外键约束会立刻报错因为被引用的 student 或 course 表里还没有对应记录。插入数据的顺序和建表顺序相反先父表后子表。-- 先插学生 INSERT INTO student (sno, sname, sex, age, dept) VALUES (20230001, A同学, 男, 19, 计算机系), (20230002, B同学, 女, 18, 软件工程系), (20230003, C同学, 男, 20, 计算机系); -- 再插课程 INSERT INTO course (cno, cname, credit, semester) VALUES (C001, 数据库原理, 3.0, 3), (C002, 数据结构, 4.0, 2), (C003, 操作系统, 3.5, 4); -- 最后插成绩 INSERT INTO sc (sno, cno, grade) VALUES (20230001, C001, 88.5), (20230001, C002, 92.0), (20230002, C001, NULL), (20230003, C003, 76.0);这段脚本有个刻意安排的细节第三条成绩记录 grade 是 NULL这是故意造的边界数据。成绩未录入的场景应该允许它在库里存在否则后面就看不到 AVG 对 NULL 的特殊处理了。插入报错时先查父表SELECT * FROM student; 看看学号是否真实存在这是排查外键错误的第一反应。批量插入多条成绩时还要考虑一个原子性问题如果第三条违规、前面的插入已经生效数据就会写入一半。课程实验通常要求演示事务可以在插入多行前加 START TRANSACTION插入完成后 COMMIT出错就 ROLLBACK。这一行代码写进实验报告是数据库事务章节的现成得分点。4.2 分组统计平均分、及格率与分数段分布成绩管理系统的核心输出是统计报表。下面两段 SQL 覆盖了每个学生的平均分和每门课的平均分、及格率这两组最常用的统计-- 每个学生的平均分保留两位小数按平均分降序 SELECT sno, AVG(grade) AS avg_grade FROM sc GROUP BY sno ORDER BY avg_grade DESC; -- 每门课的统计选课人数、平均分、及格人数、及格率 SELECT cno, COUNT(*) AS total_cnt, AVG(grade) AS avg_grade, SUM(grade 60) AS pass_cnt, ROUND(SUM(grade 60) / COUNT(*) * 100, 2) AS pass_rate FROM sc GROUP BY cno;这里有几个必须吃透的点。AVG(grade) 自动忽略 NULL所以 B同学未录入成绩不会把 C 编号课程的平均分拉低如果希望把未录入成绩当 0 分处理需要先写 COALESCE(grade, 0)这两种写法统计结果完全不同。SUM(grade 60) 是布尔表达式求和grade 60 为真时值是 1、为假时是 0SUM 做的是对 1 的计数这比写 SUM(CASE WHEN grade 60 THEN 1 ELSE 0 END) 简洁得多也是 SQL 简洁写法的经典示范。COUNT(*) 统计所有行数、包括 NULL 的行而 COUNT(grade) 只统计非 NULL 行数两者结果不一致时基本可以断定有学生选课没出成绩。分数段分布通常用 CASE WHEN 完成。下面的写法把每门课的成绩切成 A 到 E 五档SELECT cno, SUM(CASE WHEN grade 90 THEN 1 ELSE 0 END) AS grade_a, SUM(CASE WHEN grade 80 AND grade 90 THEN 1 ELSE 0 END) AS grade_b, SUM(CASE WHEN grade 70 AND grade 80 THEN 1 ELSE 0 END) AS grade_c, SUM(CASE WHEN grade 60 AND grade 70 THEN 1 ELSE 0 END) AS grade_d, SUM(CASE WHEN grade 60 THEN 1 ELSE 0 END) AS grade_e FROM sc GROUP BY cno;这段 SQL 把 cno 换成 sno 就变成个人成绩结构分析。答辩时按课程统计分数段和按个人统计各课程分数段是同一个报表的两个维度能现场改出来相当加分。4.3 排名查询窗口函数在成绩分析里的用法排名是成绩管理的高频需求。MySQL 8.0 提供了窗口函数 RANK()、DENSE_RANK()、ROW_NUMBER()实验环境如果是新版本可以直接用。写报告前先执行 SELECT VERSION(); 确认一下版本这能避免写了新语法在旧环境跑不出来的尴尬。-- 按课程分组组内按成绩从高到低排名 SELECT sno, cno, grade, RANK() OVER (PARTITION BY cno ORDER BY grade DESC) AS rank_in_course FROM sc WHERE grade IS NOT NULL;逻辑说明PARTITION BY cno 表示按课程分组ORDER BY grade DESC 表示组内按成绩降序排列。RANK() 遇到同分会产生跳跃排名比如成绩 92、92、88 会得到 1、1、3DENSE_RANK() 不跳跃得到 1、1、2ROW_NUMBER() 强制唯一序号得到 1、2、3。三者的差异是答辩经典问题报告里写上一句班级前三名且并列要都算时用 DENSE_RANK取成绩单唯一第几名时用 ROW_NUMBER比背定义显得专业。如果实验环境是 MySQL 5.7 及以下窗口函数不可用可以改用用户变量模拟排名但要注意变量赋值的执行顺序很诡、容易翻车需要在报告里注明这是旧版本兼容方案。能直接给老师看到窗口函数的版本差异本身就是一次版本意识展示。5. 数据库大作业避坑指南验收时最常见的 5 个翻车现场5.1 成绩表联合主键缺失重修记录直接被覆盖现象成绩表用了自增 id 做主键且没有加唯一约束测试时发现同一个人同一门课能插入两条记录。应用层想做先删后插来刷新成绩删除步骤在异常分支被跳过重复数据就从主键漏洞里钻了进去。原因自增主键不会阻止业务上的重复它只保证 id 不重复不保证学号加课程号不重复。数据库层面没有任何约束能拦截同一 (sno, cno) 的重复写入因为每次插入都生成了一个新 id。解决给 sc 表补一个联合唯一索引ALTER TABLE sc ADD UNIQUE KEY uk_sno_cno (sno, cno); 加完后再插重复记录数据库直接报 Duplicate entry从源头堵住重复。这是最省事的后悔药不需要改应用层代码只改一条 DDL 就能把数据完整性救回来。5.2 建外键失败引擎或字符集不一致现象执行建 sc 表脚本时报 ERROR 1215 (HY000): Cannot add foreign key constraint。检查外键字段类型完全一致却还是建不上。原因外键要求被引用的表必须使用 InnoDB 引擎同时两个关联字段的字符集必须一致。如果 course 表建表时漏写 ENGINEInnoDBMySQL 会用默认引擎 MyISAM而 MyISAM 不支持外键约束。另一常见情况是一张表是 utf8、另一张是 utf8mb4字符集不一致也会报同一个错误码。解决先查两张表的引擎和字符集确认问题出在哪一层SHOW TABLE STATUS WHERE Name IN (student, course, sc); SHOW CREATE TABLE course;看到 ENGINEMyISAM 就执行 ALTER TABLE course ENGINEInnoDB;看到字符集不一致就统一改成 utf8mb4_unicode_ci。这类问题基本都是环境配置导致和业务模型无关排查方向固定照着走一遍就能解决。5.3 AVG 与 NULL成绩为空和 0 分是两回事现象A同学求每门课平均分发现整体结果偏低但说不清原因B同学统计班级及格率算了半天分母总不对。原因A同学插入数据时把未录入的成绩写成了 00 被 AVG() 计入总和把平均分拉低了B同学的表里成绩是 NULL但统计及格率时用 COUNT(grade) 当分母NULL 被忽略导致分母偏小及格率虚高。解决先区分业务语义。选课未考试应该存 NULL缺考或 0 分才存 0。统计时如果要算实考学生的平均分AVG(grade) 是正确选择要算包含缺考学生在内的平均分就要写 COALESCE(grade, 0) 先转成 0 再求平均。每次写统计 SQL 前先问自己一句这个分母应不应该包含未录入成绩的人把答案和对应的 SQL 注释写在一起答辩被问到 NULL 语义时就能直接背出来。5.4 级联删除误伤删学生连带删光成绩数据现象某同学想在测试库清理一条学生记录执行 DELETE FROM student WHERE sno20230099; 后发现 sc 表里大量记录被同时清空整个人愣在原地。原因sc 表外键写了 ON DELETE CASCADE删除父表记录会自动删除子表所有关联记录。开发阶段图省事全表 CASCADE到真要清理数据时才意识到这个设计有多危险。解决成绩数据属于强业务数据建议课程侧外键改成 ON DELETE RESTRICT学生侧保留 CASCADE。修改语句是先删掉原外键再重建ALTER TABLE sc DROP FOREIGN KEY fk_sc_cno; ALTER TABLE sc ADD CONSTRAINT fk_sc_cno FOREIGN KEY (cno) REFERENCES course (cno) ON DELETE RESTRICT;改完后再删课程数据库会直接报错拒绝除非你先把成绩归档或清掉。这个流程走一遍比任何课堂演示都更能理解外键约束到底保护的是什么。5.5 CHECK 约束不生效MySQL 版本对约束的兼容差异现象建表时写了 CHECK (credit 0 AND credit 10)插入一条 credit 为负的记录数据库竟然愉快地接受了。原因MySQL 8.0.16 之前CHECK 约束只做语法解析、不强制检查写上的 CHECK 如同虚设。在不支持 CHECK 强制语义的版本里只能靠触发器或应用层兜底。解决升级到 MySQL 8.0.16 以上是根解。如果实验环境版本固定无法升级可以用触发器实现同样的校验DELIMITER // CREATE TRIGGER trg_check_credit BEFORE INSERT ON course FOR EACH ROW BEGIN IF NEW.credit 0 OR NEW.credit 10 THEN SIGNAL SQLSTATE 45000 SET MESSAGE_TEXT credit must be between 0 and 10; END IF; END// DELIMITER ;逻辑说明这个触发器在插入前校验新数据的 credit不合法就通过 SIGNAL 抛异常中断插入。它和 CHECK 的差别在于CHECK 是声明式约束触发器是命令式约束。报告里写清楚为什么用了触发器而不是 CHECK本身就是专业性的体现。6. 给答辩加分的最后一公里索引合理性与视图验证6.1 索引设计的两个检查方向交作业前我习惯跑一遍 EXPLAIN看高频查询有没有走全表扫描。比如按课程查成绩的语句执行 EXPLAIN SELECT * FROM sc WHERE cnoC001;如果输出里 type 列是 ALL就说明 idx_cno 二级索引没建上。这是答辩时最容易被追问的优化点联合主键 (sno, cno) 为什么不能覆盖按 cno 的查询因为联合索引的最左前缀原则要求查询从 sno 开始单独查 cno 时索引失效。把这条 EXPLAIN 的输出截图放进实验报告比写十行文字都有说服力。另一个检查方向是避免在索引列上做函数运算。比如 WHERE YEAR(create_time)2024 这种写法会让索引失效正确做法是改成范围查询 create_time 2024-01-01 AND create_time 2025-01-01。这两种情况一个体现对联合索引结构的理解一个体现对索引失效场景的敏感都是调优实验里最能拿分的内容。6.2 用视图验证业务规则我为这个系统建了一张成绩明细视图把三张表 JOIN 起来作为所有统计报表的统一数据源CREATE OR REPLACE VIEW v_grade_detail AS SELECT s.sno, s.sname, c.cname, sc.grade FROM sc JOIN student s ON sc.sno s.sno JOIN course c ON sc.cno c.cno;视图的价值在于封装三表 JOIN后续统计语句从视图取数时不用重复写关联也便于在应用层统一权限控制。答辩现场验证方式很简单SELECT * FROM v_grade_detail WHERE cname数据库原理; 然后对着结果说明视图屏蔽了底层表结构变化。如果被问到视图能不能更新要提前想好答案带有 JOIN 的视图一般不可更新需要更新数据时还是得回到底表。最后说一个我自己的习惯每次交大作业前我会把建表脚本在空库上完整执行一遍确认一条龙跑通再复制一份带测试数据的脚本作为独立附件提交。很多翻车现场都发生在老师电脑上首次建表的那一刻因为测试数据里混着本地环境特有的记录。希望这个习惯能帮到你也祝你这次实验不再只是换回一个分数而是真的把数据库设计从头到尾想明白。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑