[FEAT] 儀表板 UI 大改造、中文化與項目開發規範同步 (含深色模式修復)
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 58s

This commit is contained in:
2026-03-09 11:30:06 +08:00
parent 02918ce0e1
commit 682a9e7ac3
11 changed files with 168 additions and 26 deletions

View File

@@ -74,5 +74,13 @@ trigger: always_on
* **Demo 環境 (對應 `demo` 分支)** * **Demo 環境 (對應 `demo` 分支)**
* 透過 `deploy-demo.yaml`,合併或推送到 `demo` 分支會自動部署至 `demo-cloud.taiwan-star.com.tw` * 透過 `deploy-demo.yaml`,合併或推送到 `demo` 分支會自動部署至 `demo-cloud.taiwan-star.com.tw`
* 登入伺服器查修:`ssh gitea_work`,路徑為 `/var/www/star-cloud-demo` * 登入伺服器查修:`ssh gitea_work`,路徑為 `/var/www/star-cloud-demo`
* **Production 環境 (對應 `main` 分支)**
* 透過 `deploy-prod.yaml`,推進到 `main` 會自動部署至正式站。 ## 9. 瀏覽器測試規範 (Browser Testing)
當需要進行瀏覽器自動化測試或手動驗證時,必須遵守以下連線資訊:
* **本地測試網址**`http://localhost:8090/` (注意:非 8000 或 8080)
* **預設管理員帳號**`admin`
* **預設管理員密碼**`password`
> [!IMPORTANT]
> 在執行 `open_browser_url` 或進行 E2E 測試時,請務必優先確認 Port 是否為 `8090`,以避免連線至錯誤的服務環境。

View File

