尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

HTML网页综合项目实战教程:从技术选型到性能优化指南

HTML网页综合项目实战教程:从技术选型到性能优化指南 简介这是一份面向HTML初学者的综合实战训练包帮助学习者通过完整的小型网页项目掌握页面结构搭建、常用标签使用、样式控制与资源管理等核心技能。压缩包共12个文件以HTML页面和JPG效果图为主体积仅1.2MB包含多个可运行的网页实例、对应的完成效果预览以及配套图片素材目录按页面与图片素材分开放置方便对照练习并理解网页引用外部资源的常见方式。资源利用多页面案例串联起标题、段落、链接、图像、表格、列表、表单与按钮等知识点体现出从基础结构到交互元素的递进安排同时涉及CSS样式控制与图片路径管理能够训练学习者独立完成具有完整视觉效果的网页作品。已有566人学习使用对想通过动手操作快速入门网页设计的新手而言是一份轻量且可直接对照练习的实战资料。1. 为什么说 html网页综合项目实战是最难“装会”的一门课如果你准备把“html网页综合项目实战”当一门理论课来背我敢说你大概率会翻车。我见过太多能把table属性倒背如流的同学真要让他在一周内从零交付一个完整的企业产品展示官网时却连该先建css目录还是先写body结构都拿不准。这个标题不是在教你某个单个的新技术而是把 HTML 语义化、CSS 布局、原生 JS 交互、响应式适配和静态部署串成一条完整的交付链路。它的价值很简单用一套综合项目把语法变成作品集。它适合刚学完 HTML/CSS 基础的人也适合要在面试或期末前拿出一份能演示的网页项目的人。你需要的是能做出来、能跑起来、能讲清楚的东西而不是再抄一遍文档。2. 动手前的技术选型静态页面到底选原生 HTML 还是框架2.1 先定项目边界你要做的是“产品展示官网”而不是“克隆一个淘宝”常见做法是先把一个综合项目拆成可交付的页面和模块。我在做这类实战时习惯把目标定成“一个静态产品展示官网”包含导航栏、轮播 banner、产品卡片列表、产品详情弹窗、联系表单、页脚和返回顶部按钮。这个体量刚好能覆盖 HTML 里最有用的语义标签也能把 CSS 的 Flex/Grid、JS 的事件委托和表单校验全部串进去还不会因为需求膨胀而烂尾。技术选型上我强烈建议第一版用原生 HTML5 CSS3 原生 JavaScript不碰 Vue/React 这类框架。原因有三个。第一标题叫“html网页综合项目实战”核心是让你把 HTML 结构写对框架会替你做组件渲染和事件绑定反而把最该练习的部分藏了起来。第二原生文件可以直接用浏览器打开、可以扔到任意 Nginx 或静态托管服务上交付路径最短。第三综合项目后期如果要接入接口原生里 fetch 怎么用、跨域怎么处理这些坑先踩一遍以后用框架时心里才有底。项目目录我一般这样建product-site/ ├── index.html # 首页 ├── css/ │ ├── base.css # 重置样式、设计变量 │ ├── layout.css # header、footer、容器 │ └── components.css # 导航、卡片、弹窗、表单 ├── js/ │ ├── data.js # 模拟产品数据 │ ├── main.js # 页面初始化 │ └── dialog.js # 弹窗和事件委托 └── assets/ ├── images/ └── favicon.svg这个结构的重点是样式不要塞进一个style.css完事按“基础-布局-组件”拆开JS 按职责拆成数据、入口、弹窗三个文件。之所以这样拆是因为综合项目后期一定会出现“样式覆盖”和“全局变量污染”这两类问题先解决文件边界后面排查时就不用从一千行里捞。assets单独放图片和图标部署时可以直接把整个目录上传不需要额外处理。2.2 本地开发不要双击 index.html起一个 HTTP 服务很多新手会用双击 HTML 文件的方式预览这在只有一个文件时没问题一旦项目引入了fetch、ES Module或者按需加载的子资源file://协议会带来各种“玄学”报错——最常见的包括CORS policy和模块加载失败。所以从第一天起就把它当成一个真正的 web 项目在本地起 HTTP 服务。如果你没有装任何 IDE最省事的方式是用 Python 自带的静态文件服务器。在项目根目录执行cd product-site python3 -m http.server 8081然后在浏览器访问http://localhost:8081。这个命令会把你当前目录的index.html作为入口按 MIME 类型正确返回.css和.js这样fetch(./data/product.json)也不会被拦截。8081是端口参数如果被占用可以换成任意一个空闲端口例如8082。注意Windows 上有些版本要用python -m http.server 8081取决于你有没有把python加入 PATH。如果你用的是 VS Code我一般会直接装一个名为Live Server的扩展右键index.html选择“Open with Live Server”它会在编辑器右下角提示一个端口默认是5500并自动打开浏览器。这个扩展的好处是修改css或js后页面自动刷新省去了手工刷新的时间。但要注意Live Server 的本质仍是一个本地静态服务器它不是生产环境别用它去验证 Nginx 的配置或 CDN 的缓存行为。2.3 头部的!doctype html与元信息不是凑数是给浏览器定规矩综合项目最容易晃过去的部分是head。我收到过不少代码!doctype html丢了viewport没有页面在手机上显示得像缩小的 PC 端。记住HTML 文档头是浏览器加载和渲染页面的第一道指令写对了能省掉后面很多兼容性排查时间。一个可以直接套用的最小头部!doctype html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1 meta namedescription content一个涵盖布局、交互与响应式的 HTML 综合实战项目 title产品展示官网 - HTML 综合实战/title link relstylesheet hrefcss/base.css /head body !-- 内容 -- script srcjs/main.js defer/script /body /html逐行说一下关键参数。!doctype html是给浏览器声明当前页面按 HTML5 标准渲染如果不写部分老浏览器会进入怪异模式盒模型和对 CSS 的解析会不一致langzh-cn影响屏幕阅读器的发音和浏览器的翻译建议charsetutf-8必须放在最前防止页面加载到一半中文乱码viewport里的widthdevice-width是让布局视口等于设备宽度initial-scale1是禁止启动时缩放这两个值不写你的响应式布局在手机上一定翻车。脚本部分我特意写了defer。这个属性让 JS 在文档解析完成后、DOMContentLoaded事件前执行这样main.js里可以直接拿到document.querySelector的节点不需要再包一层window.onload。没有defer时脚本会阻塞 HTML 解析如果把它放在head里页面会出现白屏等待放在/body前不阻塞但如果脚本里有异步逻辑加载顺序容易混乱。defer是这里最不翻车的一个方案。到这里你已经有了目录、本地服务器和正确的文档头。下一步就是真正把页面结构搭起来这往往是综合项目里最耗时、也最体现专业度的地方。3. 布局与组件用 Flex 和 Grid 搭出能撑住业务的主框架3.1 先写语义化结构再想样式HTML 也是项目的骨架做综合项目最容易犯的错误是一上来就写div classheader整页全是 div。这样不是不能用而是后续维护时你很难分清哪部分是导航、哪部分是主要内容、哪部分是辅助信息更重要的是屏幕阅读器和搜索引擎只能通过标签语义来理解页面结构。所以我会先把页面的线框转化为这样一段 HTMLheader classsite-header nav classnavbar aria-label主导航 !-- 导航内容 -- /nav /header main section classhero aria-labelledbyhero-title h1 idhero-title产品展示官网/h1 p从零构建一个响应式产品展示页面。/p /section section classproducts aria-labelledbyproducts-title h2 idproducts-title产品列表/h2 !-- 卡片容器 -- /section section classcontact aria-labelledbycontact-title h2 idcontact-title联系我们/h2 !-- 表单 -- /section /main footer classsite-footer p© 2025 产品展示官网/p /footer这里用了header、nav、main、section、footer这些语义标签而不是全部替换成 div。aria-labelledby是把标题和对应的区块绑定屏幕阅读器读到时能说出“产品列表”这个区块名。main每个页面最好只有一个它告诉浏览器和辅助工具这里是页面的主体内容不在导航和版权信息里。section里必须有h2级别的标题因为它表示一个独立的主题分组。这个阶段别急着写 CSS。我一般会先用浏览器开发者工具把这段结构加载一遍确认导航菜单、banner、产品列表、表单的 DOM 父子关系正确再往下写样式。如果你一开始就把header、main、footer全部夹在一个容器里后面想做通栏背景色或者全宽 banner就得不断调整内外边距投入产出比很低。3.2 Flex 做导航Grid 做产品卡片两种布局的适用边界站点导航通常是从左到右一排链接这是 Flexbox 最擅长的场景。下面的代码用一个display: flex来解决水平排列和自动留白不需要项目分别设浮动。.navbar { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 1rem; padding: 0.8rem 2rem; background: #222; } .navbar ul { display: flex; gap: 1.2rem; list-style: none; margin: 0; padding: 0; } .navbar a { color: #fff; text-decoration: none; padding: 0.4rem 0.8rem; } .navbar a:hover { background: rgba(255, 255, 255, 0.15); border-radius: 4px; }align-items: center是让导航项在交叉轴居中justify-content: space-between让左侧 logo 和右侧菜单分别顶到两端gap是设置导航项间距不需要单独给li加 margin。flex-wrap: wrap是给窄屏准备的当菜单项过多时换到第二行而不是把容器撑破。注意这里的ul我设置了list-style: none是因为导航的列表语义并不需要着重号如果你不重置浏览器默认会在每个li前画一个黑点这很影响观感。产品卡片这种多列、等宽、能自适应换行的场景更适合用 Grid。我用一个容器直接建立列轨道.products-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 1.5rem; } .product-card { border: 1px solid #e5e7eb; border-radius: 12px; overflow: hidden; background: #fff; transition: transform 0.2s ease, box-shadow 0.2s ease; } .product-card:hover { transform: translateY(-4px); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08); }repeat(auto-fill, minmax(240px, 1fr))这一行是响应式的核心它让浏览器计算当前容器宽度下能放下多少个至少 240px 的卡片剩下的空间由1fr均分。所以你在 1440px 宽屏上看是 5 列在 768px 平板上是 3 列在手机上是 1 列不需要写断点。这个写法比“手动媒体查询里改 grid-template-columns”要省心得多唯一的边界是当你想要的卡片在某个宽度下必须是固定 4 列时才需要用媒体查询去覆盖。3.3 响应式断点从 360px 到 1440px哪些值必须写进媒体查询Grid 能解决卡片列数但导航和页面整体内边距仍然需要断点。我通常会以 360px、768px、1024px、1440px 为分水岭因为这是最常见的手机、平板竖屏、笔记本和宽屏尺寸。媒体查询的写法有min-width和max-width两种我的习惯是统一用min-width从小到大写这样后面的规则会自然地覆盖前面的而不是反过来。/* 移动端默认样式先写一个窄屏能展开的导航 */ .navbar { flex-direction: column; align-items: flex-start; } .menu-toggle { display: block; } /* 平板及以上横向导航 */ media (min-width: 768px) { .navbar { flex-direction: row; align-items: center; } .menu-toggle { display: none; } } /* 宽屏给 main 设置最大宽度并居中 */ media (min-width: 1024px) { main { max-width: 1200px; margin-inline: auto; } }这里的关键是移动端默认flex-direction: column菜单项竖向排列到了 768px 以上才切回横向。这种“移动优先”的顺序能避免在手机样式里写一堆覆盖 PC 的代码。menu-toggle可以用一个按钮做成汉堡菜单在移动端显示、点击展开菜单内容在桌面端隐藏。注意margin-inline: auto是让主内容在宽屏下居中它等价于margin-left/right: auto但写法更简洁。断点目标设备布局变化 768px手机竖屏单列导航、菜单折叠≥ 768px平板/小笔记本横向导航、产品卡片多列≥ 1024px桌面主容器限宽居中页面留白加大这张表的作用不是让你背而是让你在写媒体查询之前先问自己一句这个断点下到底哪几样东西变化最大如果一个断点只改了某个颜色、没有改变结构那就没有存在的必要。血泪经验是断点越多测试成本越高综合项目里能用 23 个断点解决的事就不要追着设备列表一个个补。4. 让页面真正“动”起来交互模块与前后端联调的四个入口4.1 事件委托别给每个产品卡片单独绑监听产品列表有 12 张卡片的场景很典型新手会写一个循环给每张卡片加addEventListener这样代码能跑但一旦数据从 12 条变成 120 条性能就会肉眼可见地掉。更合理的做法是在父容器上只绑定一次事件让它去判断真正点击的是哪一个子元素。// js/dialog.js const productsGrid document.querySelector(.products-grid); productsGrid.addEventListener(click, (event) { const card event.target.closest(.product-card); if (!card) return; const productId card.dataset.productId; const product productData.find((item) item.id Number(productId)); if (!product) return; openDialog({ title: product.name, desc: product.description, price: product.price, }); });event.target是用户实际点击的元素它可能是卡片里的图片、标题或“了解更多”按钮直接用closest(.product-card)能上溯到最近的卡片祖先。dataset.productId是卡片上的>article classproduct-card>const contactForm document.querySelector(#contactForm); contactForm.addEventListener(submit, async (event) { event.preventDefault(); const nameInput contactForm.querySelector(#name); const emailInput contactForm.querySelector(#email); const messageInput contactForm.querySelector(#message); const errors {}; if (nameInput.value.trim().length 2) { errors.name 姓名至少填写 2 个字符; } const emailPattern /^[^\s][^\s]\.[^\s]$/; if (!emailPattern.test(emailInput.value.trim())) { errors.email 请填写正确的邮箱格式; } if (messageInput.value.trim().length 10) { errors.message 留言不少于 10 个字符; } if (Object.keys(errors).length 0) { renderErrors(errors); return; } const submitBtn contactForm.querySelector(button[typesubmit]); submitBtn.disabled true; submitBtn.textContent 提交中...; try { const response await fetch(/api/contact, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ name: nameInput.value.trim(), email: emailInput.value.trim(), message: messageInput.value.trim(), }), }); if (!response.ok) throw new Error(提交失败); contactForm.reset(); showToast(提交成功我们会尽快联系你); } catch (error) { showToast(提交失败请稍后重试); } finally { submitBtn.disabled false; submitBtn.textContent 提交; } });这段代码有三层含义。第一层event.preventDefault()必须在验证前调用否则表单会直接用原生方式刷新页面你辛苦收集的输入瞬间消失。第二层校验尽量在提交前完成而不是等后端返回错误再提示用户体验会好很多。第三层submitBtn.disabled true是防止用户连点提交造成重复请求这个参数在生产环境中是最容易被忽视的网络慢的时候用户会疯狂点击后端可能同一时间收到几十条重复数据。fetch的地址/api/contact在本地静态服务器里是不存在的这会直接返回 404让response.ok为 false于是前端就能走“提交失败”分支。这是综合项目里最常见的前后端联调方式先约定接口路径、方法和参数结构后端没就绪时前端用一个本地 Mock 或用十分简单的 JSON 文件返回模拟数据来顶住。我一般会在项目根目录放一个mock/api/contact.json然后用本地服务把/api/contact代理到它但这不是必须的你完全可以在main.js里写死一个 Promise 来模拟成功结果直到后端的真实接口出来再替换。4.3 滚动状态与“一键返回顶部”用 requestAnimationFrame 而不是直接滚综合项目里几乎总会有这么个小按钮页面往下滚动到一定距离后右下角出现“返回顶部”点击后平滑滚回顶部。热搜词里“html一键返回顶部算法”说的就是这个。代码本身不长但有一处细节经常翻车你监听scroll事件时直接改button.style.display滚动事件触发频率极高会导致频繁强制重排。正确做法是合并成状态判断并考虑使用requestAnimationFrame。const backToTopBtn document.querySelector(#backToTop); let isVisible false; window.addEventListener(scroll, () { requestAnimationFrame(() { const shouldShow window.scrollY 600; if (shouldShow ! isVisible) { isVisible shouldShow; backToTopBtn.classList.toggle(show, isVisible); } }); }); backToTopBtn.addEventListener(click, () { window.scrollTo({ top: 0, behavior: smooth, }); });requestAnimationFrame会把滚动回调合并到浏览器下一次绘制前执行避免每次滚动像素都触发一次额外布局。window.scrollY 600里的 600 是一个可调参数页面高度不同出现按钮的时机也不同——你可以在开发者工具的 console 里输入window.scrollY看用户可视区高度然后取“刚好越过第一屏”的值。classList.toggle(show, isVisible)的第二个参数是强制指定状态比“先 if 再 add/remove”简洁。按钮的 CSS 还有一个容易被忽略的细节如果按钮默认display: none过渡动画会失效因为none和其他状态之间无法插值。我通常会让按钮始终在文档流中用opacity: 0; pointer-events: none;隐藏添加.show后改为opacity: 1; pointer-events: auto;这样点击和动画两者都可用。4.4 弹窗的:target与 JS 控制的选型别让一个交互方案绑架全部代码产品详情弹窗的实现有好几种纯 CSS 用:target、用dialog元素、用 JS 控制类名。我的建议是综合项目里直接选dialog或 JS 控制类名避免用:target的原因是它需要改变 URL 的 hash用户关闭弹窗按“后退”时会把整个页面搞得很奇怪。下面是一个可访问性较好的 JS 弹窗结构dialog idproductDialog classdialog div classdialog__content h2 iddialogTitle/h2 p iddialogDesc/p button idcloseDialog typebutton关闭/button /div /dialogconst productDialog document.querySelector(#productDialog); function openDialog(product) { document.querySelector(#dialogTitle).textContent product.name; document.querySelector(#dialogDesc).textContent product.desc; productDialog.showModal(); } document.querySelector(#closeDialog).addEventListener(click, () { productDialog.close(); });showModal()自带焦点陷阱打开弹窗后键盘 Tab 不会跳到弹窗背后的页面关闭后焦点会自动还给触发按钮。这三点是原生div弹窗要写一大堆代码才能实现的。要注意的坑是dialog在部分旧浏览器需要 polyfill不过只要你的目标用户用的是近两年的浏览器可以放心用。如果你还在用直接修改style.display的老方案请至少加上aria-modaltrue和roledialog否则读屏用户完全感知不到弹窗存在。5. HTML 综合项目实战避坑指南PC 端与移动端都要踩的 5 个坑综合项目翻车点往往不在疑难杂症而在最基础的行为上。下面这 5 个坑都属于“只要知道一次以后再也不犯”的类型我按从样式到脚本、再到交付的顺序写你对照着自己的项目逐个排查即可。5.1 图片底部多出 3px 空白怎么都去不掉现象在产品卡片里放一张img它和容器底部之间总会有一条 3px 左右的空隙调margin: 0也没用。原因img是行内替换元素默认按文本基线对齐。基线和行框底部之间预留了 descender 高度所以底部会有一条不属于图片本身的空隙。解决在全局样式里给img设置display: block;让图片变成块级元素不再参与行内基线对齐。或者给容器设置font-size: 0但这样会影响后代文字继承我一般只用第一种。这条是综合项目里照片墙和卡片布局最常见的一个“看起来像 bug”的细节我在至少三个作品里见过。5.2 移动端点击菜单没反应还残留 hover 状态现象手机端点导航项时菜单展开/收起偶尔失灵或者按钮点击后一直保持高亮像卡住了一样。原因移动端浏览器在触摸屏上会先触发touchstart和mouseover其中某些版本的浏览器对绑定在click上的事件存在 300ms 延迟同时:hover样式在触摸屏上无法自动取消点击后依然停留在“悬停”状态。解决在全局样式里给触摸设备隐藏 hover 效果只在(hover: hover)的设备上启用事件绑定改用pointerdown/click组合并加touch-action: manipulation来消除双击缩放延迟。注意最简单也最稳妥的是把导航项放在label checkbox 方案里纯 CSS 控制状态不依赖 JS。5.3 页面在手机上变成缩小的 PC 端字体小得需要用两根手指放大现象手机浏览器打开后整个网站像截图缩小一样用户必须双指捏合才能看内容。原因页面head里没有meta nameviewport或写错了参数。移动浏览器默认把视口宽度当成 980px为了把一个 980px 的页面塞进小屏它只能整体缩放。你写的min-width: 768px媒体查询也是在这个 980px 视口下工作的所以所有响应式规则全部失效。解决把meta nameviewport contentwidthdevice-width, initial-scale1放到head最前面并且不要再用maximum-scale1来“禁止缩放”——那会伤到可访问性用户可能有放大文本的需求。这条是综合项目最容易出现的“移动端白干”原因。5.4 多个 JS 文件里的全局变量互相污染改了 A 页面坏 B 页面现象综合项目里有main.js、dialog.js、data.js运行时控制台报Uncaught ReferenceError: xxx is not defined排查半天发现是两个文件都声明了同一个全局变量名的let或const。原因不经过模块化处理、直接用script标签按顺序加载的多个文件共享全局作用域。后声明的会覆盖先声明的如果某个函数在某处提前引用了变量运行顺序稍有变化就会炸。解决用一个全局命名空间包住所有业务代码或者把 JS 文件改成 ES Module用export/import显式传递依赖。下面是最小示例// data.js export const productData [ { id: 1, name: 人体工学椅, price: 399, description: 久坐办公的腰部支撑方案 }, // ...更多数据 ]; // dialog.js import { productData } from ./data.js; export function openDialog(product) { // 使用 productData }把script标签改成script typemodule srcjs/main.js/script即可。模块的好处是变量不再自动挂到window上每个文件有独立作用域而且模块会按依赖顺序加载不会出现“data.js 还没执行完dialog.js 就开始取数据”的问题。需要留神的是ES Module 必须通过 HTTP 服务打开直接在file://下会有 CORS 错误这也是第 2 章强调要起本地服务器的原因。5.5 表单提交后整个页面刷新好不容易填的内容全没了现象点“提交”按钮后浏览器地址栏变成?namexxx或者直接刷新表单内容清空后端调试窗口里根本没有请求。原因在form里点击typesubmit的按钮会触发表单原生提交行为如果action和method没配置就会把字段拼到当前 URL 并刷新页面。这是浏览器默认行为不是代码 bug。解决在submit事件回调里第一行必须调用event.preventDefault()。如果你用的是框架还要注意框架的submit或onSubmit是否已经阻止默认事件。另外把提交按钮的type从submit改成button再自己监听click也是一种办法但会失去回车键在输入框里触发表单的天然体验所以我不推荐。6. 交付前的最后一道工序用 Lighthouse 和真机把性能与兼容性做扎实项目做出来不等于能交付。我见过太多代码在笔记本上能跑一上手机卡成 PPT或者 Chrome 里正常、Safari 里图标乱飞。最后这道工序我建议你固定成三步。第一步用 Lighthouse 跑一份基线报告。在 Chrome 开发者工具的Lighthouse标签页里选择“移动端”模式跑一次评分。重点看三项Performance里有没有大尺寸图片、未压缩的 JSAccessibility里有没有缺少alt和表单labelSEO里是不是只有一个h1。这三个指标是最能影响“别人愿不愿意用”的分数。跑完后针对失败项改图片加loadinglazy、给容器设定aspect-ratio、把字体预加载换成font-display: swap。第二步真机验证而不是只在模拟器里划拉。在 Chrome 的设备模拟器里看布局很准但触摸响应和滚动的手感模拟器永远代替不了真机。我的一般做法是用同一个局域网手机访问本地服务器的 IP 地址具体方式是先拿到电脑的局域网 IP再在 Python 服务器命令里指定--bind 0.0.0.0让手机和电脑在同一 WiFi 下访问。这样能暴露出模拟器发现不了的问题例如字体在小屏端显得过小、点击区域不够 44px 高、系统字体和抗锯齿差异导致的行高变化。第三步做键盘可达性检查。综合项目做完后我从底部开始按 Tab 键一轮轮走链接顺序是否符合阅读顺序弹窗打开时焦点是否移进去关闭后焦点是否回到触发按钮这三关过了可访问性才算是真的做完了而不只是加几个aria-label。我自己的教训是以前总觉得“页面能滚动、能点、能提交”就算完成直到有用户反馈键盘根本跳不到某个导航项才发现是自定义滚动容器没有设置tabindex-1。把这件事当成验收标准能帮你在作品集里少一个黑匣子。希望这些步骤能让你手头的 html网页综合项目实战真正变成一个能拿出来演示、能讲清楚取舍的作品。如果你在最后一步发现性能分总在 70 分上不去那大概率不是某个技巧没学到而是图片或第三方库太沉了回头砍掉一个不需要的花哨组件分数会比调十行配置更明显。希望帮到你。本文还有配套的精品资源点击获取
返回列表