# 第一步：空间布局推理与视觉流重组

在输出代码前，请在内部执行以下扫描与推断算法。节点映射、Flex 推导、样式 lookup 与结构清理参考 D2C 引擎（D2CPRO）的还原逻辑；**DSL 字段来源与解析约定**（如 layoutStyle/layout、relativeX/Y、根节点多种形态、子节点排序与冗余矩形过滤）见 → [07-dsl-fields.md](./07-dsl-fields.md)。

---

## 节点类型与 Ray 组件语义映射（标签规范化）

* **容器类**：`FRAME`、`GROUP`、`INSTANCE`、`COMPONENT`、`RECTANGLE` 统一映射为 **View**（容器）。
* **矢量/图形类**：`VECTOR`、`PEN`、`REGULAR_POLYGON`、`ELLIPSE` 统一映射为 **Image**（若有导出图）或 Ray 支持的矢量/图标方案；内部样式按 SVG 路径与填充规则解析（见第二步）。
* **文字类**：文字节点统一映射为 **Text**，不得使用 HTML 的 `span`/`p`。
* **导航栏（NavBar）**：设计稿中的顶部导航栏区域（节点 name 含 "NavBar"、"导航栏"、"Navigation" 等）**必须使用 `@ray-js/smart-ui` 的 `NavBar` 组件**，禁止用 View/Text 手动拼写。根据设计稿内容映射 NavBar 的 props：
  * 标题文本 → `title`
  * 左侧返回箭头 → `leftArrow`
  * 左侧文本 → `leftText` + `leftTextType`
  * 右侧图标 → `rightIcon`（配合 `@tuya-miniapp/icons`）+ `rightIconSize`
  * 右侧文本 → `rightText` + `rightTextColor`
  * 背景色 → `background`
  * 自定义右侧插槽 → `slot={{ right: ... }}`
  * 两侧宽度 → `sideWidth`（`"min"` / `"mid"` / `"max"` 或具体 px 值，**注意**：部分版本的 smart-ui NavBar 类型定义中不包含 `sideWidth`，需确认当前版本支持）
  * **深色背景适配** → 通过 `customClass` 注入 CSS 变量设置白色文字。**注意**：使用 `leftTextType="home"` 时，颜色变量为 `--nav-bar-home-text-color`（非 `--nav-bar-text-color`）。完整变量与 `className` / `customClass` 区别见 → [09-smart-ui-dark-theme.md](./09-smart-ui-dark-theme.md)
  * 导航栏内的 **StatusBar / Capsule** 节点按排除规则跳过，不生成代码。
* **分段选择器 / 分类切换（Tabs）**：设计稿中带有多个并排标签且仅一个激活态（高亮背景/下划线）的区域，识别为**分段选择器**或**标签切换**，**应使用 `@ray-js/smart-ui` 的 `Tabs` + `Tab` 组件**，禁止用多个 View/Text 手动拼写切换逻辑。根据设计稿视觉样式选择 Tabs 类型：
  * **下划线指示器**（标签下方有滑动条）→ `type="line"`（默认）
  * **分段控制器**（圆角容器 + 激活项高亮背景色，类似 iOS UISegmentedControl）→ `type="card"`
  * 每个标签映射为一个 `<Tab title={...} name={...} />`，通过 `active` + `name` 控制激活状态
  * 切换事件通过 `onChange` 回调获取 `e.detail.name`
  * **深色背景适配** → 通过 `customClass` 注入 CSS 变量（`--tabs-card-*` 系列），详见 → [09-smart-ui-dark-theme.md](./09-smart-ui-dark-theme.md)

---

## 视觉流与 Flex 推导（D2C 逻辑）

### 步骤 0：子节点预处理

* 仅对「有子节点」的容器做 Flex 推导；叶子节点不参与。
* 若设计稿中节点带 `layoutMode` / `primaryAxisAlign` 等布局字段，优先按设计稿语义映射；否则按下述绝对坐标推断。

### 步骤 1：视觉流排序 (DOM 顺序)