@@ -6,7 +6,7 @@ trigger: always_on
本文件確保 AI 助手在對話中能**主動辨識**需要參照技能 (Skill) 的時機。 本文件確保 AI 助手在對話中能**主動辨識**需要參照技能 (Skill) 的時機。
Skills 位於 `.agents/skills/`,採漸進式揭露以節省 Token。 Skills 位於 `.agents/skills/`,採漸進式揭露以節省 Token。
**若對話內容命中以下任一觸發條件,必須先使用 `view_file` 讀取對應的 `SKILL.md` 後再進行作業。** **若對話內容命中以下任一觸發條件,必須先使用 `view_file` 讀取對應的 `SKILL.md`標記為 active 再進行作業。**
--- ---
@@ -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` |
| 介面, UI, 佈局, CSS, Tailwind, 奢華, 深色模式, Light Mode, Dark Mode, Blade, 樣式, 間距, 陰影, 動畫 | **極簡奢華風 UI 實作規範** | `.agents/skills/ui-minimal-luxury/SKILL.md` |
--- ---
@@ -22,6 +23,10 @@ Skills 位於 `.agents/skills/`,採漸進式揭露以節省 Token。
以下場景**無論對話中是否出現觸發詞**,都必須主動載入對應 Skill 以下場景**無論對話中是否出現觸發詞**,都必須主動載入對應 Skill
### 🔴 新增或修改頁面 (Views/Blade) 時
必須讀取:
1. **ui-minimal-luxury** — 確保符合極簡奢華風視覺與互動規範
### 🔴 新增機台通訊 API 端點時 ### 🔴 新增機台通訊 API 端點時
必須讀取: 必須讀取:
1. **iot-communication** — 決定是否使用異步隊列流程 1. **iot-communication** — 決定是否使用異步隊列流程

View File

@@ -0,0 +1,90 @@
---
name: 極簡奢華風 UI 實作規範 (Minimal Luxury UI)
description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範,包含 CSS Tokens、常用組件樣式、動畫效果與互動模式確保全站 15+ 模組的視覺一致性。
---
# 極簡奢華風 UI 實作規範 (Minimal Luxury UI)
本文件定義了 Star Cloud 專案的核心視覺語言。所有新頁面與組件開發必須嚴格遵守此規範。
## 1. 核心設計令牌 (Design Tokens)
### 色彩系統 (CSS Variables)
位於 `resources/css/app.css`
- `--color-luxury-deep`: `#0f172a` (深色背景)
- `--color-luxury-card`: `#1e293b` (卡片背景)
- `--color-accent`: `#06b6d4` (青色點綴,適用於按鈕與標示)
### 字體 (Typography)
- **內文字體**: `Plus Jakarta Sans`
- **標題/顯示字體**: `Outfit`
- **特性**: 標題需帶有 `letter-spacing: -0.02em` 以增強精密感。
## 2. 核心組件樣式
### 豪華卡片 (Luxury Card)
```html
<div class="luxury-card p-6 rounded-2xl animate-luxury-in">
<!-- 內容 -->
</div>
```
- **特效**: 懸停時帶有 Y 軸平移與深度投影。
### 側邊導覽項 (Luxury Nav Item)
```html
<a href="#" class="luxury-nav-item active">
<i class="lucide-icon"></i>
<span>節點名稱</span>
</a>
```
- **啟用狀態**: 左側帶有圓角直條指示器,並輔以青色發光陰影。
### 按鈕組件 (Buttons)
- **Primary**: `.btn-luxury-primary` (青色漸層,適用於建立、儲存)
- **Secondary**: `.btn-luxury-secondary` (白色/深色背景,帶邊框,適用於編輯、篩選)
- **Ghost**: `.btn-luxury-ghost` (無背景,適用於取消、查看更多)
```html
<!-- Primary -->
<button class="btn-luxury-primary">
<i class="lucide-plus size-4"></i>
<span>建立新機台</span>
</button>
<!-- Ghost -->
<button class="btn-luxury-ghost">取消</button>
```
## 3. 動畫與互動
### 進場動畫
- **`.animate-luxury-in`**: 所有的主內容區域或卡片在頁面載入時,應具備由下而上的淡入效果。
### Alpine.js 互動模式 (以時間選擇器為例)
- **互動原則**: 點擊觸發下拉選單時,必須使用 `x-transition` 且帶有 `scale` 偏移。
- **樣式要求**: 選單背景需使用玻璃擬態 (Glassmorphism) 或帶透明度的深色背景。
## 4. UI 檢查清單 (AI 助手執行前必讀)
- [ ] 是否使用了正確的 `rounded-2xl` (或更圓) 的導角?
- [ ] 所有的圖示是否一致使用 `lucide-react` 風格?
- [ ] 卡片是否有適當的間距 (通常為 `p-6`)
- [ ] 文字色階是否符合:標題 (slate-900/white)、副標 (slate-500/slate-400)
## 5. 開發注意事項 (Important Notes)
### 技術限制備忘
- **CSS 編譯**: 複雜的 `box-shadow` 或漸層應直接寫原生 CSS 屬性,避免在 `@apply` 中使用帶空格的數值導致編譯失敗(詳見 KI: `tailwind-luxury-ui-patterns`)。
- **深色模式**: 互動式按鈕在深色模式下必須強化文字亮度(`dark:text-white`),並輔以青色發光效果。
### 即時動態呈現規範
- **格式**: `#機台編號 動作內容` (例如 `#V-001 執行出貨`)。
- **脈絡**: 必須呈現相對時間與機台位置。
---
> [!IMPORTANT]
> **開發新功能前,必須確認 `app.css` 中的 `.btn-luxury-*` 系列組件是否滿足需求。**
> 嚴禁在 Blade 中寫入大量重複的 `bg-indigo-600` 等舊式類別。
---
> [!TIP]
> 當遇到未定義的 UI 區塊時,優先參考 `admin.dashboard.blade.php` 的卡片與即時動態實作方式進行衍生。

View File

