[FEAT] 重構與深化 MQTT 通訊規範並優化 API 文件中心
1. 重構 config/api-docs.php,將 HTTP API 與 MQTT Topic 徹底解耦,建立清晰的協議分類。 2. 實作 api-docs.blade.php 雙頁籤 (HTTP/MQTT) 介面,優化「極簡奢華風」視覺呈現與互動體驗。 3. 詳細定義 MQTT 指令下發 Payload,包含遠端出貨、庫存同步、遠端找零及系統控制。 4. 新增 Heartbeat (心跳) 與 Transaction (交易) MQTT 主題規範,確保 APP 端有明確的對接基準。 5. 修正 Heartbeat Payload 參數,僅保留實際傳送的溫度與韌體版本,確保文件與現況對齊。 6. 移除 B012 (商品同步) 尚未實作的 PATCH 方法,統一只保留 GET 方法以維持系統一致性。 7. 同步更新 SKILL.md 技術規格文件,確保開發規範與實際程式碼邏輯完全對等。
This commit is contained in:
parent
9114269a7a
commit
ae554ef130
@ -116,9 +116,9 @@ description: 本技能規範定義了 Star Cloud 系統中所有機台 (IoT) 與
|
||||
|
||||
|
||||
### 3.5 B012: 商品配置與商品主檔同步 (Unified Sync)
|
||||
用於機台端獲取目前所有可販售商品的詳細配置。App 端應依據呼叫的方法決定數據處理方式。
|
||||
用於機台端獲取目前所有可販售商品的詳細配置。
|
||||
|
||||
- **URL**: GET|PATCH /api/v1/app/machine/products/B012
|
||||
- **URL**: GET /api/v1/app/machine/products/B012
|
||||
- **Authentication**: Bearer Token (Header)
|
||||
- **Request Body:** 無 (由 Token 自動識別機台)
|
||||
|
||||
@ -262,30 +262,6 @@ description: 本技能規範定義了 Star Cloud 系統中所有機台 (IoT) 與
|
||||
|
||||
---
|
||||
|
||||
### 3.11 B055: 遠端指令出貨控制 (Remote Dispense / Force Open)
|
||||
用於遠端手動驅動機台出貨。通常用於補償使用者、測試機台或客服協助開門的情景。
|
||||
|
||||
- **URL**: GET|PUT /api/v1/app/machine/dispense/B055
|
||||
- **Authentication**: Bearer Token (Header)
|
||||
- **運作模式**:
|
||||
- **GET (查詢)**:當收到 **MQTT 指令 (`force_dispense` 或 `compensation`)** 時呼叫。雲端會回傳待執行的貨道編號與指令 ID。
|
||||
- **PUT (回報)**:實體出貨完成後回饋結果,以便雲端將該指令標記為「已執行」。
|
||||
|
||||
- **Response Body (GET - 查詢階段):**
|
||||
|
||||
| 參數 | 類型 | 說明 | 範例 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| data | Array | 指令物件陣列 | [{"res1": "ID", "res2": "SlotNo"}] |
|
||||
|
||||
- **Request Body (PUT - 回報階段):**
|
||||
|
||||
| 參數 | 類型 | 必填 | 說明 | 範例 |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| id | String | 是 | 雲端下發的指令 ID | "99" |
|
||||
| type | String | 是 | 出貨類型代碼 (0: 成功, 其他: 失敗) | "0" |
|
||||
| stock | String | 是 | 出貨後的貨道剩餘數量 | "9" |
|
||||
|
||||
---
|
||||
|
||||
## 4. 已遷移至 MQTT 的端點索引
|
||||
|
||||
|
||||
@ -198,24 +198,23 @@ Mqtt3Client client = MqttClient.builder()
|
||||
```json
|
||||
{
|
||||
"command": "dispense",
|
||||
"command_id": "20",
|
||||
"payload": {
|
||||
"slot_no": 5,
|
||||
"transaction_id": "T202604140001"
|
||||
},
|
||||
"command_id": "MSG_123456789"
|
||||
"new_stock": 6,
|
||||
"old_stock": 7,
|
||||
"slot_no": "1"
|
||||
}
|
||||
}
|
||||
```
|
||||
**常用指令集:**
|
||||
- `reboot`: 遠端重啟機台系統。
|
||||
- `reboot_card`: 遠端重啟讀卡機。
|
||||
- `checkout`: 遠端結帳/登出指令。
|
||||
- `lock`: 遠端鎖定機台(暫停服務)。
|
||||
- `unlock`: 遠端解鎖機台(恢復服務)。
|
||||
- `change`: 遠端修改數值(夾帶 amount 參數)。
|
||||
- `reload_config`: 重新下載參數 (B014)。
|
||||
- `reload_products`: 重新同步商品 (B012,適用於商品主檔、圖文變更)。
|
||||
- `update_inventory`: 遠端即時修改庫存 (直接夾帶數量更新 SQLite,取代 HTTP)。
|
||||
- `dispense`: 遠端出貨指令 (B055)。
|
||||
- `checkout`: 遠端結帳。
|
||||
- `lock`: 鎖定頁鎖定。
|
||||
- `unlock`: 鎖定頁解鎖。
|
||||
- `change`: 遠端找零(夾帶 amount 參數)。
|
||||
- `update_inventory`: 遠端即時修改庫存。
|
||||
- `dispense`: 遠端出貨指令。
|
||||
|
||||
**`update_inventory` Payload 範例:**
|
||||
```json
|
||||
@ -291,11 +290,11 @@ Mqtt3Client client = MqttClient.builder()
|
||||
## 5. Redis 橋接注意事項
|
||||
|
||||
### 5.1 Key 前綴一致性
|
||||
Laravel 的 Redis 驅動會自動在 Key 前加上前綴 (預設 `starcloud_database_`)。Go Gateway 不使用 Laravel Redis 客戶端,因此**必須在程式碼中手動加上此前綴**:
|
||||
Laravel 的 Redis 驅動會自動在 Key 前加上前綴 (預設 `star_cloud_database_`)。Go Gateway 不使用 Laravel Redis 客戶端,因此**必須在程式碼中手動加上此前綴**:
|
||||
|
||||
```go
|
||||
// main.go
|
||||
prefixedIncomingKey := "starcloud_database_" + cfg.IncomingQueueKey
|
||||
prefixedIncomingKey := "star_cloud_database_" + cfg.IncomingQueueKey
|
||||
pusher := bridge.NewRedisPusher(rdb, prefixedIncomingKey)
|
||||
```
|
||||
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'title' => 'Star Cloud IoT API 說明文件',
|
||||
'title' => 'Star Cloud IoT 通訊協議',
|
||||
'version' => 'v1.0.0',
|
||||
'description' => '此文件提供 Star Cloud 智能販賣機 IoT 端點通訊協議說明,供硬體端與前端開發者調研與串接使用。',
|
||||
'categories' => [
|
||||
'description' => '此文件提供 Star Cloud 智能販賣機 IoT 端點通訊協議說明,包含主動拉取 (HTTP REST) 與被動接收 (MQTT) 雙軌通訊機制,供硬體端與前端開發者調研與串接使用。',
|
||||
|
||||
'http_apis' => [
|
||||
[
|
||||
'name' => '機台核心通訊 (IoT Core)',
|
||||
'apis' => [
|
||||
@ -231,9 +232,9 @@ return [
|
||||
[
|
||||
'name' => 'B012: 商品配置與商品主檔同步 (Unified Sync)',
|
||||
'slug' => 'b012-unified-sync',
|
||||
'method' => 'GET/PATCH',
|
||||
'method' => 'GET',
|
||||
'path' => '/api/v1/app/machine/products/B012',
|
||||
'description' => '用於機台端獲取目前所有可販售商品的詳細配置。GET 為全量同步,PATCH 為增量更新。',
|
||||
'description' => '用於機台端獲取目前所有可販售商品的詳細配置 (全量同步)。',
|
||||
'headers' => [
|
||||
'Authorization' => 'Bearer <api_token>',
|
||||
'Content-Type' => 'application/json',
|
||||
@ -351,7 +352,7 @@ return [
|
||||
[
|
||||
'name' => 'B024: 取貨碼/通行碼驗證與消耗回報',
|
||||
'slug' => 'b024-access-code',
|
||||
'method' => 'POST/PUT',
|
||||
'method' => 'POST',
|
||||
'path' => '/api/v1/app/sell/access-code/B024',
|
||||
'description' => '處理代碼取貨流程。POST 用於驗證碼有效性,PUT 用於回報出貨成功並消耗代碼。',
|
||||
'headers' => [
|
||||
@ -388,7 +389,7 @@ return [
|
||||
[
|
||||
'name' => 'B027: 贈品碼/優惠券驗證與消耗回報',
|
||||
'slug' => 'b027-freebie-code',
|
||||
'method' => 'POST/PUT',
|
||||
'method' => 'POST',
|
||||
'path' => '/api/v1/app/sell/free-gift/B027',
|
||||
'description' => '處理贈品券與 0 元購活動。邏輯與 B024 相似但對象為行銷贈品。',
|
||||
'headers' => [
|
||||
@ -412,27 +413,169 @@ return [
|
||||
'message' => 'Free gift verified'
|
||||
],
|
||||
],
|
||||
]
|
||||
]
|
||||
],
|
||||
|
||||
'mqtt_topics' => [
|
||||
[
|
||||
'name' => '指令與通訊 (Commands & Flow)',
|
||||
'topics' => [
|
||||
[
|
||||
'name' => 'B055: 遠端指令出貨控制 (Remote Dispense)',
|
||||
'slug' => 'b055-remote-dispense',
|
||||
'method' => 'POST/PUT',
|
||||
'path' => '/api/v1/app/machine/dispense/B055',
|
||||
'description' => '遠端手動驅動機台出貨。POST 用於獲取待處理指令,PUT 用於回報出貨完成。',
|
||||
'headers' => [
|
||||
'Authorization' => 'Bearer <api_token>',
|
||||
'Content-Type' => 'application/json',
|
||||
'name' => '機台心跳上報 (Heartbeat)',
|
||||
'slug' => 'mqtt-heartbeat',
|
||||
'action' => 'PUB',
|
||||
'topic' => 'machine/{serial_no}/heartbeat',
|
||||
'qos' => 0,
|
||||
'description' => '機台每分鐘固定上報的狀態大包裝。整合了溫度、韌體版本與機台系統級日誌。',
|
||||
'payload_parameters' => [
|
||||
'temperature' => ['type' => 'number', 'description' => '目前機台溫度'],
|
||||
'firmware_version' => ['type' => 'string', 'description' => 'APP 韌體版本'],
|
||||
],
|
||||
'parameters' => [
|
||||
'id' => ['type' => 'string', 'description' => '指令 ID (PUT)'],
|
||||
'stock' => ['type' => 'string', 'description' => '剩餘庫存 (PUT)'],
|
||||
'payload_example' => [
|
||||
'temperature' => 20.5,
|
||||
'firmware_version' => '2.1.6'
|
||||
],
|
||||
'request' => [],
|
||||
'response' => [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
['slot_no' => '1', 'order_id' => 'RE-123']
|
||||
],
|
||||
[
|
||||
'name' => '交易生命週期上報 (Transaction)',
|
||||
'slug' => 'mqtt-transaction',
|
||||
'action' => 'PUB',
|
||||
'topic' => 'machine/{serial_no}/transaction',
|
||||
'qos' => 1,
|
||||
'description' => '整合交易生命週期的三個階段:交易建立 (create)、發票開立 (invoice) 以及出貨結果 (dispense)。',
|
||||
'payload_parameters' => [
|
||||
'action' => ['type' => 'string', 'description' => '生命週期動作 (create, invoice, dispense)'],
|
||||
'payment_type' => ['type' => 'string', 'description' => '支付方式 (action 為 create 時必填)'],
|
||||
'amount' => ['type' => 'number', 'description' => '交易金額 (create/invoice 時使用)'],
|
||||
'slot_no' => ['type' => 'string', 'description' => '出貨貨道號碼'],
|
||||
'invoice_no' => ['type' => 'string', 'description' => '發票號碼 (action 為 invoice 時必填)'],
|
||||
'status' => ['type' => 'string', 'description' => '出貨結果狀態 (action 為 dispense 時必填,如 success, failed)'],
|
||||
],
|
||||
'payload_example' => [
|
||||
'action' => 'create',
|
||||
'payment_type' => 'credit_card',
|
||||
'amount' => 25.0,
|
||||
'slot_no' => '3',
|
||||
'product_id' => '5'
|
||||
],
|
||||
'notes' => '備註:此端點雲端後台暫未實作對應邏輯,但 APP 端可先行按照此結構開發。'
|
||||
],
|
||||
[
|
||||
'name' => '指令下發:遠端出貨 (Dispense)',
|
||||
'slug' => 'mqtt-command-dispense',
|
||||
'action' => 'SUB',
|
||||
'topic' => 'machine/{serial_no}/command',
|
||||
'qos' => 1,
|
||||
'description' => '雲端主動下發「遠端出貨」指令。機台收到後應立即驅動馬達進行出貨,出貨完成或失敗後回報 ACK。',
|
||||
'payload_parameters' => [
|
||||
'command' => ['type' => 'string', 'description' => '固定為 "dispense"'],
|
||||
'command_id' => ['type' => 'string', 'description' => '指令唯一 ID'],
|
||||
'payload' => ['type' => 'object', 'description' => '出貨參數'],
|
||||
'payload.slot_no' => ['type' => 'string', 'description' => '欲出貨的貨道號碼'],
|
||||
'payload.old_stock' => ['type' => 'integer', 'description' => '出貨前的庫存量'],
|
||||
'payload.new_stock' => ['type' => 'integer', 'description' => '出貨後應剩餘的庫存量 (樂觀扣減結果)'],
|
||||
],
|
||||
'payload_example' => [
|
||||
'command' => 'dispense',
|
||||
'command_id' => '9988776655',
|
||||
'payload' => [
|
||||
'slot_no' => '1',
|
||||
'old_stock' => 10,
|
||||
'new_stock' => 9
|
||||
]
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => '指令下發:庫存同步 (Update Inventory)',
|
||||
'slug' => 'mqtt-command-update-inventory',
|
||||
'action' => 'SUB',
|
||||
'topic' => 'machine/{serial_no}/command',
|
||||
'qos' => 1,
|
||||
'description' => '雲端主動下發「庫存更新」指令 (如從後台修改庫存、效期時觸發)。',
|
||||
'payload_parameters' => [
|
||||
'command' => ['type' => 'string', 'description' => '固定為 "update_inventory"'],
|
||||
'command_id' => ['type' => 'string', 'description' => '指令唯一 ID'],
|
||||
'payload' => ['type' => 'object', 'description' => '更新參數'],
|
||||
'payload.data' => ['type' => 'array', 'description' => '欲更新的貨道資料陣列'],
|
||||
'payload.data[].slot_no' => ['type' => 'string', 'description' => '貨道號碼'],
|
||||
'payload.data[].stock' => ['type' => 'integer', 'description' => '最新庫存量'],
|
||||
'payload.data[].expiry_date' => ['type' => 'string', 'description' => '最新效期 (YYYY-MM-DD)'],
|
||||
'payload.data[].batch_no' => ['type' => 'string', 'description' => '批號 (可為 null)'],
|
||||
],
|
||||
'payload_example' => [
|
||||
'command' => 'update_inventory',
|
||||
'command_id' => '5566778899',
|
||||
'payload' => [
|
||||
'data' => [
|
||||
[
|
||||
'slot_no' => '1',
|
||||
'stock' => 10,
|
||||
'expiry_date' => '2026-12-31',
|
||||
'batch_no' => null
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => '指令下發:遠端找零 (Change)',
|
||||
'slug' => 'mqtt-command-change',
|
||||
'action' => 'SUB',
|
||||
'topic' => 'machine/{serial_no}/command',
|
||||
'qos' => 1,
|
||||
'description' => '雲端主動下發「找零」指令。指示機台退還指定金額的現金。',
|
||||
'payload_parameters' => [
|
||||
'command' => ['type' => 'string', 'description' => '固定為 "change"'],
|
||||
'command_id' => ['type' => 'string', 'description' => '指令唯一 ID'],
|
||||
'payload' => ['type' => 'object', 'description' => '找零參數'],
|
||||
'payload.amount' => ['type' => 'integer', 'description' => '欲找零/退款的金額'],
|
||||
],
|
||||
'payload_example' => [
|
||||
'command' => 'change',
|
||||
'command_id' => '1122334455',
|
||||
'payload' => [
|
||||
'amount' => 50
|
||||
]
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => '指令下發:系統控制 (System Control)',
|
||||
'slug' => 'mqtt-command-system',
|
||||
'action' => 'SUB',
|
||||
'topic' => 'machine/{serial_no}/command',
|
||||
'qos' => 1,
|
||||
'description' => '雲端主動下發的系統級別控制指令。包含重啟、設備鎖定解鎖、購物車結帳等無額外參數的指令。',
|
||||
'payload_parameters' => [
|
||||
'command' => ['type' => 'string', 'description' => '系統指令類型。支援:reboot (重啟機台), reboot_card (重啟刷卡機), checkout (購物車結帳), lock (設備鎖定), unlock (設備解鎖)'],
|
||||
'command_id' => ['type' => 'string', 'description' => '指令唯一 ID'],
|
||||
'payload' => ['type' => 'object', 'description' => '空物件 (無額外參數)'],
|
||||
],
|
||||
'payload_example' => [
|
||||
'command' => 'reboot',
|
||||
'command_id' => '9999999999',
|
||||
'payload' => new \stdClass()
|
||||
],
|
||||
],
|
||||
|
||||
[
|
||||
'name' => '指令執行回報 (Machine to Cloud)',
|
||||
'slug' => 'mqtt-command-ack',
|
||||
'action' => 'PUB',
|
||||
'topic' => 'machine/{serial_no}/command/ack',
|
||||
'qos' => 1,
|
||||
'description' => '機台執行完雲端指令後的主動回報。此回報極為重要,雲端將依此決定交易是否完成或需進行庫存回滾。',
|
||||
'payload_parameters' => [
|
||||
'command_id' => ['type' => 'string', 'description' => '對應下發時的指令 ID'],
|
||||
'result' => ['type' => 'string', 'description' => '執行結果,僅接受: success 或 failed'],
|
||||
'stock' => ['type' => 'integer', 'description' => '執行指令後的最終剩餘庫存 (選填,通常出貨指令需帶上)'],
|
||||
'message' => ['type' => 'string', 'description' => '額外的錯誤訊息 (選填)'],
|
||||
],
|
||||
'payload_example' => [
|
||||
'command_id' => '12345',
|
||||
'result' => 'success',
|
||||
'stock' => 9
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
@ -9,9 +9,7 @@
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap"
|
||||
rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- CSS (Vite or CDN for layout) -->
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
@ -21,7 +19,6 @@
|
||||
--color-luxury-deep: #f8fafc;
|
||||
--color-luxury-card: #ffffff;
|
||||
--color-accent: #0891b2;
|
||||
/* Cyan-600 for light mode */
|
||||
}
|
||||
|
||||
body {
|
||||
@ -51,25 +48,6 @@
|
||||
max-width: 1400px;
|
||||
}
|
||||
|
||||
.method-badge {
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
font-weight: 800;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.method-post {
|
||||
background: #0891b2;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.method-get {
|
||||
background: #10b981;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.param-table th {
|
||||
text-align: left;
|
||||
padding: 0.75rem 1rem;
|
||||
@ -94,6 +72,7 @@
|
||||
font-family: 'ui-monospace', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.luxury-nav-section {
|
||||
@ -138,7 +117,6 @@
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
@ -147,39 +125,78 @@
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body x-data="{ activeSection: '{{ $docs['categories'][0]['apis'][0]['slug'] ?? '' }}' }">
|
||||
<body x-data="{
|
||||
currentTab: 'http',
|
||||
activeSection: '{{ $docs['http_apis'][0]['apis'][0]['slug'] ?? '' }}'
|
||||
}">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar shadow-sm">
|
||||
<div class="p-6 border-b border-slate-100">
|
||||
<h1 class="font-display text-2xl font-black tracking-tighter text-slate-900">
|
||||
STAR CLOUD <span class="text-cyan-600">文件</span>
|
||||
<div class="sidebar shadow-sm flex flex-col">
|
||||
<div class="p-6 border-b border-slate-100 pb-4">
|
||||
<h1 class="font-display text-2xl font-black tracking-tighter text-slate-900 leading-none">
|
||||
STAR CLOUD <span class="text-cyan-600">API</span>
|
||||
</h1>
|
||||
<p class="text-[11px] font-black text-slate-400 uppercase tracking-widest mt-1">API 參考手冊 {{ $docs['version']
|
||||
}}</p>
|
||||
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest mt-1 mb-4">通訊參考手冊 {{ $docs['version'] }}</p>
|
||||
|
||||
<!-- Tab Controls -->
|
||||
<div class="flex gap-2 bg-slate-100 p-1 rounded-xl">
|
||||
<button @click="currentTab = 'http'; activeSection = '{{ $docs['http_apis'][0]['apis'][0]['slug'] ?? '' }}'"
|
||||
:class="currentTab === 'http' ? 'bg-white text-slate-900 shadow-sm' : 'text-slate-500 hover:text-slate-700'"
|
||||
class="flex-1 py-1.5 text-[11px] font-black uppercase tracking-widest rounded-lg transition-all">
|
||||
HTTP
|
||||
</button>
|
||||
<button @click="currentTab = 'mqtt'; activeSection = '{{ $docs['mqtt_topics'][0]['topics'][0]['slug'] ?? '' }}'"
|
||||
:class="currentTab === 'mqtt' ? 'bg-white text-slate-900 shadow-sm' : 'text-slate-500 hover:text-slate-700'"
|
||||
class="flex-1 py-1.5 text-[11px] font-black uppercase tracking-widest rounded-lg transition-all">
|
||||
MQTT
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="mt-2 overflow-y-auto" style="height: calc(100vh - 100px);">
|
||||
@foreach($docs['categories'] as $category)
|
||||
<div class="luxury-nav-section">{{ $category['name'] }}</div>
|
||||
@foreach($category['apis'] as $api)
|
||||
<a href="#{{ $api['slug'] }}" class="luxury-nav-link"
|
||||
:class="{ 'active': activeSection === '{{ $api['slug'] }}' }"
|
||||
@click="activeSection = '{{ $api['slug'] }}'">
|
||||
<div class="flex items-center gap-2 overflow-hidden w-full">
|
||||
<span class="flex-shrink-0 text-[10px] w-10 text-center font-black px-1.5 py-0.5 rounded-md uppercase tracking-tighter {{ $api['method'] === 'GET' ? 'bg-emerald-50 text-emerald-600 border border-emerald-200' : ($api['method'] === 'POST' ? 'bg-cyan-50 text-cyan-600 border border-cyan-200' : 'bg-amber-50 text-amber-600 border border-amber-200') }}">
|
||||
{{ $api['method'] }}
|
||||
</span>
|
||||
<span class="truncate">{{ $api['name'] }}</span>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
@endforeach
|
||||
<nav class="flex-1 overflow-y-auto py-2">
|
||||
<!-- HTTP Nav -->
|
||||
<div x-show="currentTab === 'http'" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100">
|
||||
@foreach($docs['http_apis'] as $category)
|
||||
<div class="luxury-nav-section">{{ $category['name'] }}</div>
|
||||
@foreach($category['apis'] as $api)
|
||||
<a href="#{{ $api['slug'] }}" class="luxury-nav-link"
|
||||
:class="{ 'active': activeSection === '{{ $api['slug'] }}' }"
|
||||
@click="activeSection = '{{ $api['slug'] }}'">
|
||||
<div class="flex items-center gap-2 overflow-hidden w-full">
|
||||
<span class="flex-shrink-0 text-[10px] w-10 text-center font-black px-1 py-0.5 rounded uppercase tracking-tighter {{ $api['method'] === 'GET' ? 'bg-emerald-50 text-emerald-600 border border-emerald-200' : ($api['method'] === 'POST' ? 'bg-cyan-50 text-cyan-600 border border-cyan-200' : 'bg-amber-50 text-amber-600 border border-amber-200') }}">
|
||||
{{ $api['method'] }}
|
||||
</span>
|
||||
<span class="truncate">{{ $api['name'] }}</span>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<!-- MQTT Nav -->
|
||||
<div x-show="currentTab === 'mqtt'" style="display: none;" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100">
|
||||
@foreach($docs['mqtt_topics'] as $category)
|
||||
<div class="luxury-nav-section">{{ $category['name'] }}</div>
|
||||
@foreach($category['topics'] as $topic)
|
||||
<a href="#{{ $topic['slug'] }}" class="luxury-nav-link"
|
||||
:class="{ 'active': activeSection === '{{ $topic['slug'] }}' }"
|
||||
@click="activeSection = '{{ $topic['slug'] }}'">
|
||||
<div class="flex items-center gap-2 overflow-hidden w-full">
|
||||
<span class="flex-shrink-0 text-[10px] w-10 text-center font-black px-1 py-0.5 rounded uppercase tracking-tighter {{ $topic['action'] === 'PUB' ? 'bg-violet-50 text-violet-600 border border-violet-200' : 'bg-indigo-50 text-indigo-600 border border-indigo-200' }}">
|
||||
{{ $topic['action'] }}
|
||||
</span>
|
||||
<span class="truncate">{{ $topic['name'] }}</span>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
@endforeach
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content">
|
||||
<div class="main-content pb-24">
|
||||
<!-- Title Card -->
|
||||
<div class="mb-10 p-8 bg-white rounded-3xl border border-slate-100 shadow-sm">
|
||||
<h2 class="font-display text-4xl font-black tracking-tight text-slate-900 mb-4">{{ $docs['title'] }}</h2>
|
||||
<p class="text-slate-500 leading-relaxed text-lg max-w-3xl font-medium">
|
||||
@ -187,176 +204,239 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@foreach($docs['categories'] as $category)
|
||||
@foreach($category['apis'] as $api)
|
||||
<div id="{{ $api['slug'] }}" class="mb-16 animate-luxury-in" x-intersect="activeSection = '{{ $api['slug'] }}'">
|
||||
<div class="mb-4"></div>
|
||||
<!-- HTTP Content -->
|
||||
<div x-show="currentTab === 'http'" x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 transform translate-y-4" x-transition:enter-end="opacity-100 transform translate-y-0">
|
||||
@foreach($docs['http_apis'] as $category)
|
||||
@foreach($category['apis'] as $api)
|
||||
<div id="{{ $api['slug'] }}" class="mb-16 animate-luxury-in scroll-mt-10" x-intersect.margin.-200px="activeSection = '{{ $api['slug'] }}'">
|
||||
|
||||
<div class="flex items-center gap-4 mb-6">
|
||||
<span class="px-3 py-1 text-sm font-black rounded-lg uppercase tracking-widest {{ $api['method'] === 'GET' ? 'bg-emerald-100 text-emerald-700' : ($api['method'] === 'POST' ? 'bg-cyan-100 text-cyan-700' : 'bg-amber-100 text-amber-700') }}">
|
||||
{{ $api['method'] }}
|
||||
</span>
|
||||
<h3 class="font-display text-3xl font-black text-slate-900 tracking-tight">{{ $api['name'] }}</h3>
|
||||
</div>
|
||||
<p class="text-slate-600 mb-8 text-lg font-medium">{{ $api['description'] }}</p>
|
||||
<div class="flex items-center gap-4 mb-6">
|
||||
<span class="px-3 py-1 text-sm font-black rounded-lg uppercase tracking-widest {{ $api['method'] === 'GET' ? 'bg-emerald-100 text-emerald-700' : ($api['method'] === 'POST' ? 'bg-cyan-100 text-cyan-700' : 'bg-amber-100 text-amber-700') }}">
|
||||
{{ $api['method'] }}
|
||||
</span>
|
||||
<h3 class="font-display text-3xl font-black text-slate-900 tracking-tight">{{ $api['name'] }}</h3>
|
||||
</div>
|
||||
<p class="text-slate-600 mb-8 text-lg font-medium">{{ $api['description'] }}</p>
|
||||
|
||||
<!-- Headers & URL -->
|
||||
<div class="mb-6 overflow-hidden rounded-2xl border border-slate-200">
|
||||
<div class="bg-slate-900 px-6 py-4 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<span
|
||||
class="px-3 py-1 bg-cyan-500 text-white text-xs font-black rounded-lg uppercase letter-spacing-widest">{{
|
||||
$api['method'] }}</span>
|
||||
<code
|
||||
class="text-slate-300 font-mono text-base">{{ config('app.url') }}{{ $api['path'] }}</code>
|
||||
</div>
|
||||
<span
|
||||
class="text-slate-500 text-[10px] font-black uppercase tracking-widest hidden sm:block">Endpoint
|
||||
URL</span>
|
||||
</div>
|
||||
<div class="bg-slate-50 p-6 border-t border-slate-200">
|
||||
<h4
|
||||
class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4 flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 00-2 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
||||
</svg>
|
||||
請求標頭 (Headers)
|
||||
</h4>
|
||||
<div class="space-y-4">
|
||||
@foreach($api['headers'] as $key => $val)
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4">
|
||||
<span class="text-slate-500 font-bold min-w-[140px] text-sm font-mono tracking-tight">{{
|
||||
$key }}</span>
|
||||
<div class="flex-1">
|
||||
<code
|
||||
class="text-cyan-700 font-bold bg-white px-3 py-1.5 rounded-lg border border-cyan-100 text-sm italic shadow-sm break-all block sm:inline-block w-full sm:w-auto">
|
||||
{{ $val }}
|
||||
</code>
|
||||
<!-- Headers & URL -->
|
||||
<div class="mb-6 overflow-hidden rounded-2xl border border-slate-200">
|
||||
<div class="bg-slate-900 px-6 py-4 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="px-3 py-1 bg-cyan-500 text-white text-xs font-black rounded-lg uppercase letter-spacing-widest">{{ $api['method'] }}</span>
|
||||
<code class="text-slate-300 font-mono text-base break-all">{{ config('app.url') }}{{ $api['path'] }}</code>
|
||||
</div>
|
||||
<span class="text-slate-500 text-[10px] font-black uppercase tracking-widest hidden sm:block">Endpoint URL</span>
|
||||
</div>
|
||||
@if(isset($api['headers']) && count($api['headers']) > 0)
|
||||
<div class="bg-slate-50 p-6 border-t border-slate-200">
|
||||
<h4 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">請求標頭 (Headers)</h4>
|
||||
<div class="space-y-4">
|
||||
@foreach($api['headers'] as $key => $val)
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4">
|
||||
<span class="text-slate-500 font-bold min-w-[140px] text-sm font-mono tracking-tight">{{ $key }}</span>
|
||||
<div class="flex-1">
|
||||
<code class="text-cyan-700 font-bold bg-white px-3 py-1.5 rounded-lg border border-cyan-100 text-sm italic shadow-sm block sm:inline-block">{{ $val }}</code>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Parameters -->
|
||||
@if(isset($api['parameters']) && count($api['parameters']) > 0)
|
||||
<div class="mb-8">
|
||||
<h4 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">請求參數 (Parameters)</h4>
|
||||
<div class="overflow-hidden rounded-2xl border border-slate-200 shadow-sm bg-white">
|
||||
<table class="w-full param-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-[25%]">欄位名稱</th>
|
||||
<th class="w-[15%]">型態</th>
|
||||
<th>說明</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($api['parameters'] as $name => $param)
|
||||
<tr class="hover:bg-slate-50/50 transition-colors">
|
||||
<td class="font-black text-slate-900 text-base">
|
||||
{{ $name }}
|
||||
@if($param['required'] ?? false)
|
||||
<span class="text-rose-500 ml-1">*</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<span class="px-2 py-1 rounded bg-slate-100 text-xs font-black text-slate-600 uppercase tracking-wider">{{ $param['type'] }}</span>
|
||||
</td>
|
||||
<td class="text-slate-600 leading-relaxed font-semibold">
|
||||
{{ $param['description'] }}
|
||||
@if(isset($param['example']))
|
||||
<div class="mt-2 flex items-center gap-2">
|
||||
<span class="text-[11px] font-black text-slate-400 uppercase tracking-tight">範例:</span>
|
||||
<code class="text-sm text-cyan-600 font-bold">{{ is_array($param['example']) ? json_encode($param['example'], JSON_UNESCAPED_UNICODE) : $param['example'] }}</code>
|
||||
</div>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Parameters -->
|
||||
<div class="mb-8">
|
||||
<h4 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">請求主體 (Parameters)</h4>
|
||||
<div class="overflow-hidden rounded-2xl border border-slate-200 shadow-sm bg-white">
|
||||
<table class="w-full param-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-[20%]">欄位名稱</th>
|
||||
<th class="w-[15%]">型態</th>
|
||||
<th>說明</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($api['parameters'] as $name => $param)
|
||||
<tr class="hover:bg-slate-50/50 transition-colors">
|
||||
<td class="font-black text-slate-900 text-base">
|
||||
{{ $name }}
|
||||
@if($param['required'] ?? false)
|
||||
<span class="text-rose-500 ml-1">*</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
class="px-2 py-1 rounded bg-slate-100 text-xs font-black text-slate-600 uppercase tracking-wider">
|
||||
{{ $param['type'] }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-slate-600 leading-relaxed font-semibold">
|
||||
{{ $param['description'] }}
|
||||
@if(isset($param['example']))
|
||||
<div class="mt-2 flex items-center gap-2">
|
||||
<span
|
||||
class="text-[11px] font-black text-slate-400 uppercase tracking-tight">範例:</span>
|
||||
<code
|
||||
class="text-sm text-cyan-600 font-bold">{{ is_array($param['example']) ? json_encode($param['example'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $param['example'] }}</code>
|
||||
</div>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Request & Response Examples -->
|
||||
<div class="space-y-8 mt-10">
|
||||
@if(isset($api['request']) && !empty($api['request']))
|
||||
<div>
|
||||
<h4 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-3">請求範例 (Request Body)</h4>
|
||||
<pre><code>{{ json_encode($api['request'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) }}</code></pre>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Request & Response Examples -->
|
||||
<div class="space-y-12 mt-12">
|
||||
<!-- Request Example -->
|
||||
<div>
|
||||
<h4 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-6">請求範例 (Request Body)</h4>
|
||||
<pre><code>{{ json_encode($api['request'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) }}</code></pre>
|
||||
</div>
|
||||
|
||||
<!-- Response Examples -->
|
||||
<div>
|
||||
@if(isset($api['response_parameters']))
|
||||
@if(isset($api['response_parameters']) && count($api['response_parameters']) > 0)
|
||||
<div>
|
||||
<h4 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">回應參數說明 (Response Parameters)</h4>
|
||||
<div class="overflow-hidden rounded-2xl border border-slate-200 shadow-sm bg-white mb-8">
|
||||
<div class="overflow-hidden rounded-2xl border border-slate-200 shadow-sm bg-white mb-6">
|
||||
<table class="w-full param-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-[20%]">欄位名稱</th>
|
||||
<th class="w-[25%]">欄位名稱</th>
|
||||
<th class="w-[15%]">型態</th>
|
||||
<th>說明</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($api['response_parameters'] as $name => $param)
|
||||
<tr class="hover:bg-slate-50/50 transition-colors">
|
||||
<td class="font-black text-slate-900 text-sm italic">{{ $name }}</td>
|
||||
<td>
|
||||
<span class="px-2 py-1 rounded bg-slate-100 text-[10px] font-black text-slate-600 uppercase tracking-wider">
|
||||
{{ $param['type'] }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-slate-600 font-semibold text-sm whitespace-pre-line">
|
||||
{{ $param['description'] }}
|
||||
@if(isset($param['example']))
|
||||
<div class="mt-2 flex items-center gap-2">
|
||||
<span class="text-[10px] font-black text-slate-400 uppercase tracking-tight">範例:</span>
|
||||
<code class="text-xs text-cyan-600 font-bold bg-cyan-50/50 px-2 py-0.5 rounded">{{ is_array($param['example']) ? json_encode($param['example'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $param['example'] }}</code>
|
||||
</div>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-slate-50/50 transition-colors">
|
||||
<td class="font-black text-slate-900 text-sm italic">{{ $name }}</td>
|
||||
<td><span class="px-2 py-1 rounded bg-slate-100 text-[10px] font-black text-slate-600 uppercase tracking-wider">{{ $param['type'] }}</span></td>
|
||||
<td class="text-slate-600 font-semibold text-sm whitespace-pre-line">{{ $param['description'] }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<h4 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">回應範例 (Response Body)</h4>
|
||||
<pre><code>{{ json_encode($api['response'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) }}</code></pre>
|
||||
</div>
|
||||
@if(isset($api['response']) && !empty($api['response']))
|
||||
<div>
|
||||
<h4 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-3">回應範例 (Response Body)</h4>
|
||||
<pre><code>{{ json_encode($api['response'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) }}</code></pre>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(isset($api['notes']))
|
||||
<div class="p-8 bg-cyan-50/50 border border-cyan-100 rounded-3xl">
|
||||
<h5 class="text-cyan-700 font-black mb-4 flex items-center gap-2 text-xl">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
@if(isset($api['notes']))
|
||||
<div class="p-6 bg-cyan-50/50 border border-cyan-100 rounded-3xl mt-6">
|
||||
<h5 class="text-cyan-700 font-black mb-2 flex items-center gap-2 text-lg">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
實作備註
|
||||
</h5>
|
||||
<p class="text-cyan-800 font-semibold leading-relaxed text-lg whitespace-pre-line">
|
||||
{{ $api['notes'] }}
|
||||
</p>
|
||||
<p class="text-cyan-800 font-semibold leading-relaxed text-base">{{ $api['notes'] }}</p>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
<div class="mt-16 mb-8 border-b border-slate-100"></div>
|
||||
</div>
|
||||
@endforeach
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<!-- MQTT Content -->
|
||||
<div x-show="currentTab === 'mqtt'" style="display: none;" x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 transform translate-y-4" x-transition:enter-end="opacity-100 transform translate-y-0">
|
||||
@foreach($docs['mqtt_topics'] as $category)
|
||||
@foreach($category['topics'] as $topic)
|
||||
<div id="{{ $topic['slug'] }}" class="mb-16 animate-luxury-in scroll-mt-10" x-intersect.margin.-200px="activeSection = '{{ $topic['slug'] }}'">
|
||||
|
||||
<div class="flex items-center gap-4 mb-6">
|
||||
<span class="px-3 py-1 text-sm font-black rounded-lg uppercase tracking-widest {{ $topic['action'] === 'PUB' ? 'bg-violet-100 text-violet-700' : 'bg-indigo-100 text-indigo-700' }}">
|
||||
{{ $topic['action'] }}
|
||||
</span>
|
||||
<h3 class="font-display text-3xl font-black text-slate-900 tracking-tight">{{ $topic['name'] }}</h3>
|
||||
</div>
|
||||
<p class="text-slate-600 mb-8 text-lg font-medium">{{ $topic['description'] }}</p>
|
||||
|
||||
<!-- Topic Block -->
|
||||
<div class="mb-8 overflow-hidden rounded-2xl border border-slate-200">
|
||||
<div class="bg-slate-900 px-6 py-5 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="px-3 py-1 {{ $topic['action'] === 'PUB' ? 'bg-violet-500' : 'bg-indigo-500' }} text-white text-xs font-black rounded-lg uppercase letter-spacing-widest shadow-sm">{{ $topic['action'] }}</span>
|
||||
<code class="text-slate-300 font-mono text-base sm:text-lg break-all">{{ $topic['topic'] }}</code>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="text-slate-400 font-mono text-xs font-bold px-2 py-1 bg-slate-800 rounded">QoS {{ $topic['qos'] ?? 0 }}</span>
|
||||
<span class="text-slate-500 text-[10px] font-black uppercase tracking-widest hidden sm:block">MQTT Topic</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Payload Parameters -->
|
||||
@if(isset($topic['payload_parameters']) && count($topic['payload_parameters']) > 0)
|
||||
<div class="mb-8">
|
||||
<h4 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">資料載體 (Payload Parameters)</h4>
|
||||
<div class="overflow-hidden rounded-2xl border border-slate-200 shadow-sm bg-white">
|
||||
<table class="w-full param-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-[25%]">欄位名稱</th>
|
||||
<th class="w-[15%]">型態</th>
|
||||
<th>說明</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($topic['payload_parameters'] as $name => $param)
|
||||
<tr class="hover:bg-slate-50/50 transition-colors">
|
||||
<td class="font-black text-slate-900 text-base">
|
||||
{{ $name }}
|
||||
@if($param['required'] ?? true)
|
||||
<span class="text-rose-500 ml-1">*</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<span class="px-2 py-1 rounded bg-slate-100 text-xs font-black text-slate-600 uppercase tracking-wider">{{ $param['type'] }}</span>
|
||||
</td>
|
||||
<td class="text-slate-600 leading-relaxed font-semibold">
|
||||
{{ $param['description'] }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Payload Example -->
|
||||
@if(isset($topic['payload_example']) && !empty($topic['payload_example']))
|
||||
<div class="mt-8">
|
||||
<h4 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-3 flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" /></svg>
|
||||
Payload JSON 範例
|
||||
</h4>
|
||||
<pre><code>{{ json_encode($topic['payload_example'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) }}</code></pre>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(isset($topic['notes']))
|
||||
<div class="p-6 bg-indigo-50/50 border border-indigo-100 rounded-3xl mt-8">
|
||||
<h5 class="text-indigo-700 font-black mb-2 flex items-center gap-2 text-lg">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
實作備註
|
||||
</h5>
|
||||
<p class="text-indigo-800 font-semibold leading-relaxed text-base whitespace-pre-line">{{ $topic['notes'] }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="mt-16 mb-8 border-b border-slate-100"></div>
|
||||
</div>
|
||||
@endforeach
|
||||
@endforeach
|
||||
</div>
|
||||
@endforeach
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<!-- Font Awesome or Lucide (Simplified) -->
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -69,7 +69,7 @@ Route::prefix('v1')->middleware(['throttle:api'])->group(function () {
|
||||
Route::put('products/supplementary/B009', [App\Http\Controllers\Api\V1\App\MachineController::class, 'reportSlotList']);
|
||||
|
||||
// 統一商品主檔 API (B012 整合版)
|
||||
Route::match(['get', 'patch'], 'machine/products/B012', [App\Http\Controllers\Api\V1\App\MachineController::class, 'getProducts']);
|
||||
Route::get('machine/products/B012', [App\Http\Controllers\Api\V1\App\MachineController::class, 'getProducts']);
|
||||
|
||||
|
||||
// 遠端指令出貨控制 (B055) — PUT 回報已遷移至 MQTT command/ack Topic
|
||||
|
||||
Loading…
Reference in New Issue
Block a user