# 平面图编辑器 API 文档

## 接口概述

| 项目   | 说明                          |
|------|-----------------------------|
| 接口前缀 | `/shop/schedule/floor-plan` |
| 所属模块 | Schedule                    |
| 鉴权方式 | Shop 端 Token（同其他 /shop/ 接口） |
| 说明   | 平面图编辑器 CRUD，用于管理店铺的资源平面布局   |

## 接口列表

| 方法     | 路径                                      | 说明           |
|--------|-----------------------------------------|--------------|
| GET    | `/shop/schedule/floor-plan`             | 获取平面图列表      |
| GET    | `/shop/schedule/floor-plan/{id}`        | 通过 ID 获取详情   |
| GET    | `/shop/schedule/floor-plan/code/{code}` | 通过 code 获取详情 |
| POST   | `/shop/schedule/floor-plan`             | 创建平面图        |
| PUT    | `/shop/schedule/floor-plan/{id}`        | 更新平面图        |
| DELETE | `/shop/schedule/floor-plan/{id}`        | 删除平面图        |

---

## 1. 获取平面图列表

### 请求

```
GET /shop/schedule/floor-plan
```

无参数，返回当前店铺的所有平面图，按 sort 升序、id 倒序排列。

### 响应示例

> `name` 字段：当请求 Header 携带语言标识时返回对应语言的字符串；未设置时返回完整的多语言 JSON 对象。

```json
{
  "status": true,
  "code": 200,
  "message": "",
  "data": [
    {
      "id": 1,
      "shop_id": 123,
      "code": "main_area",
      "name": "Main Area",
      "layout": [
        {
          "id": "shape_uuid_1",
          "type": "rect",
          "label": "T-1",
          "x": 50,
          "y": 100,
          "width": 150,
          "height": 120,
          "rotation": 0,
          "color": "#1a2332",
          "border_color": "#ffffff",
          "resource_id": 46971
        }
      ],
      "canvas_width": 1920,
      "canvas_height": 1080,
      "sort": 1,
      "status": 1,
      "created_at": "2026-03-20T20:00:00.000000Z",
      "updated_at": "2026-03-20T20:00:00.000000Z"
    },
    {
      "id": 2,
      "shop_id": 123,
      "code": "vip_area",
      "name": "VIP Area",
      "layout": [],
      "canvas_width": 1920,
      "canvas_height": 1080,
      "sort": 2,
      "status": 1,
      "created_at": "2026-03-20T20:00:00.000000Z",
      "updated_at": "2026-03-20T20:00:00.000000Z"
    }
  ]
}
```

---

## 2. 通过 ID 获取平面图详情

### 请求

```
GET /shop/schedule/floor-plan/{id}
```

| 参数 | 位置  | 类型  | 必填 | 说明     |
|----|-----|-----|----|--------|
| id | URL | int | 是  | 平面图 ID |

### 响应示例

```json
{
  "status": true,
  "code": 200,
  "message": "",
  "data": {
    "id": 1,
    "shop_id": 123,
    "code": "main_area",
    "name": "Main Area",
    "layout": [
      {
        "id": "shape_uuid_1",
        "type": "rect",
        "label": "T-1",
        "x": 50,
        "y": 100,
        "width": 150,
        "height": 120,
        "rotation": 0,
        "color": "#1a2332",
        "border_color": "#ffffff",
        "resource_id": 46971
      },
      {
        "id": "shape_uuid_2",
        "type": "circle",
        "label": "B-1",
        "x": 80,
        "y": 500,
        "radius": 40,
        "rotation": 0,
        "color": "#e67e22",
        "border_color": "#e67e22",
        "resource_id": 44153
      }
    ],
    "canvas_width": 1920,
    "canvas_height": 1080,
    "sort": 1,
    "status": 1,
    "created_at": "2026-03-20T20:00:00.000000Z",
    "updated_at": "2026-03-20T20:00:00.000000Z"
  }
}
```

### 错误响应（不存在）

```json
{
  "status": false,
  "code": 404,
  "message": "Not Found"
}
```

---

## 3. 通过 code 获取平面图详情

### 请求

```
GET /shop/schedule/floor-plan/code/{code}
```

| 参数   | 位置  | 类型     | 必填 | 说明          |
|------|-----|--------|----|-------------|
| code | URL | string | 是  | 平面图编码，店铺内唯一 |

### Demo 请求

```
GET /shop/schedule/floor-plan/code/main_area
```

### 响应示例

与「通过 ID 获取详情」返回结构完全一致。

### 错误响应（不存在）

