资讯动态

WorkBuddy 六周上线 Hybrid App:从环境搭建到上架的十六个坑

发布时间:2026/10/15 17:20:47 来源:尧图企业网站定制
1. 从零到上线为什么我选择用 WorkBuddy 走完整个 App 开发链路去年年底我接手了一个内部工具类 App 的从零搭建任务。需求不复杂一个带 AI 对话能力的轻量级应用需要跑在 Android 和 iOS 两端最好还能有个 Web 版本方便快速验证。团队一共两个人一个是我另一个是刚毕业的前端。预算不多时间给了六周。摆在面前的选择其实很明确要么走原生开发Android 用 Kotlin、iOS 用 Swift双端并行要么走跨平台方案Flutter 或 React Native要么走 Hybrid 路线用 WebView 套壳核心逻辑用 Web 技术栈实现。原生开发第一个被排除。两个人六周做双端原生还要集成 AI 对话能力时间根本不够。Flutter 我试过学习曲线对新人不太友好而且某些原生能力的调用需要写 Platform Channel调试成本高。React Native 生态成熟但版本升级带来的依赖冲突问题一直让我头疼。最后我选了 Hybrid 路线核心工具是 WorkBuddy。这个选择背后有几个很实际的考量第一Web 技术栈我和搭档都熟HTML、CSS、JavaScript 写起来快第二WorkBuddy 提供了一套相对完整的项目脚手架和构建流程省去了大量配置时间第三WebView 方案天然支持热更新后期迭代不需要走应用商店审核这对内部工具类应用来说太重要了。提示Hybrid 方案不是万能的。如果你的 App 涉及大量原生硬件调用比如蓝牙、NFC、高精度传感器或者对帧率有极高要求比如游戏还是老老实实走原生或 Flutter。Hybrid 的甜点区是内容展示型、工具型、内部系统类应用。这篇文章我会把整个开发过程拆成六个阶段每个阶段踩过的坑、做出的取舍、最终落地的方案都写清楚。如果你也在考虑用 WorkBuddy 搭一个能真正上线的 App或者你正在 Hybrid 和原生之间摇摆这篇内容应该能帮你省下不少试错时间。2. 六个阶段拆解从环境搭建到应用上架2.1 阶段一环境搭建与项目初始化环境搭建听起来简单实际上是我踩坑最多的阶段之一。WorkBuddy 的安装本身不复杂官方文档写得还算清楚但有几个细节文档里没提我是折腾了大半天才搞明白的。首先是 Node.js 版本的问题。WorkBuddy 的某些依赖对 Node 版本有硬性要求我一开始用的是系统自带的 Node 16构建时报了一堆ERR_OSSL_EVP_UNSUPPORTED的错误。后来换成 Node 18 LTS 才解决。如果你用的是 macOS建议用 nvm 管理 Node 版本切换起来方便。# 安装 nvmmacOS/Linux curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash # 安装并使用 Node 18 nvm install 18 nvm use 18 # 验证版本 node -v npm -vAndroid 开发环境的配置是另一个大头。Android Studio 的下载和安装没什么好说的但 SDK 版本的选择有讲究。WorkBuddy 生成的项目默认 targetSdkVersion 是 33但如果你要兼容一些老设备minSdkVersion 建议设到 24Android 7.0。这个版本覆盖了绝大多数活跃设备同时避免了一些新 API 的兼容性问题。WebView 版本是 Hybrid 应用的生命线。不同 Android 设备上的 WebView 版本差异巨大从 Android 8.1 的 WebView 58 到最新版的 WebView 120特性支持天差地别。我的做法是在项目初始化阶段就锁定一个最低支持的 WebView 版本然后在代码里做特性检测。// 检测 WebView 版本Android 端 function getWebViewVersion() { const ua navigator.userAgent; const match ua.match(/Chrome\/(\d)/); return match ? parseInt(match[1]) : 0; } const webviewVersion getWebViewVersion(); if (webviewVersion 80) { // 降级处理使用 polyfill 或提示用户升级 console.warn(WebView 版本过低部分功能可能不可用); }iOS 端相对省心WKWebView 的版本跟随系统更新iOS 14 以上基本没有兼容性问题。但 iOS 浏览器唤起安装 App 的流程需要额外配置这个后面会细说。项目初始化完成后目录结构大概是这样的workbuddy-app/ ├── src/ │ ├── pages/ # 页面 │ ├── components/ # 组件 │ ├── utils/ # 工具函数 │ └── assets/ # 静态资源 ├── android/ # Android 原生工程 ├── ios/ # iOS 原生工程 ├── public/ # 公共资源 └── workbuddy.config.js # 项目配置注意不要随意修改 android 和 ios 目录下的原生代码除非你清楚自己在做什么。WorkBuddy 的构建流程会覆盖部分文件手动修改的内容可能在下次构建时丢失。需要自定义原生能力时优先通过插件机制实现。2.2 阶段二核心功能开发与 AI 能力集成这个阶段是整个项目的核心。我们的 App 主要功能是 AI 对话用户输入问题后端调用大模型 API 返回结果。前端需要处理消息列表渲染、输入框状态管理、流式响应展示等。AI 对话的流式响应是个技术难点。传统的 HTTP 请求是等全部数据返回后再渲染但对话场景需要逐字显示用户体验才好。实现方式有两种SSEServer-Sent Events和 WebSocket。SSE 更简单单向通信足够用WebSocket 更灵活但维护成本高。我选了 SSE。// SSE 流式响应处理 function streamChat(message, onChunk, onComplete) { const eventSource new EventSource(/api/chat?message${encodeURIComponent(message)}); eventSource.onmessage (event) { const data JSON.parse(event.data); if (data.done) { eventSource.close(); onComplete(); } else { onChunk(data.content); } }; eventSource.onerror () { eventSource.close(); onComplete(); }; }消息列表的渲染性能是另一个坑。对话轮次多了之后DOM 节点数量暴涨页面会卡顿。解决方案是虚拟列表只渲染可视区域内的消息。我一开始自己手写虚拟列表后来发现社区有成熟的方案直接引入就行。WebView 里的滚动行为需要特别注意。iOS 的 WKWebView 默认有回弹效果Android 的 WebView 滚动惯性也不一样。统一体验的做法是在 CSS 里设置overscroll-behavior: none然后通过原生端配置禁用回弹。/* 禁用滚动回弹 */ html, body { overscroll-behavior: none; -webkit-overflow-scrolling: touch; }AI 能力的集成还涉及一个关键问题API Key 的安全。绝对不能把 API Key 写在前端代码里否则被人抓包就泄露了。正确做法是前端请求自己的后端后端再调用大模型 API。后端可以用任何你熟悉的技术栈Node.js、Python、Go 都行。2.3 阶段三WebView 深度调优与原生能力桥接WebView 调优是 Hybrid 应用体验好坏的分水岭。默认配置下的 WebView 性能堪忧加载慢、滚动卡、白屏时间长。我做了以下几件事第一开启硬件加速。Android 端在 AndroidManifest.xml 里设置android:hardwareAcceleratedtrueiOS 端 WKWebView 默认开启。第二预加载 WebView。App 启动时就在后台初始化一个 WebView 实例等用户进入页面时直接复用省去初始化时间。第三资源本地化。把 HTML、CSS、JS、图片等静态资源打包进 App通过file://协议加载而不是从远程服务器拉取。这样首屏加载速度能提升好几倍。// Android 端 WebView 配置 WebView webView findViewById(R.id.webview); WebSettings settings webView.getSettings(); settings.setJavaScriptEnabled(true); settings.setDomStorageEnabled(true); settings.setDatabaseEnabled(true); settings.setCacheMode(WebSettings.LOAD_DEFAULT); settings.setAllowFileAccess(true); settings.setAllowContentAccess(true);原生能力桥接是另一个重点。Web 端需要调用原生功能时比如获取设备信息、调用相机、分享需要通过 JSBridge 通信。WorkBuddy 提供了一套桥接机制但有些场景需要自己扩展。// Web 端调用原生方法 window.WorkBuddy.invoke(getDeviceInfo, {}, (result) { console.log(设备信息:, result); }); // 原生端注册方法Android webView.addJavascriptInterface(new Object() { JavascriptInterface public String getDeviceInfo() { JSONObject info new JSONObject(); info.put(model, Build.MODEL); info.put(version, Build.VERSION.RELEASE); return info.toString(); } }, WorkBuddy);注意addJavascriptInterface在 Android 4.2 以下有安全漏洞但 minSdkVersion 设到 24 后就不用担心这个问题了。另外JSBridge 的调用是异步的回调函数要处理好超时和错误情况。2.4 阶段四双端适配与兼容性处理Android 和 iOS 的差异体现在方方面面从状态栏高度到滚动行为从字体渲染到键盘弹出。我整理了一份对照表开发时对照着处理差异点AndroidiOS处理方案状态栏高度动态24-48px固定44px刘海屏CSS 变量 原生注入键盘弹出调整窗口大小覆盖内容监听键盘事件手动调整布局滚动回弹无有CSSoverscroll-behavior: none字体渲染偏粗偏细使用系统字体栈点击延迟300ms无使用touch-action: manipulation安全区域无有env(safe-area-inset-*)iOS 浏览器唤起安装 App 的流程需要单独处理。用户在 Safari 里打开网页点击安装按钮后需要跳转到 App Store。这个跳转不能直接用window.location.href否则会被拦截。正确做法是用 Universal Link 或者自定义 URL Scheme。!-- iOS 唤起 App Store -- a hrefhttps://apps.apple.com/app/idXXXXXXXX onclickhandleInstall(event) 安装 App /a script function handleInstall(e) { e.preventDefault(); const ua navigator.userAgent; if (/iPhone|iPad|iPod/.test(ua)) { // iOS 设备跳转 App Store window.location.href https://apps.apple.com/app/idXXXXXXXX; } else if (/Android/.test(ua)) { // Android 设备跳转应用市场 window.location.href market://details?idcom.example.app; } } /scriptAndroid 的进度条处理也有讲究。WebView 加载页面时原生进度条和网页内的进度条要协调好。我的做法是原生端监听onProgressChanged网页端监听document.readyState两边同步更新。2.5 阶段五测试、调试与性能优化测试阶段我主要做了三件事功能测试、性能测试、兼容性测试。功能测试用 Jest 写单元测试覆盖核心工具函数和业务逻辑。E2E 测试用 Detox模拟真实用户操作。不过 E2E 测试的维护成本很高UI 一改测试就挂后来我只保留了最核心的几条用例。性能测试重点关注三个指标首屏加载时间、页面切换流畅度、内存占用。首屏加载时间用 Chrome DevTools 的 Performance 面板分析页面切换流畅度用 FPS 监控内存占用用 Android Profiler 和 Xcode Instruments。// 简单的 FPS 监控 let lastTime performance.now(); let frames 0; function checkFPS() { const now performance.now(); frames; if (now - lastTime 1000) { const fps frames; console.log(FPS: ${fps}); if (fps 50) { console.warn(帧率过低可能存在性能问题); } frames 0; lastTime now; } requestAnimationFrame(checkFPS); } requestAnimationFrame(checkFPS);兼容性测试是最耗时的。我准备了五台测试机一台 Android 低端机Android 8.1WebView 58、一台 Android 中端机Android 12WebView 100、一台 Android 高端机Android 14WebView 120、一台 iPhone SEiOS 15、一台 iPhone 14 ProiOS 17。每台设备上跑一遍核心流程记录问题。提示Android 低端机上的 WebView 版本可能非常老某些 ES6 语法不支持。构建时要用 Babel 转译并且引入必要的 polyfill。core-js和regenerator-runtime是必备的。2.6 阶段六打包上架与持续迭代打包上架是最后一步但坑一点也不少。Android 端需要生成签名证书、配置混淆规则、处理多渠道打包。iOS 端需要配置证书、Provisioning Profile、App Store Connect 信息。Android 签名证书的生成keytool -genkey -v -keystore my-release-key.keystore \ -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000混淆规则要小心配置特别是涉及 JSBridge 的类和方法不能被混淆掉否则 Web 端调用会失败。# 保留 JSBridge 相关类 -keepclassmembers class com.example.app.bridge.** { public *; } -keepattributes JavascriptInterfaceiOS 上架审核对 Hybrid 应用有一些额外要求。Apple 的审核指南 4.7 条规定WebView 加载的内容不能是简单的网页套壳必须有原生功能增强。我们的 App 集成了推送、相机、文件选择等原生能力顺利通过了审核。持续迭代方面Hybrid 应用的优势就体现出来了。Web 部分的更新可以直接热更新不需要重新发版。原生部分的更新才需要走应用商店。我的策略是业务逻辑尽量放在 Web 层原生层只保留必要的能力桥接。3. 十六个坑每一个都是真金白银换来的3.1 环境与构建类坑位坑一Node 版本不兼容导致构建失败。前面提过WorkBuddy 对 Node 版本有要求建议锁定 Node 18 LTS。团队协作时在项目根目录放一个.nvmrc文件内容写18这样每个人切换项目时自动使用正确的版本。坑二Android SDK 路径包含中文或空格。这个问题很隐蔽构建时报错信息也不明确。确保 Android SDK 路径全是英文且没有空格。Windows 用户特别注意默认路径C:\Users\用户名\AppData\Local\Android\Sdk里的用户名如果是中文就会出问题。坑三Gradle 下载超时。国内网络环境下Gradle 依赖下载经常超时。配置阿里云镜像可以解决// build.gradle allprojects { repositories { maven { url https://maven.aliyun.com/repository/public } maven { url https://maven.aliyun.com/repository/google } google() mavenCentral() } }坑四iOS 证书配置错误。iOS 证书分开发证书和发布证书Provisioning Profile 也分 Development 和 Distribution。搞混了会导致打包失败或上架被拒。建议用 Xcode 的自动管理签名功能省心很多。3.2 开发与调试类坑位坑五WebView 里localStorage不生效。Android WebView 默认不开启 DOM Storage需要在设置里手动开启setDomStorageEnabled(true)。这个问题在低版本 Android 上尤其常见。坑六content://协议的文件访问被拦截。Android 7.0 以后直接通过file://访问文件会抛FileUriExposedException。需要用FileProvider生成content://URI。WorkBuddy 已经内置了 FileProvider 配置但如果你要访问外部存储的文件需要自己扩展。!-- AndroidManifest.xml -- provider android:nameandroidx.core.content.FileProvider android:authorities${applicationId}.fileprovider android:exportedfalse android:grantUriPermissionstrue meta-data android:nameandroid.support.FILE_PROVIDER_PATHS android:resourcexml/file_paths / /provider坑七iOS 键盘弹出导致页面布局错乱。iOS 的键盘是覆盖在页面上的不会调整窗口大小。需要在键盘弹出时手动滚动到输入框位置。可以用visualViewportAPI 监听键盘高度变化。if (window.visualViewport) { window.visualViewport.addEventListener(resize, () { const keyboardHeight window.innerHeight - window.visualViewport.height; if (keyboardHeight 100) { // 键盘弹出滚动到输入框 document.querySelector(.input-box).scrollIntoView({ behavior: smooth }); } }); }坑八Android 返回键行为不一致。WebView 里的页面有自己的历史栈按返回键时应该先回退网页历史而不是直接退出 App。需要重写onKeyDown方法。Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode KeyEvent.KEYCODE_BACK webView.canGoBack()) { webView.goBack(); return true; } return super.onKeyDown(keyCode, event); }3.3 性能与体验类坑位坑九首屏白屏时间过长。默认情况下WebView 要等 HTML、CSS、JS 全部加载完才渲染白屏时间可能好几秒。解决方案是服务端渲染SSR或者骨架屏。我用了骨架屏在 HTML 里先放一个静态的加载动画等 JS 加载完再替换成真实内容。坑十图片加载导致内存溢出。对话场景里用户可能会发大图如果不做处理直接渲染低端机上很容易 OOM。解决方案是图片压缩和懒加载。上传前用 Canvas 压缩展示时用loadinglazy。// 图片压缩 function compressImage(file, maxWidth 1024, quality 0.8) { return new Promise((resolve) { const reader new FileReader(); reader.onload (e) { const img new Image(); img.onload () { const canvas document.createElement(canvas); const scale Math.min(1, maxWidth / img.width); canvas.width img.width * scale; canvas.height img.height * scale; const ctx canvas.getContext(2d); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); canvas.toBlob(resolve, image/jpeg, quality); }; img.src e.target.result; }; reader.readAsDataURL(file); }); }坑十一长列表滚动卡顿。对话消息多了之后滚动明显掉帧。虚拟列表是标配但虚拟列表的实现要注意动态高度的问题。消息高度不固定时需要先测量再渲染。我用的是vue-virtual-scroller它支持动态高度。坑十二键盘弹出时页面跳动。Android 上键盘弹出会调整窗口大小导致页面重新布局。可以在 AndroidManifest.xml 里设置android:windowSoftInputModeadjustResize让系统自动处理。3.4 上架与运营类坑位坑十三隐私政策不合规被拒。应用商店对隐私政策的要求越来越严。必须在 App 内提供隐私政策入口且首次启动时要弹窗让用户确认。隐私政策内容要明确说明收集了哪些数据、用于什么目的、如何保护。坑十四权限申请时机不当。Android 6.0 以后权限需要动态申请。不要在 App 启动时就一次性申请所有权限用户会很反感。正确的做法是在真正需要某个权限时才申请并说明用途。// 动态申请权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) ! PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, CAMERA_REQUEST_CODE); }坑十五应用截图和描述不符合要求。应用商店对截图尺寸、格式、内容都有要求。截图要真实反映 App 功能不能有虚假宣传。描述要准确不能堆砌关键词。坑十六版本更新提示被忽略。用户不更新版本会导致旧版本的 bug 一直存在。需要在 App 内做版本检测发现新版本时弹窗提示。但不要强制更新除非是安全漏洞修复。4. 常见问题与排查技巧实录4.1 WebView 相关高频问题速查问题现象可能原因排查方法解决方案页面白屏JS 报错、资源加载失败开启 WebView 调试查看 Console修复 JS 错误检查资源路径样式错乱CSS 兼容性问题对比不同 WebView 版本表现添加浏览器前缀使用 polyfill点击无响应事件被拦截、层级问题检查 z-index 和 pointer-events调整层级移除拦截层滚动卡顿重绘重排频繁Performance 面板分析使用 transform 替代 top/left键盘遮挡输入框键盘处理不当监听键盘事件滚动到可视区域返回键直接退出未处理历史栈检查 onKeyDown重写返回逻辑图片不显示混合内容拦截检查 Console 警告使用 HTTPS 或配置允许混合内容本地存储失效DOM Storage 未开启检查 WebSettings开启 setDomStorageEnabled4.2 调试技巧与工具推荐WebView 调试是 Hybrid 开发的基本功。Android 端可以用 Chrome 的chrome://inspect远程调试iOS 端用 Safari 的开发者菜单。这两个工具能让你像调试普通网页一样调试 WebView 里的内容。# Android 端开启 WebView 调试 if (Build.VERSION.SDK_INT Build.VERSION_CODES.KITKAT) { WebView.setWebContentsDebuggingEnabled(true); }抓包是排查网络问题的利器。但 Hybrid 应用的抓包经常失败因为 WebView 默认不信任用户安装的证书。解决方案是在调试版本里配置信任所有证书但发布版本绝对不能这么做。// 仅调试版本使用 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { if (BuildConfig.DEBUG) { handler.proceed(); // 信任所有证书 } else { handler.cancel(); } }注意信任所有证书的代码绝对不能带到生产环境否则会有中间人攻击风险。用BuildConfig.DEBUG做条件判断确保只在调试版本生效。4.3 性能优化实战经验性能优化不是一蹴而就的需要持续监控和迭代。我总结了几个投入产出比最高的优化点第一减少 HTTP 请求。把多个小图标合并成雪碧图把多个 CSS/JS 文件合并打包。HTTP/2 虽然支持多路复用但请求数量少总是好的。第二启用缓存。静态资源设置长期缓存接口数据设置短期缓存。WebView 的缓存策略要配置好LOAD_DEFAULT在大多数场景下够用。第三延迟加载非关键资源。首屏只加载必要的 CSS 和 JS其他资源等首屏渲染完再加载。可以用requestIdleCallback在浏览器空闲时加载。// 延迟加载非关键资源 if (requestIdleCallback in window) { requestIdleCallback(() { import(./non-critical-module.js); }); } else { setTimeout(() { import(./non-critical-module.js); }, 2000); }第四使用 Web Worker 处理耗时计算。AI 对话的文本处理、图片压缩等操作可以放到 Worker 里避免阻塞主线程。// 创建 Worker const worker new Worker(worker.js); worker.postMessage({ type: compress, data: imageData }); worker.onmessage (e) { console.log(压缩完成:, e.data); };4.4 上线后的监控与迭代App 上线不是终点而是起点。我接入了错误监控和性能监控及时发现线上问题。错误监控用 Sentry能捕获 JS 错误、原生崩溃、网络异常。配置很简单引入 SDK 后初始化即可。import * as Sentry from sentry/browser; Sentry.init({ dsn: YOUR_DSN, environment: process.env.NODE_ENV, release: workbuddy-app${process.env.APP_VERSION}, });性能监控用自研的埋点系统记录首屏时间、页面切换时间、接口响应时间等关键指标。数据上报用navigator.sendBeacon不阻塞页面卸载。// 性能数据上报 window.addEventListener(load, () { const timing performance.timing; const metrics { dns: timing.domainLookupEnd - timing.domainLookupStart, tcp: timing.connectEnd - timing.connectStart, ttfb: timing.responseStart - timing.requestStart, domReady: timing.domContentLoadedEventEnd - timing.navigationStart, load: timing.loadEventEnd - timing.navigationStart, }; navigator.sendBeacon(/api/metrics, JSON.stringify(metrics)); });5. 可复用经验总结与后续扩展方向5.1 团队协作与代码规范两个人开发也需要规范。我们约定了 Git 提交规范、代码风格、分支管理策略。Git 提交用 Conventional Commits方便生成 CHANGELOG。代码风格用 ESLint Prettier提交前自动检查。// .eslintrc.json { extends: [eslint:recommended, plugin:vue/recommended], rules: { no-console: warn, no-unused-vars: error } }分支管理用 Git Flow 的简化版main 分支保持稳定develop 分支日常开发feature 分支做新功能hotfix 分支修紧急 bug。5.2 技术选型的取舍逻辑回顾整个项目几个关键选型的逻辑值得记录选 WorkBuddy 而不是纯手写 Hybrid 框架是因为它提供了完整的构建流程和插件机制省去了大量配置时间。但代价是灵活性受限某些深度定制需求需要绕过它的机制。选 SSE 而不是 WebSocket是因为对话场景是单向流式输出SSE 更简单自动重连机制也省心。如果未来需要双向实时通信再换 WebSocket 也不迟。选虚拟列表而不是分页加载是因为对话场景用户期望连续滚动分页会打断体验。虚拟列表的实现复杂度高一些但体验更好。5.3 后续可以扩展的方向这个 App 目前只做了核心的对话功能后续可以扩展的方向不少语音输入和语音播报让交互更自然。可以用 Web Speech API 或者原生语音能力。多模态输入支持图片、文件、链接的解析。这需要后端配合前端做好上传和展示。对话历史同步用户换设备后能继续之前的对话。需要设计一套账号体系和数据同步机制。离线模式网络不好时也能查看历史对话。可以用 IndexedDB 做本地存储Service Worker 做离线缓存。提示扩展功能时要注意包体积。Hybrid 应用的优势是轻量如果引入太多重型依赖包体积膨胀就失去了优势。每个新功能都要评估对包体积的影响。5.4 一些个人体会六周时间做完这个项目压力不小但收获也大。最大的体会是Hybrid 方案的上限取决于你对 WebView 的理解深度。同样是用 WebView有人做出来的 App 卡顿白屏有人做出来的流畅得像原生。差距就在那些细节里预加载、缓存策略、渲染优化、桥接效率。另一个体会是不要过早优化。项目初期我花了不少时间在性能优化上后来发现有些优化根本没必要因为那些页面用户很少访问。正确的做法是先跑通核心流程上线后根据真实数据决定优化优先级。最后文档和注释真的很重要。六周后回头看自己写的代码有些地方已经想不起来为什么那么写了。幸好当时写了注释省去了不少回忆时间。团队协作时更是如此你的搭档可能随时需要接手你的代码。这个项目后续我还会继续迭代有新踩的坑再回来补充。如果你也在做类似的项目欢迎交流。

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

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

免费获取报价 →
↑