Compare commits
2 Commits
7883a755d2
...
ac51027dda
| Author | SHA1 | Date | |
|---|---|---|---|
| ac51027dda | |||
| 17b5c1a316 |
@@ -15,6 +15,7 @@ Skills 位於 `.agents/skills/`,採漸進式揭露以節省 Token。
|
|||||||
| 觸發詞 / 情境 | 對應 Skill | 路徑 |
|
| 觸發詞 / 情境 | 對應 Skill | 路徑 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 機台通訊, IoT, 日誌上報, Log Ingestion, 異步隊列, Queue, Heartbeat, 心跳發報 | **IoT 通訊與高併發處理規範** | `.agents/skills/iot-communication/SKILL.md` |
|
| 機台通訊, IoT, 日誌上報, Log Ingestion, 異步隊列, Queue, Heartbeat, 心跳發報 | **IoT 通訊與高併發處理規範** | `.agents/skills/iot-communication/SKILL.md` |
|
||||||
|
| B010, B017, B600, B055, API 規格, 通訊協議, 狀態碼, 頁面碼, 範例, JSON | **API 技術規格與通訊協議規範** | `.agents/skills/api-technical-specs/SKILL.md` |
|
||||||
| 介面, UI, 設計, 佈局, CSS, Tailwind, 奢華, 深色模式, Light Mode, Dark Mode, Blade, 樣式, 間距, 陰影, 動畫, 畫面, 頁面 | **極簡奢華風 UI 實作規範** | `.agents/skills/ui-minimal-luxury/SKILL.md` |
|
| 介面, UI, 設計, 佈局, CSS, Tailwind, 奢華, 深色模式, Light Mode, Dark Mode, Blade, 樣式, 間距, 陰影, 動畫, 畫面, 頁面 | **極簡奢華風 UI 實作規範** | `.agents/skills/ui-minimal-luxury/SKILL.md` |
|
||||||
| 查詢、撈資料、Query、Controller、下拉選單、Eloquent、N+1、`->get()`、select、交易、Transaction、Bulk、分頁、索引 | **資料庫與 ORM 最佳實踐規範** | `/home/mama/.gemini/antigravity/global_skills/database-best-practices/SKILL.md` |
|
| 查詢、撈資料、Query、Controller、下拉選單、Eloquent、N+1、`->get()`、select、交易、Transaction、Bulk、分頁、索引 | **資料庫與 ORM 最佳實踐規範** | `/home/mama/.gemini/antigravity/global_skills/database-best-practices/SKILL.md` |
|
||||||
| RBAC, 權限, 角色, 租戶, Tenant, Company, Access Control, 多租戶, 權限控管 | **多租戶與權限架構實作規範** | `.agents/rules/rbac-rules.md` |
|
| RBAC, 權限, 角色, 租戶, Tenant, Company, Access Control, 多租戶, 權限控管 | **多租戶與權限架構實作規範** | `.agents/rules/rbac-rules.md` |
|
||||||
|
|||||||
75
.agents/skills/api-technical-specs/SKILL.md
Normal file
75
.agents/skills/api-technical-specs/SKILL.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
---
|
||||||
|
name: API 技術規格與通訊協議規範
|
||||||
|
description: 本技能規範定義了 Star Cloud 系統中所有機台 (IoT) 與後端 (Cloud) 通訊的 API 細節、參數命名規則、狀態代碼對照與認證機制,作為系統開發的唯一規格來源。
|
||||||
|
---
|
||||||
|
|
||||||
|
# API 技術規格與通訊協議規範 (API Technical Specs)
|
||||||
|
|
||||||
|
本文件集中定義所有機台與雲端通訊的 API 規格,確保硬體端與軟體端在資料交換格式與業務定義上保持完全一致。
|
||||||
|
|
||||||
|
## 1. 核心命名原則
|
||||||
|
- **語意化優先**:捨棄舊版 `M_` 前綴,統一使用 snake_case (如 `firmware_version`)。
|
||||||
|
- **類型嚴格**:文件定義的類型 (Integer, Float, String) 必須在後端 Model 與前端文件中心嚴格遵守。
|
||||||
|
|
||||||
|
## 2. 身份認證 (Authentication)
|
||||||
|
- **Bearer Token**:所有 API 必須在 Header 帶入 `Authorization: Bearer <api_token>`。
|
||||||
|
- **身分綁定**:後端透過 Token 自動識別 `machine_id`,禁止在 Body 帶入 `machine` 或 `key` 欄位。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 機台核心 API (IoT Endpoints)
|
||||||
|
|
||||||
|
### 3.1 B010: 心跳上報與狀態同步
|
||||||
|
用於確認機台在線狀態、更新感測數據、提交事件日誌並獲取雲端指令。
|
||||||
|
|
||||||
|
- **URL**: `POST /api/v1/app/machine/status/B010`
|
||||||
|
- **Request Body:**
|
||||||
|
| 參數 | 類型 | 必填 | 說明 | 範例 |
|
||||||
|
| :--- | :--- | :--- | :--- | :--- |
|
||||||
|
| `current_page` | Integer | 是 | 當前頁面代碼 (見下表) | `1` |
|
||||||
|
| `firmware_version` | String | 是 | 韌體版本號 | `1.0.5` |
|
||||||
|
| `model` | String | 是 | 機台型號 | `STAR-V1` |
|
||||||
|
| `temperature` | Float | 否 | 環境溫度 | `25.5` |
|
||||||
|
| `door_status` | Integer | 否 | 門狀態 (0:關 / 1:開) | `0` |
|
||||||
|
| `log` | String | 否 | 事件日誌簡述 | `Door opened` |
|
||||||
|
| `log_level` | String | 否 | info, warn, error | `info` |
|
||||||
|
| `log_payload` | Object | 否 | 額外日誌 JSON 對象 | `{"code":500}` |
|
||||||
|
|
||||||
|
- **Response Body:**
|
||||||
|
| 參數 | 類型 | 說明 | 範例 |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| `success` | Boolean | 請求是否處理成功 | `true` |
|
||||||
|
| `code` | Integer | 內部業務狀態碼 | `200` |
|
||||||
|
| `message` | String | 回應訊息 | `OK` |
|
||||||
|
| `status` | String | **雲端指令代碼** (見下表) | `49` |
|
||||||
|
|
||||||
|
#### B010 代碼對照表
|
||||||
|
|
||||||
|
**頁面代碼 (current_page):**
|
||||||
|
- `0`: 離線 / `1`: 主頁面 / `2`: 販賣頁 / `3`: 管理頁
|
||||||
|
- `4`: 補貨頁 / `5`: 教學頁 / `6`: 購買中 / `7`: 鎖定頁
|
||||||
|
- `60`: 出貨成功 / `61`: 貨道測試 / `62`: 付款選擇
|
||||||
|
- `63`: 等待付款 / `64`: 出貨 / `65`: 收據簽單
|
||||||
|
- `612`: 出貨失敗
|
||||||
|
|
||||||
|
**雲端指令代碼 (status):**
|
||||||
|
- `49`: reload B017 (貨道同步)
|
||||||
|
- `50`: reload B005 (基礎參數)
|
||||||
|
- `51`: reboot (重啟系統)
|
||||||
|
- `60`: reboot card machine (刷卡機重啟)
|
||||||
|
- `61`: checkout (結帳)
|
||||||
|
- `70`: unlock (解鎖) / `71`: lock (鎖定)
|
||||||
|
- `72`: sellCode reload B023 (即期品)
|
||||||
|
- `75`: exp reload B026 (效期)
|
||||||
|
- `79`: read B050 (參數讀取)
|
||||||
|
- `85`: reload B0552 (出貨腳本)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.2 B017: 貨道與庫存同步 (規劃中)
|
||||||
|
- **URL**: `POST /api/v1/app/machine/reload_msg/B017`
|
||||||
|
- 說明:當機台收到 B010 回應 `status: 49` 時,應呼叫此 API 同步最新貨道佈局。
|
||||||
|
|
||||||
|
### 3.3 B600: 交易數據回傳 (規劃中)
|
||||||
|
- **URL**: `POST /api/v1/app/B600`
|
||||||
|
- 說明:交易完成後提交支付方式、金額、商品與出貨結果。
|
||||||
@@ -60,4 +60,19 @@ public function handle(MachineService $service): void
|
|||||||
- [ ] 是否使用了 `ApiResponse` Trait?
|
- [ ] 是否使用了 `ApiResponse` Trait?
|
||||||
- [ ] 業務邏輯是否已封裝至 `App\Services`?
|
- [ ] 業務邏輯是否已封裝至 `App\Services`?
|
||||||
- [ ] 是否使用了 Redis Queue 進行非同步處理?
|
- [ ] 是否使用了 Redis Queue 進行非同步處理?
|
||||||
- [ ] 是否在 API 層級進行了基礎的參數驗證?
|
|
||||||
|
## 6. API 規格定義 (API Specifications)
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> **規格分離原則**:本技能僅規範「通訊處理邏輯」。關於具體的 API 欄位、參數命名、狀態代碼對照與範例,請務必參閱專屬技能規範:
|
||||||
|
> - **[API 技術規格與通訊協議規範](file:///home/mama/projects/star-cloud/.agents/skills/api-technical-specs/SKILL.md)**
|
||||||
|
|
||||||
|
### 常見端點處理模式
|
||||||
|
1. **B010 (心跳)**:高頻點,必須進入 Redis Queue。更新 `last_heartbeat_at` 與感測器快照。
|
||||||
|
2. **B600 (交易)**:高價值點,必須進入任務隊列並支援重試。建立 `Transaction` 紀錄。
|
||||||
|
3. **B017 (貨道)**:回覆較大資料量,應確保 Service 層具備緩存 (Cache) 機制。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> **身份識別機制**:禁止在 Body 傳輸 `machine` 或 `key`。系統強制透過 `Bearer Token` 識別並自動關聯資料。
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class MachineController extends AdminController
|
|||||||
$machines = $query->when($request->status, function ($query, $status) {
|
$machines = $query->when($request->status, function ($query, $status) {
|
||||||
return $query->where('status', $status);
|
return $query->where('status', $status);
|
||||||
})
|
})
|
||||||
->latest()
|
->orderBy("last_heartbeat_at", "desc")->orderBy("id", "desc")
|
||||||
->paginate($per_page)
|
->paginate($per_page)
|
||||||
->withQueryString();
|
->withQueryString();
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ class MachineController extends AdminController
|
|||||||
->withCount(['slots as warning_count' => function ($q) {
|
->withCount(['slots as warning_count' => function ($q) {
|
||||||
$q->whereBetween('expiry_date', [now()->toDateString(), now()->addDays(7)->toDateString()]);
|
$q->whereBetween('expiry_date', [now()->toDateString(), now()->addDays(7)->toDateString()]);
|
||||||
}])
|
}])
|
||||||
->latest()
|
->orderBy("last_heartbeat_at", "desc")->orderBy("id", "desc")
|
||||||
->paginate($per_page)
|
->paginate($per_page)
|
||||||
->withQueryString();
|
->withQueryString();
|
||||||
|
|
||||||
|
|||||||
18
app/Http/Controllers/ApiDocsController.php
Normal file
18
app/Http/Controllers/ApiDocsController.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ApiDocsController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display the API documentation page.
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$docs = config('api-docs');
|
||||||
|
|
||||||
|
return view('docs.api-docs', compact('docs'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,12 +21,12 @@ class MachineService
|
|||||||
return DB::transaction(function () use ($serialNo, $data) {
|
return DB::transaction(function () use ($serialNo, $data) {
|
||||||
$machine = Machine::where('serial_no', $serialNo)->firstOrFail();
|
$machine = Machine::where('serial_no', $serialNo)->firstOrFail();
|
||||||
|
|
||||||
// 參數相容性處理 (Mapping legacy fields to new fields)
|
// 採用現代化語意命名 (Modern semantic naming)
|
||||||
$temperature = $data['temperature'] ?? $machine->temperature;
|
$temperature = $data['temperature'] ?? $machine->temperature;
|
||||||
$currentPage = $data['current_page'] ?? $data['M_Stus2'] ?? $machine->current_page;
|
$currentPage = $data['current_page'] ?? $machine->current_page;
|
||||||
$doorStatus = $data['door_status'] ?? $data['door'] ?? $machine->door_status;
|
$doorStatus = $data['door_status'] ?? $machine->door_status;
|
||||||
$firmwareVersion = $data['firmware_version'] ?? $data['M_Ver'] ?? $machine->firmware_version;
|
$firmwareVersion = $data['firmware_version'] ?? $machine->firmware_version;
|
||||||
$model = $data['model'] ?? $data['M_Stus'] ?? $machine->model;
|
$model = $data['model'] ?? $machine->model;
|
||||||
|
|
||||||
$updateData = [
|
$updateData = [
|
||||||
'status' => 'online',
|
'status' => 'online',
|
||||||
|
|||||||
126
config/api-docs.php
Normal file
126
config/api-docs.php
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => 'Star Cloud IoT API 說明文件',
|
||||||
|
'version' => 'v1.0.0',
|
||||||
|
'description' => '此文件提供 Star Cloud 智能販賣機 IoT 端點通訊協議說明,供硬體端與前端開發者調研與串接使用。',
|
||||||
|
'categories' => [
|
||||||
|
[
|
||||||
|
'name' => '機台核心通訊 (IoT Core)',
|
||||||
|
'apis' => [
|
||||||
|
[
|
||||||
|
'name' => 'B010: 心跳上報與狀態同步 (Heartbeat)',
|
||||||
|
'slug' => 'b010-heartbeat',
|
||||||
|
'method' => 'POST',
|
||||||
|
'path' => '/api/v1/app/machine/status/B010',
|
||||||
|
'description' => '機台定期向雲端回報當前頁面、版本、溫度及門禁狀態。身份由 Bearer Token 識別。',
|
||||||
|
'headers' => [
|
||||||
|
'Authorization' => 'Bearer <api_token>',
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
],
|
||||||
|
'parameters' => [
|
||||||
|
'current_page' => [
|
||||||
|
'type' => 'integer',
|
||||||
|
'required' => true,
|
||||||
|
'description' => '當前頁面編號。對照表:
|
||||||
|
0: 離線, 1: 主頁面, 2: 販賣頁, 3: 管理頁, 4: 補貨頁, 5: 教學頁
|
||||||
|
6: 購買中, 7: 鎖定頁, 60: 出貨成功, 61: 貨道測試, 62: 付款選擇
|
||||||
|
63: 等待付款, 64: 出貨, 65: 收據簽單, 66: 通行碼, 67: 取貨碼
|
||||||
|
68: 訊息顯示, 69: 取消購買, 610: 購買結束, 611: 來店禮, 612: 出貨失敗',
|
||||||
|
'example' => 1
|
||||||
|
],
|
||||||
|
'firmware_version' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'required' => true,
|
||||||
|
'description' => '軟體或韌體版本號',
|
||||||
|
'example' => '1.0.5'
|
||||||
|
],
|
||||||
|
'model' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'required' => false,
|
||||||
|
'description' => '機台型號',
|
||||||
|
'example' => 'STAR-V1'
|
||||||
|
],
|
||||||
|
'temperature' => [
|
||||||
|
'type' => 'float',
|
||||||
|
'required' => false,
|
||||||
|
'description' => '感測環境溫度',
|
||||||
|
'example' => 25.5
|
||||||
|
],
|
||||||
|
'door_status' => [
|
||||||
|
'type' => 'integer',
|
||||||
|
'required' => false,
|
||||||
|
'description' => '門禁狀態 (0: 關閉, 1: 開啟)',
|
||||||
|
'example' => 0
|
||||||
|
],
|
||||||
|
'log' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'required' => false,
|
||||||
|
'description' => '事件日誌主訊息',
|
||||||
|
'example' => 'Door opened'
|
||||||
|
],
|
||||||
|
'log_level' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'required' => false,
|
||||||
|
'description' => '日誌等級 (info, warning, error)',
|
||||||
|
'example' => 'info'
|
||||||
|
],
|
||||||
|
'log_payload' => [
|
||||||
|
'type' => 'object',
|
||||||
|
'required' => false,
|
||||||
|
'description' => '詳細上下文 (JSON 對象)',
|
||||||
|
'example' => ['error_code' => 500, 'component' => 'door_sensor']
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'response_parameters' => [
|
||||||
|
'success' => [
|
||||||
|
'type' => 'boolean',
|
||||||
|
'description' => '請求是否處理成功',
|
||||||
|
'example' => true
|
||||||
|
],
|
||||||
|
'code' => [
|
||||||
|
'type' => 'integer',
|
||||||
|
'description' => '內部業務狀態碼',
|
||||||
|
'example' => 200
|
||||||
|
],
|
||||||
|
'message' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'description' => '回應訊息說明',
|
||||||
|
'example' => 'OK'
|
||||||
|
],
|
||||||
|
'status' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'description' => '雲端指令代碼。對照表:
|
||||||
|
49: reload B017 (貨道同步), 50: reload B005 (基礎參數), 51: reboot (重啟)
|
||||||
|
60: reboot card machine (刷卡機重啟), 61: checkout (結帳)
|
||||||
|
70: unlock (解鎖), 71: lock (鎖定), 72: sellCode reload B023 (即期品)
|
||||||
|
75: exp reload B026 (效期), 79: read B050 (參數讀取)
|
||||||
|
81: sync timer status (B710), 85: reload B0552 (出貨腳本)',
|
||||||
|
'example' => '49'
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'request' => [
|
||||||
|
'current_page' => 1,
|
||||||
|
'firmware_version' => '1.0.5',
|
||||||
|
'model' => 'STAR-V1',
|
||||||
|
'temperature' => 25.5,
|
||||||
|
'door_status' => 0,
|
||||||
|
'log' => 'Door opened',
|
||||||
|
'log_level' => 'info',
|
||||||
|
'log_payload' => [
|
||||||
|
'error_code' => 500,
|
||||||
|
'component' => 'door_sensor'
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'response' => [
|
||||||
|
'success' => true,
|
||||||
|
'code' => 200,
|
||||||
|
'message' => 'OK',
|
||||||
|
'status' => '49'
|
||||||
|
],
|
||||||
|
'notes' => '機台收到 B010 回應中的特定 `status` 代碼後,應根據對照表執行對應的指令動作或 API 呼叫 (如 B017)。若為空則代表無指令。'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
352
resources/views/docs/api-docs.blade.php
Normal file
352
resources/views/docs/api-docs.blade.php
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>{{ $docs['title'] ?? 'API Documentation' }}</title>
|
||||||
|
|
||||||
|
<!-- 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">
|
||||||
|
|
||||||
|
<!-- CSS (Vite or CDN for layout) -->
|
||||||
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--color-luxury-deep: #f8fafc;
|
||||||
|
--color-luxury-card: #ffffff;
|
||||||
|
--color-accent: #0891b2;
|
||||||
|
/* Cyan-600 for light mode */
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||||
|
background-color: var(--color-luxury-deep);
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-display {
|
||||||
|
font-family: 'Outfit', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: 280px;
|
||||||
|
height: 100vh;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
background: #ffffff;
|
||||||
|
border-right: 1px solid #e2e8f0;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
margin-left: 280px;
|
||||||
|
padding: 2.5rem;
|
||||||
|
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;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #64748b;
|
||||||
|
background: #f1f5f9;
|
||||||
|
border-bottom: 2px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-table td {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-bottom: 1px solid #f1f5f9;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: #1e293b !important;
|
||||||
|
color: #e2e8f0 !important;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.25rem;
|
||||||
|
font-family: 'ui-monospace', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luxury-nav-section {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: #94a3b8;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luxury-nav-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.6rem 1.5rem;
|
||||||
|
color: #475569;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
transition: all 0.2s;
|
||||||
|
border-left: 4px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luxury-nav-link:hover {
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luxury-nav-link.active {
|
||||||
|
color: var(--color-accent);
|
||||||
|
background: #ecfeff;
|
||||||
|
border-left-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-luxury-in {
|
||||||
|
animation: luxuryIn 0.5s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes luxuryIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body x-data="{ activeSection: '{{ $docs['categories'][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>
|
||||||
|
</h1>
|
||||||
|
<p class="text-[11px] font-black text-slate-400 uppercase tracking-widest mt-1">API 參考手冊 {{ $docs['version']
|
||||||
|
}}</p>
|
||||||
|
</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'] }}'">
|
||||||
|
<span>{{ $api['name'] }}</span>
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
@endforeach
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="main-content">
|
||||||
|
<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">
|
||||||
|
{{ $docs['description'] }}
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<h3 class="font-display text-3xl font-black text-slate-900 mb-6 tracking-tight">{{ $api['name'] }}</h3>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 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']) : $param['example'] }}</code>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 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) }}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Response Examples -->
|
||||||
|
<div>
|
||||||
|
@if(isset($api['response_parameters']))
|
||||||
|
<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">
|
||||||
|
<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['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">{{ $param['example'] }}</code>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</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) }}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@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>
|
||||||
|
實作備註
|
||||||
|
</h5>
|
||||||
|
<p class="text-cyan-800 font-semibold leading-relaxed text-lg whitespace-pre-line">
|
||||||
|
{{ $api['notes'] }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Font Awesome or Lucide (Simplified) -->
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<script>
|
||||||
|
lucide.createIcons();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -234,3 +234,5 @@ Route::prefix('test')->name('test.')->group(function () {
|
|||||||
Route::get('/social-login', [App\Http\Controllers\SocialLoginTestController::class , 'index'])->name('social-login');
|
Route::get('/social-login', [App\Http\Controllers\SocialLoginTestController::class , 'index'])->name('social-login');
|
||||||
Route::get('/line/callback', [App\Http\Controllers\SocialLoginTestController::class , 'lineCallback'])->name('line.callback');
|
Route::get('/line/callback', [App\Http\Controllers\SocialLoginTestController::class , 'lineCallback'])->name('line.callback');
|
||||||
});
|
});
|
||||||
|
// 公開 API 文件 (無需登入)
|
||||||
|
Route::get('/api/docs', [App\Http\Controllers\ApiDocsController::class, 'index'])->name('api.docs');
|
||||||
|
|||||||
Reference in New Issue
Block a user