* **主序 (Y)**：兄弟节点按 `relativeY`（或 `y`，来自 layoutStyle/layout，见 [07-dsl-fields.md](./07-dsl-fields.md)）升序。
* **同行判定**：两节点 Y 方向中心差 < 10px 视为同一行；同一行内按 `relativeX` 升序。
* **结果**：DOM 顺序 = 从上到下、同行内从左到右，保证 Flex 流与视觉一致。

### 步骤 2：主轴方向 (flex-direction)

* **Row 判定**：子节点在 Y 方向分布集中（例如所有子节点 `relativeY` 的极差 < 8px，或 Y 方差远小于 X 方差），则主轴为水平 → `flex-direction: row`。
* **Column 判定**：子节点在 X 方向分布集中（所有子节点 `relativeX` 极差 < 8px，或 X 方差远小于 Y 方差），则主轴为垂直 → `flex-direction: column`。
* **单子**：单子节点时默认 `column`，避免多余横向拉伸。
* **多行/多列**：若明显存在多行（Row 时）或多列（Column 时），且行/列间间距一致，考虑 `flex-wrap: wrap` 并配合 `gap`（或 `row-gap` / `column-gap`）还原。
* **阈值与单位**：上述 8px / 10px 为设计稿坐标下的判定阈值；写出 Less 时长度、间距、padding、gap 等统一换算为 **rpx**。

### 步骤 3：内边距 (padding)

* **坐标归零化**：子节点相对父容器左、上、右、下的最小距离记为 `minLeft`、`minTop`、`minRight`、`minBottom`（基于子节点左/上边与右/下边与父边界的距离）。
* **映射**：`padding-left: minLeft`，`padding-top: minTop`，`padding-right: minRight`，`padding-bottom: minBottom`。
* **约束**：子节点进入 Flex 后不再使用绝对 `relativeX/Y` 定位，仅靠 padding + gap + 对齐 还原位置。

### 步骤 4：主轴间距 (gap vs margin)

* **沿主轴**：按「排序后的顺序」计算相邻两子节点在主轴方向的间距（如 Row 时为后一子 left − 前一子 right；Column 时为后一子 top − 前一子 bottom）。
* **一致间距**：若所有相邻间距相等（或差异 ≤ 1px），使用 `gap`（或 Row 时 `column-gap`、Column 时 `row-gap`）统一设置。
* **不一致间距**：若间距有多档，优先对「多数相同」的那档用 `gap`，其余用个别子节点的 `margin-left` / `margin-top` 等补足；或全部用 margin 分别设置。
* **双轴均有间距**：若横向、纵向均有稳定间距（如网格），可同时设 `gap` 或 `column-gap` + `row-gap`（或 Less 中 `gap: row col`）。

### 步骤 5：主轴对齐 (justify-content)

* **space-between**：第一个子节点贴父容器主轴起点、最后一个贴主轴终点，且中间有间隙 → `justify-content: space-between`。
* **space-around / space-evenly**：子节点之间、以及首尾与边缘的空白成比例分布时，可对应 `space-around` 或 `space-evenly`。
* **flex-start**：子节点整体紧贴主轴起点 → `justify-content: flex-start`（默认）。
* **flex-end**：子节点整体紧贴主轴终点 → `justify-content: flex-end`。
* **center**：子节点整体在主轴方向居中 → `justify-content: center`。

### 步骤 6：交叉轴对齐 (align-items)

* **center**：所有子节点在交叉轴方向居中（如 Row 时垂直居中）→ `align-items: center`。
* **flex-start / flex-end**：子节点贴交叉轴起点或终点 → `align-items: flex-start` 或 `flex-end`。
* **stretch**：子节点在交叉轴方向填满（无固定高度/宽度）→ `align-items: stretch`（默认）。
* **基线**：若为多行文本/图标对齐，可按需使用 `align-items: baseline`（少见）。

### 步骤 7：网格列均匀分布

设计稿中等间距网格（如场景图标 4 列、颜色圆圈 5 列）需要在容器内均匀分布。**常见错误**：使用固定 rpx 宽度 + `justify-content: flex-start`，导致列全部靠左、右侧留白。