```json
{
  "status": false,
  "code": 404,
  "message": "Not Found"
}
```

---

## 4. 创建平面图

### 请求

```
POST /shop/schedule/floor-plan
```

| 参数            | 类型     | 必填 | 说明                                                     |
|---------------|--------|----|--------------------------------------------------------|
| code          | string | 是  | 平面图编码，店铺内唯一，最长 100 字符                                  |
| name          | object | 是  | 平面图名称，多语言 JSON，如 `{"en": "Main Area", "zh-CN": "主区域"}` |
| layout        | array  | 否  | 画布布局数据，图形数组；新建时可为空                                     |
| canvas_width  | int    | 否  | 画布宽度(px)，默认 1920                                       |
| canvas_height | int    | 否  | 画布高度(px)，默认 1080                                       |
| sort          | int    | 否  | 排序，默认 0                                                |
| status        | int    | 否  | 状态：1=启用 0=禁用，默认 1                                      |

### 请求示例

```json
{
  "code": "main_area",
  "name": {
    "en": "Main Area",
    "zh-CN": "主区域"
  },
  "layout": [
    {
      "id": "shape_uuid_1",
      "type": "rect",
      "label": "T-1",
      "x": 50,
      "y": 100,
      "width": 150,
      "height": 120,
      "rotation": 0,
      "color": "#1a2332",
      "border_color": "#ffffff",
      "resource_id": 46971
    }
  ],
  "canvas_width": 1920,
  "canvas_height": 1080,
  "sort": 1
}
```

### 成功响应

```json
{
  "status": true,
  "code": 200,
  "message": "",
  "data": {
    "id": 1,
    "shop_id": 123,
    "code": "main_area",
    "name": {
      "en": "Main Area",
      "zh-CN": "主区域"
    },
    "layout": [
      ...
    ],
    "canvas_width": 1920,
    "canvas_height": 1080,
    "sort": 1,
    "status": 1,
    "created_at": "2026-03-20T20:00:00.000000Z",
    "updated_at": "2026-03-20T20:00:00.000000Z"
  }
}
```

### 错误响应（code 重复）

```json
{
  "status": false,
  "code": 5001,
  "message": "平面图编码已存在。"
}
```

> 错误提示根据请求语言自动切换（en/zh-CN/zh-HK/ja/pt）。

---

## 5. 更新平面图

### 请求

```
PUT /shop/schedule/floor-plan/{id}
```

| 参数            | 位置   | 类型     | 必填 | 说明             |
|---------------|------|--------|----|----------------|
| id            | URL  | int    | 是  | 平面图 ID         |
| code          | Body | string | 否  | 平面图编码，店铺内唯一    |
| name          | Body | object | 否  | 平面图名称，多语言 JSON |
| layout        | Body | array  | 否  | 画布布局数据（整体覆盖）   |
| canvas_width  | Body | int    | 否  | 画布宽度(px)       |
| canvas_height | Body | int    | 否  | 画布高度(px)       |
| sort          | Body | int    | 否  | 排序             |
| status        | Body | int    | 否  | 状态：1=启用 0=禁用   |

> **注意**：`layout` 是整体覆盖，前端每次保存需提交完整的图形数组。

### 请求示例（仅更新布局）

```json
{
  "layout": [
    {
      "id": "shape_uuid_1",
      "type": "rect",
      "label": "T-1",
      "x": 100,
      "y": 200,
      "width": 150,
      "height": 120,
      "rotation": 0,
      "color": "#1a2332",
      "border_color": "#ffffff",
      "resource_id": 46971
    },
    {
      "id": "shape_uuid_2",
      "type": "circle",
      "label": "B-1",
      "x": 300,
      "y": 400,
      "radius": 40,
      "rotation": 0,
      "color": "#e67e22",
      "border_color": "#e67e22",
      "resource_id": 44153
    }
  ]
}
```

### 成功响应

```json
{
  "status": true,
  "code": 200,
  "message": "",
  "data": {
    "id": 1,
    "shop_id": 123,
    "code": "main_area",
    "name": "Main Area",
    "layout": [
      ...
    ],
    "canvas_width": 1920,
    "canvas_height": 1080,
    "sort": 1,
    "status": 1,
    "created_at": "2026-03-20T20:00:00.000000Z",
    "updated_at": "2026-03-20T21:00:00.000000Z"
  }
}
```

---

## 6. 删除平面图

### 请求

```
DELETE /shop/schedule/floor-plan/{id}
```

| 参数 | 位置  | 类型  | 必填 | 说明     |
|----|-----|-----|----|--------|
| id | URL | int | 是  | 平面图 ID |

