资讯动态

Vue3+Vite打包体积优化实战:从2.8M到500K的完整方案

发布时间:2026/9/14 19:55:27 来源:尧图企业网站定制
先说个真实场景。上个月接手一个 Vue3 后台管理系统用的是 Vite 做构建工具功能其实不算复杂登录鉴权、用户管理、订单列表、数据报表、还有几个大屏展示页。但同事提了个问题——每次npm run build之后打包出来的 dist 目录里就躺着一个 2.8M 的 JS 文件加载时首屏白屏两三秒客户在低配置电脑上打开尤其明显。我当时第一反应是这项目到底塞了多少东西能把一个前端应用打到 2.8M把这个问题排查下来其实整个过程挺典型的。一番操作之后打包体积从 2.8M 降到了 500K 左右优化幅度超过 80%。这篇文章就把完整方案写出来包括每一步的决策逻辑、具体配置、还有我踩坑的过程。文章覆盖的核心关键词是 Vue3、Vite、打包体积优化适合的人群主要是用 Vite 构建 Vue3 项目的开发者、遇到首屏加载慢问题的前端工程师、以及想系统了解构建优化思路的人。1. 优化前先看现状定位体积“大头”在哪1.1 环境与项目情况说明先交代一下项目背景。技术栈是 Vue 3.2 Vite 3 Vue Router 4 Pinia Element Plus ECharts Axios路由大概 30 多个页面其中大概 8 个页面用到了 ECharts 图表4 个页面引用了 Element Plus 的表格、弹窗、表单等组件。整个项目没有做任何分包和懒加载处理所有组件都在 main.js 里集中注册。当时在本地执行构建命令npm run build输出结果大概是这样的dist/index.html 0.46 KB dist/assets/index-xxxx.js 2798.32 KB / gzip: 812.36 KB dist/assets/index-xxxx.css 156.22 KB / gzip: 24.15 KB2798KB 的 JS 文件gzip 之后也有 812KB。这个体积放到服务器上不考虑 HTTP 缓存的情况下用户每次访问首屏都要下载将近 1M 的压缩资源再加上浏览器解析执行 JS 的时间白屏时长可想而知。我去查了浏览器开发工具里的网络面板发现这个 JS 文件下载耗时其实还行本地 100M 带宽测试但脚本执行和解析花了将近 2 秒。也就是说问题不只是网络传输还有浏览器在启动阶段就要去解析、编译这个巨大的 JS bundle。这也是为什么大家都说“打包体积越大首屏越慢”的根本原因。1.2 用构建分析工具给打包产物做“体检”做体积优化第一步绝对不是凭感觉去猜哪块大而是用工具看清楚每个模块占了多少体积。这里推荐一个我用了好几次的插件rollup-plugin-visualizer。安装命令npm install -D rollup-plugin-visualizer在vite.config.js里配置import { visualizer } from rollup-plugin-visualizer; export default defineConfig({ plugins: [ vue(), visualizer({ open: true, gzipSize: true, brotliSize: true, filename: analyze.html }) ] });重新执行npm run build构建结束后会自动打开一个analyze.html的交互式图表页面。这个页面会以矩形树图的方式展示所有模块体积鼠标移到色块上就能看到具体是哪个依赖、占了多少字节、gzip 之后又是多少。我当时看到分析结果时基本可以一眼锁定问题依赖包打包体积未压缩占比echarts1094 KB39.1%element-plus674 KB24.1%vue-router / pinia / vue346 KB12.4%axios / dayjs / lodash-es262 KB9.4%业务代码及其他422 KB15%一个 ECharts 全量包就占了接近 40%。Element Plus 全量引入也占了将近四分之一。这两个是绝对的大头优先处理它们收益会非常明显。1.3 从分析结果判断后续优化优先级很多人看到体积大就直接上 CDN其实这是不严谨的。我的建议是先判断“哪些体积是可以避免的”再判断“哪些体积是必须传输的”。ECharts 全量引入的问题在于项目里只用到了折线图、柱状图、饼图这几个常用图表但全量包会把所有地图、所有图表类型、所有组件都打包进来。这就好比去超市买一瓶酱油结果把整个货架都搬回家了。按需引入可以把 ECharts 的体积压缩到 300KB 左右如果能配合 gzip最终传输只有 100KB 上下。Element Plus 全量注册的情况也类似。项目中用到的组件大概 20 个左右但全量引入会让所有组件都进入 bundle。而且还要注意一个问题Element Plus 的样式文件也是大头如果按需引入组件但不按需引入样式CSS 体积也不会降下来。Vue 相关的依赖体积算是比较合理的不需要做太多处理但它们可以通过拆包策略共享到单独 chunk这样配合浏览器的缓存策略能明显提升二次访问体验。整体判断下来优化优先级是这样排的第一优先ECharts 按需引入第二优先Element Plus 按需引入第三优先路由懒加载第四优先构建拆包 gzip这个优先级是按照“体积下降幅度 × 改动风险”来排的。ECharts 和 Element Plus 改动只影响业务代码的引用方式不影响构建配置风险较低。路由懒加载改动每个页面的引入方式工作量略大。构建拆包和 gzip 是纯配置层面的事随时可以加。2. 优化方案的选型与取舍为什么这么做2.1 先从“路由懒加载”开始路由懒加载的实现方式本质上就是利用 Vite底层是 Rollup对动态import()的支持。把原来在路由配置里直接 import 组件改成箭头函数返回动态 import// 优化前所有组件都会打包进同一个 chunk import UserManage from /views/user/UserManage.vue; // 优化后组件会在路由被访问时才加载 const UserManage () import(/views/user/UserManage.vue);如果把所有路由都改成这种写法原来 2.8M 的 index.js 会按路由被拆成若干个更小的 chunk。用户访问首页时只加载首页相关的 JS访问用户管理页时才加载用户管理页的 JS。这个方案之所以放在前面是因为它不改变代码逻辑、不改变依赖体系、不引入外部服务纯粹是改变模块的加载时机。收益很稳定风险几乎为零。而且它带来一个额外的好处不同页面之间各自独立修改其中某页的代码后再发版浏览器可以只重新下载这个小 chunk配合强缓存策略老用户的下一次访问也会更快。如果你用的是 Vue Router还有一个细节值得注意在createWebHistory与createWebHashHistory的选择上懒加载不受影响但历史模式会要求服务器做 history fallback 配置这点在后端部署时容易踩坑下面第四部分会专门说。2.2 第三方库“按需引入”的收益边界按需引入并不是所有场景都有收益一定要分情况看。如果是 UI 组件库比如说 Element Plus按需引入的收益取决于你这个项目用了它多少组件。用得多按需引入省得少用得少省得多。而且现在 Element Plus 官方给出了一个比较省事的方案配合unplugin-vue-components和unplugin-auto-import插件做自动按需引入。插件会在编译阶段扫描模板中用到的组件自动 import 对应的组件和样式。npm install -D unplugin-vue-components unplugin-auto-import配置到 vite.config.js 里import Components from unplugin-vue-components/vite; import AutoImport from unplugin-auto-import/vite; import { ElementPlusResolver } from unplugin-vue-components/resolvers; export default defineConfig({ plugins: [ vue(), AutoImport({ resolvers: [ElementPlusResolver()] }), Components({ resolvers: [ElementPlusResolver()] }) ] });这个方案用起来确实香我不用手动去写import { ElButton } from element-plus模板里直接写el-button就行。插件会自动注入。但实际上我自己在项目里没有直接用这个自动方案而是选择手动按需引入。原因是这个项目里用了很多 Element Plus 的指令比如v-loading和消息提示组件ElMessage、ElMessageBox这些组件不是写在模板里的自动按需引入插件对它们的处理偶尔会漏。手动按需引入虽然代码看起来啰嗦一点但可控性更高。手动按需引入的做法是这样的import { ElButton, ElTable, ElDialog, ElMessage, ElMessageBox } from element-plus; import element-plus/es/components/button/style/css; import element-plus/es/components/table/style/css; import element-plus/es/components/dialog/style/css;注意样式文件要单独引而且路径要对应组件。这个写起来确实繁琐但胜在一目了然。如果是图表库 ECharts按需引入的收益会相当可观。因为 ECharts 是按模块设计的你可以只注册需要的图表类型和组件。完整写法下一部分细说。这里要特别提醒一句如果项目里用了超过 30 个 Element Plus 组件按需引入和不按需引入的体积差距会变得很小。这时候考虑分析一下是不是应该把整个组件库拆到单独的 chunk让浏览器走缓存。说到底按需引入是减少“首次加载”的体积拆包是提高“二次访问”的速度两者目标不同混着用才合理。2.3 CDN 外部化适合在什么条件下做把一些体积大、更新频率低、基本不会变的第三方库通过 CDN 加载是前端性能优化的经典方法。它的本质是让构建工具把某些依赖标记为 external不打包进产物然后在 index.html 里通过script标签引用外部链接。这个方案的优势是极端的“首包小”——比如 vue、vue-router、pinia、echarts 这些库都走 CDN业务代码可能只有几百 KB。劣势也很明显需要外网能访问到 CDN 域名内网部署环境下这是硬伤一旦 CDN 挂了或者某个依赖更新后出现兼容问题排障成本很高开发环境下 external 容易引入跨域问题需要额外处理我的个人建议是如果项目部署在公网的服务器上CDN 方案可以试如果是内网环境或者对资源可控性要求很高那建议用“本地静态资源 拆包”的方式把高频依赖进行单独 chunk并设置强缓存。在 Vite 里配置 externals 其实不算最直观因为 Vite 的构建底层是 Rollupexternal 属于build.rollupOptions的配置项。对于一个用 CDN 的 Vue3 项目配置大致是这样的export default defineConfig({ build: { rollupOptions: { external: [vue, vue-router, pinia, echarts], output: { globals: { vue: Vue, vue-router: VueRouter, pinia: Pinia, echarts: echarts } } } } });同时写一个公共方法去动态插入 script 标签加载这些 CDN 文件const cdnScripts [ https://unpkg.com/vue3.2.47/dist/vue.global.prod.js, https://unpkg.com/vue-router4.1.6/dist/vue-router.global.prod.js, https://unpkg.com/pinia2.0.36/dist/pinia.iife.prod.js, https://unpkg.com/echarts5.4.3/dist/echarts.min.js ]; function loadCdnScript(src) { return new Promise((resolve, reject) { const script document.createElement(script); script.src src; script.onload resolve; script.onerror reject; document.head.appendChild(script); }); } // 在应用入口挂载前加载 async function bootstrap() { await Promise.all(cdnScripts.map(loadCdnScript)); createApp(App).use(router).use(pinia).mount(#app); } bootstrap();说实话这套做法我自己在正式项目里用过效果确实立竿见影但它不是没有坑。下面第四部分说到“CDN 引入但被重复打包”这个问题时会展开讲。3. 实操从 2.8M 到 500K 的完整落地3.1 路由懒加载改造后的中期数据先看路由懒加载这步单独的效果。我改造完所有路由后重新构建产物从单个index-xxxx.js变成了多个 chunk结构大概是这样的dist/index.html 0.46 KB dist/assets/index-xxxx.js 821.33 KB / gzip: 236.21 KB dist/assets/UserManage-xxxx.js 356.51 KB / gzip: 92.18 KB dist/assets/DataReport-xxxx.js 184.22 KB / gzip: 48.44 KB dist/assets/Login-xxxx.js 42.33 KB / gzip: 12.08 KB dist/assets/vendor-xxxx.js 276.89 KB / gzip: 86.12 KB整体看来首屏加载的 JS 从 2.8M 降到了大约 821KBindex vendor再加上其他异步 chunk至少把首屏必须执行的代码量降了一个量级。这里面 vendor 是 Rollup 根据依赖自动提取出来的公共模块包含 vue、vue-router、pinia、axios 这些。这一步做完我发现一个有意思的现象gzip 后体积下降的比例比原始体积下降的比例更大。原因是 ECharts 和 Element Plus 这类库中重复的代码模式很多gzip 对它们的压缩率本身就很高拆包后每个 chunk 内部的重复度降低压缩效果反而更好。3.2 ECharts 手动按需引入的完整代码这一步是全场收益最大的一块。ECharts 从全量引入改成按需引入体积直接少了 700 多 KB。全量引入的写法很常见很多人会这样import * as echarts from echarts;这样的写法会打包整个 echarts 包。按需引入则要分两步先引入核心模块再注册需要使用的图表和组件。我在项目里建了一个utils/echarts.js统一处理按需注册// 引入 echarts 核心模块 import * as echarts from echarts/core; // 按需引入图表类型 import { LineChart, BarChart, PieChart } from echarts/charts; // 按需引入组件 import { TitleComponent, TooltipComponent, GridComponent, LegendComponent, DataZoomComponent } from echarts/components; // 引入 Canvas 渲染器 import { CanvasRenderer } from echarts/renderers; // 注册必须的模块 echarts.use([ LineChart, BarChart, PieChart, TitleComponent, TooltipComponent, GridComponent, LegendComponent, DataZoomComponent, CanvasRenderer ]); export default echarts;之后在每个业务页面里不再从echarts引入而是从utils/echarts.js引入import echarts from /utils/echarts;这样 ECharts 的体积会从 1094KB 降到大概 300KB 左右。如果你项目中还用到了地图Geo 或 MapChart记得再引入对应的地图数据但注意地图 JSON 数据本身是纯数据文件体积可能非常大强烈建议把地图数据也单独拆出来做异步加载不要和业务代码搅在一起。这里有一个真实的坑要说明按需引入后ECharts 的 tooltip 里如果要使用formatter回调函数并且函数里引用了echarts命名空间下的一些方法比如echarts.format.addCommas那就需要额外引入对应的工具方法。像这样import { format } from echarts/core;这种细节往往在开发环境没问题但在切换成按需引入后突然报Cannot read properties of undefined。排查方式是打开控制台看具体报错点再用全局搜索在代码里定位到echarts.的调用位置逐个确认是否在按需引入白名单里。3.3 构建配置manualChunks 拆包与 gzip 预压缩路由懒加载和按需引入解决的是“代码量”的问题但还有一个“缓存效率”的问题如果把 vue、vue-router、pinia、axios 这些不怎么变化的第三方库塞到业务 bundle 里那么每次业务代码发版后用户都要重新下载整个大文件。所以最好是把它们拆成独立的 vendor chunk设置长缓存。Vite 自带了一些拆包策略但默认行为可能不完全符合预期。我习惯在构建配置里自定义manualChunksexport default defineConfig({ build: { rollupOptions: { output: { manualChunks(id) { if (id.includes(node_modules)) { if (id.includes(vue) || id.includes(pinia) || id.includes(vue-router)) { return vendor-vue; } if (id.includes(axios) || id.includes(dayjs)) { return vendor-utils; } if (id.includes(echarts)) { return vendor-echarts; } if (id.includes(element-plus)) { return vendor-element; } return vendor; } } } } } });这样构建产物里会出现vendor-vue、vendor-utils、vendor-echarts、vendor-element这样的 chunk每个 chunk 内部都是比较稳定的依赖集合。用户第二次访问时这些 chunk 可以命中浏览器强缓存只有业务代码发生变化时才重新拉取。另外一步gzip。Vite 构建默认不会给你生成 .gz 文件需要装插件。npm install -D vite-plugin-compression配置import viteCompression from vite-plugin-compression; export default defineConfig({ plugins: [ vue(), viteCompression({ verbose: true, disable: false, threshold: 10240, algorithm: gzip, ext: .gz }) ] });threshold: 10240表示只有大于 10KB 的文件才会被压缩。这个值可以根据自己的实际产物调整压缩太多小文件收益有限反而增加构建时间。生成 .gz 文件之后还要确认服务器端是否开启了 gzip 或 brotli。如果你用的是 Nginx且没有配置gzip_static on;那服务器会实时压缩再返回虽然也能生效但会消耗 CPU。比较推荐的做法是让 Nginx 开启gzip_static on;这样它会优先读取静态的 .gz 文件直接发送给客户端CPU 开销最低。gzip on; gzip_static on; gzip_min_length 1k; gzip_types text/plain text/css application/json application/javascript application/xml image/svgxml;3.4 CDN 映射与 externals 配置完整示例如果你决定走 CDN 这条路那配置方式要严谨一些。我把当时用的 CDN 版配置拿出来做个演示。先说思路把 vue、vue-router、pinia、echarts 这四个体积大户全部 externals 掉业务代码里正常 import构建时 Rollup 不打包它们而是把 import 映射成window上的全局变量。运行时通过 index.html 里的script加载 CDN 文件确保全局变量已经存在。vite.config.js 核心配置export default defineConfig({ build: { rollupOptions: { external: [vue, vue-router, pinia, echarts], output: { globals: { vue: Vue, vue-router: VueRouter, pinia: Pinia, echarts: echarts } } } }, define: { // 避免开发环境报错 __VUE_PROD_DEVTOOLS__: false } });index.html 中手动添加!DOCTYPE html html langzh-CN head script srchttps://unpkg.com/vue3.2.47/dist/vue.global.prod.js/script script srchttps://unpkg.com/vue-router4.1.6/dist/vue-router.global.prod.js/script script srchttps://unpkg.com/pinia2.0.36/dist/pinia.iife.prod.js/script script srchttps://unpkg.com/echarts5.4.3/dist/echarts.min.js/script /head body div idapp/div script typemodule src/src/main.js/script /body /html使用 CDN 模式时需要注意pinia的全局包名是Piniavue-router的全局包名是VueRouter。globals 里写错会导致运行时找不到对象控制台会报XXX is not defined。还有个坑是 echarts如果你在业务代码里用了echarts/core的按需引入那 CDN 方式下 externals 配置就得拆开细粒度处理否则打包后会报echarts/core is not defined。当时我为了省事CDN 方式下干脆直接把 echarts 指向echarts全量包只在按需引入方案里才用核心模块。CDN 方案做完后构建产物体积很夸张。把所有第三方库都排除后整个 JS 只有大概 250KB 未压缩、80KB 左右 gzip。当然代价是首屏多加载了几个外部 script网络请求数会增加。至于这个 trade-off 值不值取决于你的部署环境、CDN 稳定性和团队对第三方资源的掌控力。我的结论是对公网项目CDN 是很成熟的手段对私网或对稳定性要求极高的系统建议别依赖公共 CDN改成本地 vendor 文件更稳妥。4. 常见问题与排查技巧实录4.1 改了懒加载后按预期生效但首屏变快不明显这种情况一般有两个原因。第一某些页面体积特别大比如数据报表页本身就带了一堆 ECharts 图表整个异步 chunk 的体积可能超过 300KB。这种情况下路由懒加载只是把压力从首屏挪到了点击路由的那一瞬间用户在该页面上的等待时间并没有减少。解决办法是继续对这个页面做组件级懒加载或数据懒加载比如图表组件在进入视口时才渲染。第二首页本身把很多模块同步 import 了。比如很多新手喜欢在根组件 layout 里一次性 import 所有子页面组件这样懒加载就失效了。检查一下首页相关组件里有没有import UserManage from这种同步写法如果你在 setup 里同时 import 了 UserManage 和 DataReport而它们本身是懒加载组件这里不会报错但会让它们被同时拉取。排查方式是看浏览器 Network 面板里的 JS 请求如果一进入首页就有多个业务 chunk 同时加载那就是某个同步链路把异步组件引用了。4.2 按需引入 ECharts 后页面图表直接报错按需引入之后最容易遇到的就是Component series.line not exists. Load it first.这类错误。这通常意味着LineChart没有被注册。但如果你确实注册了还会报这个错那就要检查是不是之前某个文件里用了echarts.init然后传入了line字符串但注册时用的是LineChart的类。写法上echarts.use([LineChart])是正确的不需要额外做映射。还有一类问题出现在 ECharts 5 中tooltip的formatter里用了params的marker或axisValueLabel这些功能都是挂在 TooltipComponent 上的如果你忘了TooltipComponent页面会直接白屏。建议所有使用 ECharts 的页面至少在utils/echarts.js里把常用组件全部注册好不要只注册图表类型而漏了组件类型。4.3 CDN 引入了但打包产物里还是能看到完整依赖这种问题的根因在于 external 配置没有生效。最常见的情况是业务代码里 import 的路径和 external 里声明的模块名不一致。比如import { createRouter } from vue-router如果 external 里写的是VueRouter大小写对不上、路径对不上就不会被识别。还有一种情况是用路径导入的写法import { ElMessage } from element-plus/lib/components/message这种带路径的导入Rollup 不会把它和element-plus这个模块关联起来externals 无法命中。尽量不要写深层路径导入。排查方法很简单构建完之后在 dist 产物里搜vue.global或者echarts.min相关内容如果看到了说明 CDN 文件内容已经被打进去了如果看不到说明 external 生效。另外也可以看 analyze.html 里有没有这些依赖的色块。4.4 gzip 文件生成了但线上体积没降让我碰到的比较典型的坑是vite-plugin-compression生成了 .gz 文件但 Nginx 没有开启gzip_static服务器实时压缩的结果跟预压缩内容不一致。这中间最让人迷惑的是浏览器开发者工具里的 Transfer-Encoding 显示gzip体积看起来还是没变。原因是实时压缩时 Nginx 可能没有启用 gzip或者是网络面板显示的是原始大小没走预压缩。解决办法是在 Nginx 里加上gzip_static on; gzip_vary on;然后保存、重新加载配置nginx -t nginx -s reload重新部署后再看响应头里的Content-Encoding是否为 gzip以及响应头里的ETag是否带上了.gz后缀。如果你用的是其他 Web 服务器比如 Caddy 或 Tomcat也需要各自确认静态文件模块是否支持预压缩文件优先返回。4.5 Vue Router 懒加载与 history 模式部署的配合问题这个坑和体积优化本身无关但如果你刚好在优化期间改用 history 路由就会遇到。生产环境下如果使用createWebHistory()刷新某个子路由页面时Nginx 如果没有做 fallback会直接 404。处理方式是在 Nginx 的 server 配置里加上location / { root /usr/share/nginx/html; try_files $uri $uri/ /index.html; }同时如果你的应用部署在子路径下比如https://example.com/admin那 Vite 的base也要相应改掉。这个base配置很多人容易忽略。它不只是影响静态资源路径还影响 Vite 构建时对 script 标签、css 标签的引用路径。不配置的话部署到子路径后会出现资源 404。export default defineConfig({ base: process.env.NODE_ENV production ? /admin/ : / });4.6 问题排查速查表现象可能原因解决方案路由懒加载后首屏体积没降首页中同步引用了异步组件检查 layout 和首页组件把异步组件改成动态 import 引用ECharts 报 series 不存在未注册对应图表类型在 utils 里调用echarts.use([LineChart, BarChart, PieChart])externals 不生效库被重复打包import 路径和 externals 模块名不一致统一用包名导入不要用深层路径构建产物里有大量 .gz但线上未压缩服务器未开启 gzip_static配置 Nginx 的gzip_static on;页面刷新 404history 路由 服务器未 fallbackNginx 配置try_files $uri $uri/ /index.html;部署到子路径后资源 404Vite base 未配置设置base为子路径Element Plus 按需引入后样式丢失样式文件未按需引入引入对应组件的 style/css 文件5. 优化过程的最终数据对比与经验总结这里贴一下我做完整套优化之后最终构建产物的对比数据优化项优化前优化后首屏 JS 体积未压缩2798 KB517 KB首屏 JS 体积gzip812 KB145 KB构建产物 chunk 数18首屏 HTTP 请求数1 个 JS4 个 JS其中 2 个命中强缓存总构建时间约 25s约 18s这个 500K 的数字最开始我以为是按需引入 Element Plus 带来的收益最大但实际分析后发现ECharts 按需引入的收益贡献占了一半还要多。Element Plus 按需引入大概贡献了 200KB路由懒加载贡献了首屏 1.8M 的削减。拆包和 gzip 更多是提升了传输效率和缓存效率。说几个实操后的心得体会。第一体积优化不要一次性把方案全上。每做一步就构建一次记录一下产物数据变化这样出了问题能快速定位。我就是先做路由懒加载确认没问题再做 ECharts 按需引入最后才动构建配置。第二所有优化措施都要过一遍“可真机验证”的流程特别是改了 CDN 或 externals 之后一定要在无痕模式里完整走一遍核心业务流程防止全局变量被错误引用导致白屏。第三优化之后最好把analyze.html保存下来作为后续版本对比的基线。以后每次加依赖、加页面都能快速判断体积变化是否合理。最后再分享一个小技巧。很多人不知道Vite 构建时支持--report这样的模式也可以直接用vite build --watch监听构建配合 visualizer 插件每次构建后自动刷新分析页面。我自己喜欢把这个流程写进 package.json 的 script 里比如{ scripts: { build: vite build, build:analyze: vite build open analyze.html } }这样每次想检查产物体积一条命令就行不用临时去改配置文件。这套流程稳定以后我在后续几个项目里都复用了同样的思路效果都很稳定。如果你现在正被 Vite 打包体积问题困扰照着这份方案走一遍应该能把首屏体积实实在在地降下来。

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

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

免费获取报价