@@ -112,8 +112,8 @@
.luxury-nav-item.active::before { .luxury-nav-item.active::before {
content: ""; content: "";
@apply absolute left-0 w-1 h-5 bg-cyan-500 rounded-full shadow-[0_0_8px_rgba(6,182,212,0.5)]; @apply absolute left-0 w-1 h-5 bg-cyan-500 rounded-full;
left: 0; box-shadow: 0 0 8px rgba(6, 182, 212, 0.5);
} }
/* Submenu styling */ /* Submenu styling */
@@ -121,4 +121,38 @@
@apply mt-2 space-y-1 ps-4 border-l border-slate-200 ms-4; @apply mt-2 space-y-1 ps-4 border-l border-slate-200 ms-4;
@apply dark:border-white/10; @apply dark:border-white/10;
} }
/* Luxury Buttons */
.btn-luxury-primary {
@apply inline-flex items-center justify-center gap-x-2 px-4 py-2.5 text-sm font-semibold rounded-xl text-white transition-all duration-200;
background: linear-gradient(135deg, #06b6d4, #3b82f6);
box-shadow: 0 4px 12px -2px rgba(6, 182, 212, 0.3);
}
.btn-luxury-primary:hover {
@apply -translate-y-0.5;
box-shadow: 0 8px 20px -4px rgba(6, 182, 212, 0.4);
filter: brightness(1.1);
}
.btn-luxury-primary:active {
@apply translate-y-0 scale-[0.98];
}
.btn-luxury-secondary {
@apply inline-flex items-center justify-center gap-x-2 px-3 py-2 text-sm font-semibold rounded-xl transition-all duration-200;
@apply bg-white text-slate-700 border border-slate-200 shadow-sm;
@apply dark:bg-slate-800 dark:text-white dark:border-slate-700;
}
.btn-luxury-secondary:hover {
@apply bg-slate-50 border-slate-300 -translate-y-0.5 shadow-md;
@apply dark:bg-slate-700/50 dark:border-slate-600;
}
.btn-luxury-ghost {
@apply inline-flex items-center justify-center gap-x-2 px-4 py-2 text-sm font-bold rounded-xl transition-all duration-200;
@apply text-slate-500 hover:bg-slate-100 hover:text-slate-900;
@apply dark:text-slate-300 dark:hover:bg-white/10 dark:hover:text-cyan-400;
}
} }

View File