### 成功响应

```json
{
  "status": true,
  "code": 200,
  "message": "",
  "data": null
}
```

---

## 数据表字段说明

### schedule_floor_plan 表

| 字段            | 类型            | 说明                                               |
|---------------|---------------|--------------------------------------------------|
| id            | bigint, PK    | 主键                                               |
| shop_id       | bigint, INDEX | 店铺 ID                                            |
| code          | varchar(100)  | 平面图编码，shop_id + code 联合唯一                        |
| name          | json          | 平面图名称，多语言 JSON，如 `{"en": "Main", "zh-CN": "主区"}` |
| layout        | json          | 画布布局数据                                           |
| canvas_width  | int           | 画布宽度(px)，默认 1920                                 |
| canvas_height | int           | 画布高度(px)，默认 1080                                 |
| sort          | int           | 排序，默认 0                                          |
| status        | tinyint       | 状态：1=启用 0=禁用，默认 1                                |
| created_at    | timestamp     | 创建时间                                             |
| updated_at    | timestamp     | 更新时间                                             |
| deleted_at    | timestamp     | 软删除时间                                            |

### layout 数组中单个图形的字段说明

| 字段           | 类型       | 说明                                  |
|--------------|----------|-------------------------------------|
| id           | string   | 图形唯一标识（前端生成 UUID）                   |
| type         | string   | 图形类型：`rect`（矩形）、`circle`（圆形）等       |
| label        | string   | 图形显示标签，如 "T-1"、"B-2"                |
| x            | number   | 左上角 X 坐标                            |
| y            | number   | 左上角 Y 坐标                            |
| width        | number   | 宽度（type=rect 时）                     |
| height       | number   | 高度（type=rect 时）                     |
| radius       | number   | 半径（type=circle 时）                   |
| rotation     | number   | 旋转角度（度）                             |
| color        | string   | 填充颜色，如 "#1a2332"                    |
| border_color | string   | 边框颜色                                |
| resource_id  | int/null | 关联的资源 ID（form_record.id），未关联时为 null |

> layout 字段结构由前端定义，后端仅做 JSON 存储，不校验内部结构。前端可根据需要扩展图形属性。

---

## 实时推送（Socket 通知）

平面图的创建、更新、删除操作会自动通过 Socket 推送变更通知到终端设备。

### 防抖机制

- 使用 `ChangeCollectorService` 防抖，默认 3 秒窗口
- 窗口期内的多次操作（如快速连续保存）会合并为一次通知
- 通过 `ably` 队列异步推送

### 推送消息格式

#### 创建/更新（operation = change）

单条变更（含完整 body）：

```json
{
  "module": "floor_plan",
  "action": "change",
  "operation": "change",
  "id": 1,
  "body": {
    "id": 1,
    "shop_id": 123,
    "code": "main_area",
    "name": {
      "en": "Main Area",
      "zh-CN": "主区域"
    },
    "layout": [
      ...
    ],
    "canvas_width": 1920,
    "canvas_height": 1080,
    "sort": 1,
    "status": 1
  },
  "message_uuid": "3a9301fef9345cd90bc061c6531454a8",
  "timestamp": "2026-03-24T10:00:00+08:00"
}
```

窗口期内多次变更（仅 ids，前端需调接口获取最新数据）：

```json
{
  "module": "floor_plan",
  "action": "change",
  "operation": "change",
  "ids": [
    1,
    2
  ],
  "body": {},
  "message_uuid": "...",
  "timestamp": "2026-03-24T10:00:00+08:00"
}
```

#### 删除（operation = delete）

```json
{
  "module": "floor_plan",
  "action": "change",
  "operation": "delete",
  "ids": [
    1
  ],
  "body": {},
  "message_uuid": "...",
  "timestamp": "2026-03-24T10:00:00+08:00"
}
```

### 消息字段说明

| 字段           | 类型           | 说明                           |
|--------------|--------------|------------------------------|
| module       | string       | 固定为 `floor_plan`             |
| action       | string       | 固定为 `change`，表示数据有变更         |
| operation    | string       | `change`=创建/更新，`delete`=删除   |
| id           | int          | 单条变更时的平面图 ID（与 body 互斥于 ids） |
| ids          | array\<int\> | 批量变更时的平面图 ID 列表              |
| body         | object       | 单条变更时的完整数据；批量时为空对象 `{}`      |
| message_uuid | string       | 消息唯一标识                       |
| timestamp    | string       | 消息发送时间（ISO 8601）             |
