删除多个完成报告文件,优化项目结构以提升可维护性。

This commit is contained in:
乘风
2026-02-03 15:59:37 +08:00
parent d4ca9573f5
commit a2443c097c
119 changed files with 2119 additions and 8537 deletions

View File

@@ -22,11 +22,18 @@
- 开发流程和最佳实践
- 快速检查清单
### reference.md
Next → 小程序转化参考(详细步骤),包含:
- 架构与 newpp 配置、适配层、功能控制一致
- 底部菜单必须用自定义组件(不要用原生 tabBar
- UI 来源策略、样式兼容、**安全区与标题/胶囊避让**
- 构建与合并流程、检查清单
### troubleshooting.md
详细的故障排查指南,包含:
- 编译问题chunk 文件、Babel、依赖
- 从 Next 迁移要点、编译问题chunk 文件、Babel、依赖
- 运行时问题URLSearchParams、localStorage、window/document
- 样式问题Grid、盒模型、Flexbox
- 样式问题Grid、盒模型、**标题/头部被胶囊或电池栏遮挡**、Flexbox
- 路由问题导航、switchTab、动态路由
- 网络问题API 请求、跨域)
- 性能问题(加载慢、卡顿)
@@ -46,9 +53,10 @@ Agent 应该在以下情况下自动应用此技能:
- 需要配置 `webpack.mp.config.js`
2. **代码转换**
- 将 React Web 应用转换为小程序
- 将 React Web 应用转换为小程序(含 Next 项目转化)
- 需要创建跨平台适配层
- 处理 Web 和小程序的 API 差异
- 底部菜单保留自定义组件(不要用原生 tabBar、安全区与标题避让
3. **问题排查**
- 编译错误chunk 文件、Webpack 配置)
@@ -129,10 +137,14 @@ function isMiniProgram() {
| 问题 | 文档位置 |
|------|---------|
| redirect 跳转异常(用了 home | SKILL.md > 必记坑点 1 / troubleshooting.md > 配置问题 1 |
| Babel 报错(?. / ?? | SKILL.md > 必记坑点 2 / troubleshooting.md > 编译问题 2 |
| chunk 文件缺失 | SKILL.md > 问题 2 |
| URLSearchParams 错误 | SKILL.md > 问题 3 / troubleshooting.md > 运行时问题 1 |
| CSS Grid 不生效 | SKILL.md > 问题 1 / troubleshooting.md > 样式问题 1 |
| 底部导航动态显示 | SKILL.md > 问题 4 |
| 底部菜单/导航(不要用原生 tabBar | SKILL.md > 问题 4 / reference.md > 4.5 |
| 安全区/标题被胶囊或电池栏遮挡 | reference.md > 第 7 节 / troubleshooting.md > 样式问题 4 |
| 阅读页上下章/数据源 | SKILL.md > 必记坑点 3、4 |
| 页面跳转失败 | troubleshooting.md > 路由问题 1 |
| API 请求失败 | troubleshooting.md > 网络问题 1 |

View File

@@ -37,7 +37,7 @@ Kbone 是腾讯提供的 Web 与小程序同构解决方案,允许使用 React
2. **适配层(必须)**
- `adapters/env.js``isMiniProgram()``typeof wx !== 'undefined' && wx.getSystemInfoSync`)。
- `adapters/request.js`Web 用 `fetch`小程序用 `wx.request`统一返回 Promise
- `adapters/request.js`Web 用 `fetch`(相对路径同源);小程序用 `wx.request`**必须带完整 baseUrl**。本项目线上 API 域名为 **`https://soul.quwanzhi.com`**(见开发文档 `开发文档/8、部署/当前项目部署到线上.md`)。转化时:小程序侧 `request(url)` 实际请求 `baseUrl + url``baseUrl` 来自 `getApp().globalData.baseUrl`,壳的 `app.js``onLaunch` 前应设置 `globalData.baseUrl = 'https://soul.quwanzhi.com'`(或从配置读),**不要写死为 your-domain.com**
- `adapters/storage.js`Web 用 `localStorage`,小程序用 `wx.getStorageSync`/`setStorageSync`
- `adapters/router.js`Web 用 `window.location` 或 Next `router`,小程序用 `wx.navigateTo`/`wx.reLaunch`;路径转小程序页面路径 `toMpPath(path)`
- **功能控制一致**:与 Next 端相同,用同一套 API`/api/db/config`)和条件渲染(如 `features.matchEnabled` 控制「找伙伴」入口),不写死开关。
@@ -48,21 +48,26 @@ Kbone 是腾讯提供的 Web 与小程序同构解决方案,允许使用 React
4. **miniprogram.config.js 要点**
- `router`:每个页面单独 key例如 `index``chapters``read``my``match`,对应 `entry` 与小程序页面路径。
- 动态底部导航:不配置原生 `tabBar``appExtraConfig`不写 `tabBar`;底部栏用 React 组件 + `wx.reLaunch` 跳转
- **底部菜单(重要)**Web 端多为**自定义底部菜单组件**,有显隐逻辑(如某页不显示、根据 API 动态显隐某项),原生 tabBar 丑且可操作性差。转化时**不要**改为小程序原生 tabBar应保留「主页面引入统一菜单组件」的方式`appExtraConfig`**不配置** `tabBar`,各主页面(首页、目录、我的、找伙伴等)继续引入同一底部菜单组件,由组件控制显隐、样式与跳转(小程序内用 `wx.reLaunch`),与 Web 行为一致
5. **样式兼容(与 Next 视觉一致)**
- Grid → Flex`boxSizing: 'border-box'`、必要时 `lineHeight`)。
- `backdrop-filter` / `position: sticky` 等不支持则用占位或纯色/渐变替代,保证布局不错乱。
- 单位:小程序侧可用 rpx与 Web 的 rem/px 按设计稿做一次换算或共用同一套换算规则。
6. **构建与合并**
6. **安全区与标题避让(必须)**
- **顶部/电池栏**:使用 **`navBarHeight`**(状态栏 + 胶囊区域总高),不用固定 `statusBarHeight + 44`。在壳的 `app.js``onLaunch` 里用 `wx.getSystemInfoSync()` + `wx.getMenuButtonBoundingClientRect()` 计算并写入 `globalData.navBarHeight``globalData.statusBarHeight`;无菜单按钮时回退 `statusBarHeight + 44`。每页顶部占位条高度设为 `navBarHeight`,内容区 `padding-top``statusBarHeight`
- **底部安全区**:底部导航/固定底栏必须加 `padding-bottom: env(safe-area-inset-bottom)`,避免在有底部刘海的设备上被遮挡。
- **标题右侧防遮挡**:小程序右上角胶囊会覆盖标题/按钮。所有带标题或右侧按钮的头部容器必须**右侧留白**:用 `globalData.capsulePaddingRight` 内联 `padding-right`,或在全局样式中定义 `.safe-header-right { padding-right: 200rpx; box-sizing: border-box; }`,避免标题、返回按钮与胶囊重叠或被遮挡。若出现标题被挡,优先检查是否加了该留白。
7. **构建与合并**
- 在 newpp 执行 `NODE_ENV=production npm run build:mp`,将 `dist/mp/common/` 及 mp-plugin 生成的页面目录合并到 `miniprogram/`
- 合并时保留 miniprogram 壳的 `app.js` 全局数据与生命周期,只覆盖/新增 Kbone 生成的页面与 common 资源。
### 与 Kbone 规则的关系
- **router**:遵循本技能「核心配置规范」——每页单独配置、不用 `other`
- **底部导航**:遵循「问题 4底部导航动态显示」——不配置原生 tabBar,用自定义组件 + `wx.reLaunch`
- **底部菜单**:遵循「问题 4底部导航动态显示」——**不要用原生 tabBar**;保留 Web 的自定义底部菜单组件,主页面统一引入该组件,显隐与样式逻辑与 Web 一致,跳转用 `wx.reLaunch`
- **API/兼容**:遵循「跨平台适配层」与「问题 3URLSearchParams」——全部走适配层避免 Web 独有 API。
- **样式**:遵循「问题 1样式错位」与 troubleshooting 中的 Grid/Flex、box-sizing、lineHeight 等。
@@ -78,7 +83,7 @@ Kbone 是腾讯提供的 Web 与小程序同构解决方案,允许使用 React
```javascript
module.exports = {
origin: 'https://your-domain.com',
origin: 'https://soul.quwanzhi.com', // 本项目线上域名,见开发文档 8、部署
entry: '/',
// ✅ 正确:每个页面单独配置
@@ -95,6 +100,12 @@ module.exports = {
// other: ['/chapters', '/read/:id', ...],
// },
// ✅ redirect 必须用 router 里存在的页面名(如 index不能写不存在的 'home'
redirect: {
notFound: 'index',
accessDenied: 'index',
},
// 全局配置
global: {
rem: true, // 支持 rem 单位
@@ -182,6 +193,15 @@ module.exports = {
---
## 必记坑点(项目实践)
1. **redirect 必须用 router 里存在的页面名**`notFound` / `accessDenied` 填 router 的 key`index`),不要填不存在的 `home`
2. **Babel 6 不支持 ?. 和 ??**:官方 React 模板是 Babel 6 + stage-3`(x && x.y)` 替代 `x?.y`,用 `x != null ? x : default` 替代 `x ?? default`,否则 build:mp 报 `Unexpected token`
3. **阅读页内容与上下章用同一数据源**:用 `useChapterContent` + `useChapters` 的 getNextSection/getPrevSection不要混用静态 bookData 与 API。
4. **useChapters 需暴露 getNextSection / getPrevSection**:有阅读页且 API 驱动时,供上一章/下一章使用。
---
## 常见问题解决
### 问题 1样式错位
@@ -270,27 +290,30 @@ const queryString = buildQueryString({ key: 'value', page: 1 })
---
### 问题 4底部导航动态显示
### 问题 4底部导航动态显示(必须用自定义组件,不要用原生 tabBar
**景**需要根据 API 配置动态显示/隐藏某些导航项
**景**Web 端通常用**自定义底部菜单组件**主页面统一引入有显隐逻辑如某路由不显示菜单根据 API 动态显示/隐藏找伙伴等项原生 tabBar 样式丑可操作性差且无法灵活控制显隐
**方案**使用完全自定义的导航组件不配置原生 tabBar
**要求**转化时**不要** Web 的自定义底部菜单改成小程序原生 `tabBar`应保留统一菜单组件方式
**方案**
- `appExtraConfig` **不配置** `tabBar`
- 各主页面首页目录我的找伙伴等继续引入**同一底部菜单组件** BottomNav由该组件负责显隐 Web 一致如文档/阅读页不显示)、菜单项动态显隐如根据 `matchEnabled` 显示/隐藏找伙伴」)、样式与跳转
- 跳转使用 `wx.reLaunch`因未配置原生 tabBar不能用 `wx.switchTab`)。
```javascript
// miniprogram.config.js
appExtraConfig: {
sitemapLocation: 'sitemap.json',
// ✅ 不配置 tabBar使用完全自定义的导航组件
// 原因:需要根据 API 配置动态显示/隐藏功能
// ✅ 不配置 tabBar保留 Web 的自定义底部菜单组件
// 原因:显隐逻辑、样式与可操作性需与 Web 一致
},
```
```javascript
// router adapter
// router adapter(底部菜单点击时)
export function switchTab(path) {
if (isMiniProgram()) {
// ✅ 使用 wx.reLaunch 代替 wx.switchTab
// 原因:没有配置原生 tabBar
wx.reLaunch({ url: toMpPath(path) })
} else {
window.location.href = path === '/' ? 'index.html' : path.replace(/^\//, '') + '.html'
@@ -321,12 +344,14 @@ export function navigateTo(path) {
}
}
// adapters/request.js
// adapters/request.js(小程序侧必须用完整 URLbaseUrl 见 globalData
export function request(url, options) {
if (isMiniProgram()) {
const baseUrl = (typeof getApp === 'function' && getApp().globalData?.baseUrl) || 'https://soul.quwanzhi.com'
const fullUrl = url.startsWith('http') ? url : baseUrl + url
return new Promise((resolve, reject) => {
wx.request({
url,
url: fullUrl,
method: options.method || 'GET',
data: options.body,
success: res => resolve(res.data),
@@ -464,13 +489,13 @@ appExtraConfig: {
}
```
**动态导航**使用自定义组件
**动态导航 / Web 自定义底部菜单**必须用自定义组件不要用原生 tabBar
```javascript
appExtraConfig: {
// 不配置 tabBar
}
// 使用 React 组件实现导航
// 使用 wx.reLaunch 进行跳转
// Web 端多为统一底部菜单组件,主页面引入;转化时保留该方式:
// 各主页面引入同一 BottomNav 等组件,显隐/样式与 Web 一致,跳转用 wx.reLaunch
```
---
@@ -480,6 +505,7 @@ appExtraConfig: {
### 配置检查
- [ ] router 每个页面单独配置
- [ ] **redirect.notFound / accessDenied 使用 router 中存在的页面名(如 index**
- [ ] pages 配置了所有页面标题
- [ ] global 启用了 rem pageStyle
- [ ] webpack mode 根据环境判断
@@ -489,6 +515,7 @@ appExtraConfig: {
- [ ] 所有 CSS Grid 替换为 Flexbox
- [ ] 添加了 boxSizing: 'border-box'
- [ ] **没有使用可选链 ?. 和空值合并 ??Babel 6 不支持)**
- [ ] 没有使用 URLSearchParams
- [ ] 没有使用其他 Web 独有 API

View File

@@ -21,7 +21,8 @@
### 2.1 miniprogram.config.js
- `router`:每个页面单独 key对应 Next 路由,**不要使用 `other` 数组**。
- 动态底部导航:`appExtraConfig` 中**不配置** `tabBar`,用自定义 React 组件 + `wx.reLaunch`
- **`redirect`**`notFound``accessDenied` 必须填 **router 里存在的页面名**(如 `index`),不要填不存在的 `home`
- **底部菜单**`appExtraConfig` 中**不配置** `tabBar`。Web 端多为自定义底部菜单组件(主页面统一引入、有显隐逻辑),转化时**不要**改为小程序原生 tabBar保留「统一菜单组件」+ `wx.reLaunch` 跳转。
- `global`:建议 `rem: true``pageStyle: true`
- `pages`:为每个页面配置 `extra.navigationBarTitleText`
@@ -56,12 +57,18 @@ appExtraConfig: {
| 文件 | 职责 |
|------|------|
| `adapters/env.js` | `isMiniProgram()``typeof wx !== 'undefined' && wx.getSystemInfoSync` |
| `adapters/request.js` | Web`fetch`;小程序:`wx.request`统一返回 Promise |
| `adapters/request.js` | Web`fetch`(相对路径);小程序:`wx.request`**必须带完整 baseUrl**,本项目线上域名为 `https://soul.quwanzhi.com`(见开发文档 8、部署 |
| `adapters/storage.js` | Web`localStorage`;小程序:`wx.getStorageSync`/`setStorageSync` |
| `adapters/router.js` | Web`window.location` 或 Next Router小程序`wx.navigateTo`/`wx.reLaunch`,路径用 `toMpPath(path)` 转成小程序页面路径 |
业务代码**只**通过适配层访问请求、存储、路由,不直接使用 `window``document``localStorage``URLSearchParams` 等。
**请求基地址(转化时必须处理)**:小程序端不能使用相对路径,需用完整 URL。本项目线上 API 域名为 **`https://soul.quwanzhi.com`**,见 `开发文档/8、部署/当前项目部署到线上.md`。转化时:
- 在**壳**的 `miniprogram/app.js``globalData` 中设置 `baseUrl: 'https://soul.quwanzhi.com'`(或从环境/配置读取)。
-**adapters/request.js** 中,小程序分支:`url` 若不以 `http` 开头则拼上 `getApp().globalData.baseUrl`,再调用 `wx.request`
- 不要写死为 `your-domain.com` 或其它占位域名。
---
## 4. 功能控制一致
@@ -74,6 +81,18 @@ appExtraConfig: {
---
## 4.5 底部菜单:必须用自定义组件,不要用原生 tabBar
- **原因**Web 端底部菜单多为**自定义组件**(如 `BottomNav`),主页面统一引入,有「某页不显示菜单」「根据 API 显隐某项」等逻辑;原生 tabBar 样式与可操作性差,且无法等价实现上述逻辑。
- **要求**:转化时**不要**把自定义底部菜单改成小程序原生 `tabBar`
- **做法**
- `appExtraConfig` 中不配置 `tabBar`
- 各主页面(首页、目录、我的、找伙伴等)**继续引入同一底部菜单组件**,由该组件控制:是否在本页显示、各菜单项显隐(如 `matchEnabled` 控制「找伙伴」)、样式、点击跳转(小程序内用 `wx.reLaunch`)。
- 与 Web 一致:阅读页、文档页、关于页等可不渲染该组件或由布局隐藏。
- **检查**:若发现被改成了原生 tabBar应回退为「主页面引入统一菜单组件」的方式。
---
## 5. UI 来源策略
- **复制适配**:从 Next 的 `app/**/page.tsx``components/**` 复制到 newpp改为 JSX用适配层替代 `Link`/`useRouter`/`usePathname`/`fetch`
@@ -91,7 +110,40 @@ appExtraConfig: {
---
## 7. 构建与合并流程
## 7. 安全区与标题/胶囊避让(必须)
### 7.1 电池栏/顶部安全区
- **问题**:使用自定义导航栏(`navigationStyle: 'custom'`)时,状态栏(电池、时间)会与页面顶部重叠,若不做占位,标题会被挡。
- **做法**
- 在**壳**的 `app.js``onLaunch` 中**同步**获取系统信息并计算占位高度,写入 `globalData`
- `statusBarHeight``wx.getSystemInfoSync().statusBarHeight || 44`
- `navBarHeight`:用 `wx.getMenuButtonBoundingClientRect()` 计算「状态栏 + 胶囊区域」总高,公式:`menuButton.bottom + menuButton.top - systemInfo.statusBarHeight`;无菜单按钮时回退 `statusBarHeight + 44`
- `capsulePaddingRight``systemInfo.windowWidth - menuButton.left + 10`,供标题右侧留白使用
- 每个页面的**顶部占位条**高度设为 `navBarHeight`px占位条内部若需区分状态栏与导航内容可用 `padding-top: {{ statusBarHeight || 44 }}px`
- 页面 `onLoad`/`onShow` 时从 `getApp().globalData``navBarHeight``statusBarHeight` 写入页面 `data`,供 WXML 内联样式使用。
### 7.2 底部安全区
- **问题**:有底部刘海的设备上,固定底栏(如 TabBar、底部导航会贴边内容被遮挡。
- **做法**:底部导航容器加 `padding-bottom: env(safe-area-inset-bottom)`WXSS 或内联均可),无需额外 JS。
### 7.3 标题右侧不被胶囊遮挡
- **问题**:小程序右上角有胶囊按钮(…、首页),若标题或返回按钮延伸到右侧,会被遮挡或误触。
- **做法**
- 所有带标题/返回/右侧按钮的**头部容器**必须预留右侧空白:
- **方式 A**:全局样式 `.safe-header-right { padding-right: 200rpx; box-sizing: border-box; }`,头部容器加该类。
- **方式 B**:内联 `style="padding-right: {{ capsulePaddingRight }}px"``capsulePaddingRight` 来自 `getApp().globalData.capsulePaddingRight`(在 onLaunch 中已计算)。
- 若出现「标题或按钮被挡」,优先检查该头部是否加了上述留白,并确认 `app.js` 中已正确写入 `capsulePaddingRight`
### 7.4 统一占位模板(可选)
- 无复杂导航的页面(如设置、关于、推广、购买记录等)建议使用**同一套**顶部安全区:占位条高度 `navBarHeight`,其内 `padding-top: statusBarHeight`,导航容器 `display: flex; flex-direction: column; justify-content: flex-end; box-sizing: border-box;`,并加 `.safe-header-right``capsulePaddingRight`,保证标题与返回按钮不被遮挡且右侧留白一致。
---
## 8. 构建与合并流程
1. 在 newpp 执行:`NODE_ENV=production npm run build:mp`
2.`newpp/dist/mp/` 下生成的页面目录及 `common/` 复制/合并到 `miniprogram/`
@@ -100,11 +152,12 @@ appExtraConfig: {
---
## 8. 检查清单
## 9. 检查清单
- [ ] miniprogram.config.js每页单独 router`other`;未配置原生 tabBar。
- [ ] miniprogram.config.js每页单独 router`other`**未配置原生 tabBar**,底部菜单沿用 Web 的自定义组件(主页面统一引入,显隐与跳转逻辑一致)
- [ ] webpack.mp.config.js每页有 entryisOptimize 按 NODE_ENV中小型项目 splitChunks 为 false。
- [ ] 适配层env、request、storage、router 已实现并在业务中统一使用。
- [ ] 功能开关与 Next 一致,来自同一 API。
- [ ] 样式:无 GridFlex 已加 boxSizing/lineHeight不支持特性已替代。
- [ ] **安全区**app.js 的 onLaunch 中已计算并写入 `navBarHeight``statusBarHeight``capsulePaddingRight`;每页顶部占位用 `navBarHeight`;底部导航有 `padding-bottom: env(safe-area-inset-bottom)`;带标题的头部有 `.safe-header-right``capsulePaddingRight` 留白,标题/按钮未被胶囊遮挡。
- [ ] 合并后 app.js 未被错误覆盖,页面路径与 app.json 一致。

View File

@@ -8,15 +8,66 @@
-**newpp** 为 Kbone 构建源,**miniprogram** 为壳,构建后合并产物。
- 必须做 **适配层**`env``request``storage``router`;功能开关与 Next 一致,用同一 API`/api/db/config``features.matchEnabled`)。
- **UI**:在 newpp 内复制并适配 Next 的页面/组件,或通过 webpack alias 引用根目录共享组件(无 Next 专属 API
- **配置**`miniprogram.config.js` 每页单独 router、不配原生 tabBar`webpack.mp.config.js``isOptimize = process.env.NODE_ENV === 'production'`,中小型项目建议 `splitChunks: false`
- **配置**`miniprogram.config.js` 每页单独 router、**不配原生 tabBar**`webpack.mp.config.js``isOptimize = process.env.NODE_ENV === 'production'`,中小型项目建议 `splitChunks: false`
- **底部菜单**Web 端多为自定义底部菜单组件(主页面统一引入、有显隐逻辑),**不要**转为小程序原生 tabBar保留「主页面引入统一菜单组件」显隐与跳转与 Web 一致,跳转用 `wx.reLaunch`
- **样式**Grid 改 Flex`boxSizing`/`lineHeight`;不支持特性用兼容写法或替代。
遇到具体报错时,在下方「编译问题」「运行时问题」「样式问题」中按类型排查。
遇到具体报错时,在下方「配置问题」「编译问题」「运行时问题」「样式问题」中按类型排查。
---
## 配置问题
### 1. redirect 用了不存在的页面名
**现象**:配置了 `redirect.notFound: 'home'``redirect.accessDenied: 'home'`,但 router 里没有名为 `home` 的页面。
**后果**404 或无权时Kbone 可能无法正确跳转到首页。
**解决**redirect 必须使用 **router 里存在的 key**。首页通常是 `index`,不要写 `home`
```javascript
// ❌ 错误
redirect: { notFound: 'home', accessDenied: 'home' }
// ✅ 正确
redirect: { notFound: 'index', accessDenied: 'index' }
```
---
## 编译问题
### 0. Node 17+ OpenSSL 错误ERR_OSSL_EVP_UNSUPPORTED
**错误信息**
```
Error: error:0308010C:digital envelope routines::unsupported
code: 'ERR_OSSL_EVP_UNSUPPORTED'
```
**原因**Node.js 17+ 使用 OpenSSL 3.0,旧版 webpack如 4.x依赖的 MD4 等算法被移出默认提供,导致构建时报错。
**解决方案**
**方案 1在 newpp 的 package.json 脚本中加 NODE_OPTIONS**(推荐)
```json
"scripts": {
"web": "cross-env NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=development webpack-dev-server ...",
"mp": "rimraf dist/mp/common && cross-env NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=development webpack ...",
"build:mp": "rimraf dist/mp/common && cross-env NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=production webpack ..."
}
```
**方案 2当前终端临时设置后再运行**
- Windows PowerShell`$env:NODE_OPTIONS="--openssl-legacy-provider"; cd newpp; npm run web`
- Windows CMD`set NODE_OPTIONS=--openssl-legacy-provider && cd newpp && npm run web`
- Linux/macOS`NODE_OPTIONS=--openssl-legacy-provider npm run web`
**方案 3**:使用 Node.js 16.xLTS运行 newpp 构建,避免 OpenSSL 3.0。
---
### 1. chunk 文件缺失错误
**错误信息**
@@ -74,15 +125,28 @@ optimization: {
```
SyntaxError: Unexpected token ...
```
或具体到某列:`Unexpected token (45:64)`(多为可选链位置)
**原因**
- Babel 配置不正确
- 使用了小程序不支持的 ES6+ 语法
- 使用了 Babel 6stage-3不支持的语法**可选链 `?.`、空值合并 `??`**(属 ES2020
- 其他小程序不支持的 ES6+ 语法
**解决方案**
**若为可选链 / 空值合并**(最常见):
```javascript
// ❌ Babel 6 会报错
res.content?.length
x ?? 'default'
// ✅ 兼容写法
(res.content && res.content.length) || 0
x != null ? x : 'default'
```
**Babel 配置**.babelrc 或 babel.config.js
```javascript
// .babelrc 或 babel.config.js
{
"presets": [
["env", {
@@ -377,7 +441,26 @@ const styles = {
---
### 4. Flexbox 间距问题
### 4. 标题/头部被胶囊或电池栏遮挡
**问题描述**:自定义导航栏时,标题、返回按钮被右上角胶囊或顶部状态栏遮挡。
**原因**:未预留顶部安全区高度和右侧胶囊留白。
**解决方案**
1. **顶部安全区**:在 `app.js``onLaunch` 中计算并写入 `globalData`
- `statusBarHeight = wx.getSystemInfoSync().statusBarHeight || 44`
- `navBarHeight`:用 `wx.getMenuButtonBoundingClientRect()` 计算 `menuButton.bottom + menuButton.top - statusBarHeight`,无菜单时用 `statusBarHeight + 44`
- `capsulePaddingRight = systemInfo.windowWidth - menuButton.left + 10`
2. **页面占位**:顶部占位条高度设为 `{{ navBarHeight }}px`,内容区 `padding-top: {{ statusBarHeight }}px`;页面 `onLoad`/`onShow``getApp().globalData` 取上述值写入 `data`
3. **标题右侧留白**:头部容器加类 `.safe-header-right { padding-right: 200rpx; box-sizing: border-box; }`,或内联 `padding-right: {{ capsulePaddingRight }}px`,避免标题与胶囊重叠。
详见本目录 `reference.md` 第 7 节「安全区与标题/胶囊避让」。
---
### 5. Flexbox 间距问题
**问题描述**Flexbox 子元素间距不均匀
@@ -528,7 +611,7 @@ request:fail url not in domain list
```javascript
// miniprogram.config.js
module.exports = {
origin: 'https://your-domain.com', // 使用已配置的域名
origin: 'https://soul.quwanzhi.com', // 本项目线上域名,见开发文档 8、部署
}
```
@@ -563,7 +646,7 @@ devServer: {
```javascript
// Express 示例
app.use(cors({
origin: ['http://localhost:8080', 'https://your-domain.com'],
origin: ['http://localhost:8080', 'https://soul.quwanzhi.com'],
credentials: true,
}))
```