@@ -62,7 +62,7 @@
</div> </div>
<div x-data="{ open: false, selected: '最近 7 天' }" class="relative inline-block text-left"> <div x-data="{ open: false, selected: '最近 7 天' }" class="relative inline-block text-left">
<button @click="open = !open" type="button" class="inline-flex items-center gap-x-2 px-3 py-2 text-sm font-semibold text-slate-700 dark:text-slate-300 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-all shadow-sm"> <button @click="open = !open" type="button" class="btn-luxury-secondary">
<span x-text="selected"></span> <span x-text="selected"></span>
<svg class="shrink-0 w-4 h-4 transition-transform" :class="open ? 'rotate-180' : ''" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg> <svg class="shrink-0 w-4 h-4 transition-transform" :class="open ? 'rotate-180' : ''" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
</button> </button>
@@ -113,7 +113,7 @@
</div> </div>
@endforelse @endforelse
</div> </div>
<button class="w-full mt-8 py-3 text-sm font-bold text-slate-500 hover:text-cyan-500 transition-colors uppercase">查看所有日誌 </button> <button class="btn-luxury-ghost w-full mt-8 py-3 font-bold uppercase tracking-wider">查看所有日誌 </button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -42,7 +42,7 @@
<div class="container mx-auto px-6 py-8"> <div class="container mx-auto px-6 py-8">
<div class="flex justify-between items-center mb-6"> <div class="flex justify-between items-center mb-6">
<h3 class="text-gray-900 dark:text-gray-200 text-3xl font-medium">禮品設定</h3> <h3 class="text-gray-900 dark:text-gray-200 text-3xl font-medium">禮品設定</h3>
<button onclick="document.getElementById('createModal').classList.remove('hidden')" class="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700"> <button onclick="document.getElementById('createModal').classList.remove('hidden')" class="btn-luxury-primary">
新增禮品 新增禮品
</button> </button>
</div> </div>
@@ -148,8 +148,8 @@
</div> </div>
</div> </div>
<div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex justify-end space-x-3"> <div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex justify-end space-x-3">
<button type="button" onclick="document.getElementById('createModal').classList.add('hidden')" class="px-4 py-2 text-gray-600 dark:text-gray-400 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700">取消</button> <button type="button" onclick="document.getElementById('createModal').classList.add('hidden')" class="btn-luxury-secondary">取消</button>
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">建立</button> <button type="submit" class="btn-luxury-primary">建立</button>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -44,7 +44,7 @@
<div class="flex justify-end"> <div class="flex justify-end">
<a href="{{ route('admin.machines.index') }}" class="bg-gray-200 dark:bg-gray-600 hover:bg-gray-300 dark:hover:bg-gray-500 text-gray-800 dark:text-white font-bold py-2 px-4 rounded mr-2">取消</a> <a href="{{ route('admin.machines.index') }}" class="bg-gray-200 dark:bg-gray-600 hover:bg-gray-300 dark:hover:bg-gray-500 text-gray-800 dark:text-white font-bold py-2 px-4 rounded mr-2">取消</a>
<button type="submit" class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded">建立</button> <button type="submit" class="btn-luxury-primary">建立</button>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -45,7 +45,7 @@
<div class="flex justify-end"> <div class="flex justify-end">
<a href="{{ route('admin.machines.index') }}" class="bg-gray-200 dark:bg-gray-600 hover:bg-gray-300 dark:hover:bg-gray-500 text-gray-800 dark:text-white font-bold py-2 px-4 rounded mr-2">取消</a> <a href="{{ route('admin.machines.index') }}" class="bg-gray-200 dark:bg-gray-600 hover:bg-gray-300 dark:hover:bg-gray-500 text-gray-800 dark:text-white font-bold py-2 px-4 rounded mr-2">取消</a>
<button type="submit" class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded">更新</button> <button type="submit" class="btn-luxury-primary">更新</button>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -27,7 +27,7 @@
<div class="container mx-auto px-6 py-8"> <div class="container mx-auto px-6 py-8">
<div class="flex justify-between items-center mb-6"> <div class="flex justify-between items-center mb-6">
<h3 class="text-gray-900 dark:text-gray-200 text-3xl font-medium">會員等級設定</h3> <h3 class="text-gray-900 dark:text-gray-200 text-3xl font-medium">會員等級設定</h3>
<button onclick="document.getElementById('createModal').classList.remove('hidden')" class="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700"> <button onclick="document.getElementById('createModal').classList.remove('hidden')" class="btn-luxury-primary">
新增等級 新增等級
</button> </button>
</div> </div>
@@ -110,8 +110,8 @@
</div> </div>
</div> </div>
<div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex justify-end space-x-3"> <div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex justify-end space-x-3">
<button type="button" onclick="document.getElementById('createModal').classList.add('hidden')" class="px-4 py-2 text-gray-600 dark:text-gray-400 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700">取消</button> <button type="button" onclick="document.getElementById('createModal').classList.add('hidden')" class="btn-luxury-secondary">取消</button>
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">建立</button> <button type="submit" class="btn-luxury-primary">建立</button>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -35,7 +35,7 @@
<div class="container mx-auto px-6 py-8"> <div class="container mx-auto px-6 py-8">
<div class="flex justify-between items-center mb-6"> <div class="flex justify-between items-center mb-6">
<h3 class="text-gray-900 dark:text-gray-200 text-3xl font-medium">點數規則設定</h3> <h3 class="text-gray-900 dark:text-gray-200 text-3xl font-medium">點數規則設定</h3>
<button onclick="document.getElementById('createModal').classList.remove('hidden')" class="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700"> <button onclick="document.getElementById('createModal').classList.remove('hidden')" class="btn-luxury-primary">
新增規則 新增規則
</button> </button>
</div> </div>
@@ -128,8 +128,8 @@
</div> </div>
</div> </div>
<div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex justify-end space-x-3"> <div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex justify-end space-x-3">
<button type="button" onclick="document.getElementById('createModal').classList.add('hidden')" class="px-4 py-2 text-gray-600 dark:text-gray-400 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700">取消</button> <button type="button" onclick="document.getElementById('createModal').classList.add('hidden')" class="btn-luxury-secondary">取消</button>
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">建立</button> <button type="submit" class="btn-luxury-primary">建立</button>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -41,8 +41,10 @@
<!-- ========== HEADER ========== --> <!-- ========== HEADER ========== -->
<header class="sticky top-0 inset-x-0 flex flex-wrap sm:justify-start sm:flex-nowrap z-[48] w-full bg-white/80 backdrop-blur-md border-b border-slate-200/50 text-sm py-2.5 sm:py-4 lg:pl-64 dark:bg-[#0f172a]/80 dark:border-slate-800/50 shadow-sm"> <header class="sticky top-0 inset-x-0 flex flex-wrap sm:justify-start sm:flex-nowrap z-[48] w-full bg-white/80 backdrop-blur-md border-b border-slate-200/50 text-sm py-2.5 sm:py-4 lg:pl-64 dark:bg-[#0f172a]/80 dark:border-slate-800/50 shadow-sm">
<nav class="flex basis-full items-center w-full mx-auto px-4 sm:px-6 md:px-8" aria-label="Global"> <nav class="flex basis-full items-center w-full mx-auto px-4 sm:px-6 md:px-8" aria-label="Global">
<div class="mr-5 lg:mr-0 lg:hidden"> <div class="mr-5 lg:mr-0 lg:hidden text-center">
<a class="flex-none text-xl font-semibold dark:text-white" href="#" aria-label="Brand">Star Cloud</a> <a class="flex-none text-xl font-bold dark:text-white font-display tracking-tight" href="{{ route('admin.dashboard') }}" aria-label="Brand">
Star<span class="text-cyan-500">Cloud</span>
</a>
</div> </div>
<div class="w-full flex items-center justify-end ml-auto sm:justify-between sm:gap-x-3 sm:order-3"> <div class="w-full flex items-center justify-end ml-auto sm:justify-between sm:gap-x-3 sm:order-3">
@@ -93,9 +95,9 @@
<p class="text-sm font-medium text-gray-800 dark:text-gray-300">{{ Auth::user()->email }}</p> <p class="text-sm font-medium text-gray-800 dark:text-gray-300">{{ Auth::user()->email }}</p>
</div> </div>
<div class="mt-2 py-2 first:pt-0 last:pb-0"> <div class="mt-2 py-2 first:pt-0 last:pb-0">
<a class="flex items-center gap-x-3.5 py-2 px-3 rounded-md text-sm text-gray-800 hover:bg-gray-100 focus:ring-2 focus:ring-blue-500 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-300" href="{{ route('profile.edit') }}"> <a class="flex items-center gap-x-3.5 py-2 px-3 rounded-md text-sm text-gray-800 hover:bg-gray-100 focus:ring-2 focus:ring-blue-500 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-300 transition-colors" href="{{ route('profile.edit') }}">
<svg class="flex-shrink-0 w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg> <svg class="flex-shrink-0 w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
個人檔案 帳戶設定
</a> </a>
<form method="POST" action="{{ route('logout') }}"> <form method="POST" action="{{ route('logout') }}">
@csrf @csrf
@@ -135,7 +137,7 @@
</svg> </svg>
</li> </li>
<li class="text-sm font-semibold text-gray-800 truncate dark:text-gray-200" aria-current="page"> <li class="text-sm font-semibold text-gray-800 truncate dark:text-gray-200" aria-current="page">
Dashboard 儀表板
</li> </li>
</ol> </ol>
<!-- End Breadcrumb --> <!-- End Breadcrumb -->
@@ -155,9 +157,12 @@
</svg> </svg>
</button> </button>
<div class="px-6 mb-4"> <div class="px-6 mb-8 mt-2">
<a class="flex-none text-2xl font-bold text-slate-900 dark:text-white font-display tracking-tight" href="#" aria-label="Brand"> <a class="flex items-center gap-x-2 text-2xl font-bold text-slate-900 dark:text-white font-display tracking-tight whitespace-nowrap" href="{{ route('admin.dashboard') }}" aria-label="Brand">
Star<span class="text-cyan-500">Cloud</span> <div class="w-8 h-8 rounded-lg bg-luxury-gradient flex items-center justify-center shadow-lg shadow-cyan-500/20">
<span class="text-white text-base">S</span>
</div>
<span>Star<span class="text-cyan-500">Cloud</span></span>
</a> </a>
</div> </div>