**正确策略**：

* **N 列网格**：单元格 `width: calc(100% / N)` 或 `width: 25%`（4列）/ `width: 20%`（5列），配合 `justify-content: space-between` 或直接让内容在单元格内居中。
* **`gap` 与列数的关系**：若使用 `gap`，需确保 `N × cellWidth + (N-1) × gap ≤ 容器宽度`，否则实际每行列数会与预期不符。
* **`space-between` + `flex-wrap` 的末行问题**：若最后一行不满 N 列，`space-between` 会让少量元素分散到两端。解决方案：(1) 使用百分比宽度而非 `space-between`；(2) 添加不可见的占位元素填满最后一行。

```less
// ✅ 推荐：百分比宽度 + 内容居中
.grid {
  display: flex;
  flex-wrap: wrap;
  row-gap: 32rpx;
}
.cell {
  width: 25%; // 4 列
  display: flex;
  flex-direction: column;
  align-items: center;
}

// ❌ 容易出错：固定宽度 + flex-start
.cell {
  width: 124rpx; // 右侧会有大块空白
}
```

---

### 步骤 8：屏幕尺寸适配

设计稿通常基于 375×812（iPhone X）绘制，但实际设备屏幕高度差异很大（如 iPhone 8 为 667px、SE 为 568px）。将设计稿 Y 坐标直接换算为固定 `margin-top` rpx 值会导致小屏设备内容溢出。

**适配策略**：

* **纵向间距使用弹性布局**：不要用固定 `margin-top` 堆叠所有区块。对主内容区（如卡片列表、网格）使用 `flex: 1` 自适应剩余空间。
* **背景区域的 height 可以固定**（用于装饰性背景图），但主流布局不依赖固定高度。
* **底部区域**使用 `env(safe-area-inset-bottom)` 适配不同设备的安全区。
* **大间距谨慎处理**：设计稿中导航到第一个内容区之间的大间距（如 200+px），在小屏上应适当缩减。可使用百分比或 `calc()` 替代固定值。

**实际换算参考**（设计稿 375×812 → rpx）：
* 设计稿坐标 × 2 = rpx（因 1px 设计稿 = 2rpx）
* 但**纵向间距要考虑小屏适配**，不能机械换算

---

### 步骤 9：固定底栏 + 滚动内容布局

设计稿高度超过一屏（如编辑页 1021px > 812px）时，需要：底部操作栏始终可见，中间内容可滚动。

**标准实现**：

```tsx
<View className={styles.page}>
  <View className={styles.navBar}>...</View>
  <ScrollView className={styles.scrollArea} scrollY>
    <View className={styles.card}>...</View>
    <View className={styles.card}>...</View>
  </ScrollView>
  <View className={styles.bottomBar}>...</View>
</View>
```

```less
.page {
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow: hidden;
}
.scrollArea {
  flex: 1;
  overflow: hidden;
}
.bottomBar {
  padding: 32rpx 24rpx;
  padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
}
```

**关键要点**：
* `.page` 使用 `height: 100vh`（非 `min-height`）+ `overflow: hidden`，否则底栏会被推出屏幕。
* `ScrollView` 设为 `flex: 1` 占满中间剩余空间，必须加 `scrollY` 属性。
* 底栏使用 `env(safe-area-inset-bottom)` 适配刘海屏安全区。

---

### 步骤 10：冗余与溢出

* **冗余 Frame**：`type === "FRAME"` 且无 `fills`、无 `effects` 时，视为逻辑组，不单独生成包裹层，仅保留 Flex 属性；若仅有一子且无样式则坍塌到父级。
* **冗余纯色矩形**：当某子节点为「仅纯色矩形」且兄弟中存在带图片填充的节点时，可过滤该纯色矩形不生成（与 [07-dsl-fields.md](./07-dsl-fields.md) 中 filterRedundantBackgroundRects 一致）。
* **溢出**：设计稿中 Frame 开启裁剪时 → `overflow: hidden`；节点宽度接近父级 → `width: 100%`。
