<?php // app/templates/AdminMaster.php ?>
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<base href="<?= $ro->getBaseHref() ?>" />
<title><?= $t->getAttribute('_title', 'Admin') ?> - WASP Admin</title>
<!-- 前台共用樣式 -->
<link rel="stylesheet" href="/css/default.css">
<!-- 後台專用樣式 (後載入,優先級較高) -->
<link rel="stylesheet" href="/css/admin.css">
<!-- 圖示字體 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/css/all.min.css">
</head>
<body class="admin-body">
<!-- Header -->
<header class="admin-header">
<div class="brand">
<a href="<?= $ro->gen('admin.dashboard') ?>">
<img src="/images/logo-admin.svg" alt="WASP Admin" height="32">
</a>
</div>
<nav class="admin-nav">
<a href="<?= $ro->gen('admin.listing.index') ?>"><i class="fas fa-car"></i> 車輛管理</a>
<a href="<?= $ro->gen('admin.user.index') ?>"><i class="fas fa-users"></i> 用戶管理</a>
<a href="<?= $ro->gen('admin.settings') ?>"><i class="fas fa-cog"></i> 設定</a>
<a href="<?= $ro->gen('admin.logout') ?>" class="btn-logout"><i class="fas fa-sign-out-alt"></i> 登出</a>
</nav>
</header>
<!-- Sidebar + Content -->
<div class="admin-container">
<aside class="admin-sidebar">
<ul class="sidebar-menu">
<li><a href="<?= $ro->gen('admin.listing.index') ?>"><i class="fas fa-list"></i> 所有車輛</a></li>
<li><a href="<?= $ro->gen('admin.listing.create') ?>"><i class="fas fa-plus"></i> 新增車輛</a></li>
<li><a href="<?= $ro->gen('admin.user.index') ?>"><i class="fas fa-users"></i> 銷售商帳號</a></li>
</ul>
</aside>
<main class="admin-content">
<div class="page-header">
<h1><?= $t->getAttribute('_title', '儀表板') ?></h1>
<ol class="breadcrumb">
<li><a href="<?= $ro->gen('admin.dashboard') ?>">首頁</a></li>
<li class="active"><?= $t->getAttribute('_title', '') ?></li>
</ol>
</div>
<!-- 子視圖內容注入點 -->
<?= $inner ?>
</main>
</div>
<footer class="admin-footer">
<p>© <?= date('Y') ?> WASP Admin. Powered by <a href="https://agavi.org">Agavi</a>.</p>
</footer>
<!-- 後台專用 JS -->
<script src="/js/admin.js"></script>
</body></html>
// admin.scss
$sidebar-width: 240px;
$header-height: 56px;
$primary: #2c3e50;
$accent: #3498db;
.admin-body {
display: flex;
flex-direction: column;
min-height: 100vh;
margin: 0;
font-family: 'Noto Sans TC', system-ui, sans-serif;
background: #f4f6f9;
}
.admin-header {
height: $header-height;
background: $primary;
color: #fff;
display: flex;
align-items: center;
padding: 0 1.5rem;
position: sticky;
top: 0;
z-index: 100;
.brand img { height: 28px; }
.admin-nav {
margin-left: auto;
display: flex; gap: 1rem; align-items: center;
a { color: rgba(255,255,255,0.8); text-decoration: none; padding: 0.5rem 1rem; border-radius: 4px; transition: background .2s; }
a:hover, a:focus { background: rgba(255,255,255,0.1); color: #fff; }
.btn-logout { background: #e74c3c; }
}
}
.admin-container {
display: flex;
flex: 1;
overflow: hidden;
}
.admin-sidebar {
width: $sidebar-width;
background: #fff;
border-right: 1px solid #e1e4e8;
padding: 1rem 0;
overflow-y: auto;
.sidebar-menu {
list-style: none; padding: 0; margin: 0;
li a { display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem 1.5rem; color: #333; text-decoration: none; transition: background .2s; }
li a:hover, li a.active { background: #e8f0fe; color: $accent; }
i { width: 20px; text-align: center; }
}
}
.admin-content {
flex: 1;
padding: 2rem;
overflow-y: auto;
.page-header { margin-bottom: 1.5rem; }
.breadcrumb { font-size: 0.875rem; color: #666; }
.breadcrumb a { color: $accent; }
}
.admin-footer {
padding: 1rem;
text-align: center;
color: #999;
font-size: 0.875rem;
border-top: 1px solid #e1e4e8;
background: #fff;
}
/* 響應式: 768px 以下側邊欄收合為抽屜 */
@media (max-width: 768px) {
.admin-sidebar {
position: fixed; left: 0; top: $header-height; bottom: 0;
transform: translateX(-100%);
transition: transform .3s ease;
z-index: 200;
&.open { transform: translateX(0); }
}
.admin-content { padding-top: calc(#{$header-height} + 1rem); }
}
# 專案根目錄
npm init -y
npm install -D sass
# package.json scripts
"build:css": "sass pub/css/admin.scss pub/css/admin.css --style=compressed --no-source-map"
# 執行
npm run build:css
上線前執行 `npm run build:css` 產出壓縮 CSS,配合 Agavi 快取機制 (`output_cache`)。
<?php
// app/modules/Admin/views/ListingIndexSuccessView.class.php
class Admin_ListingIndexSuccessView extends AgaviHtmlView
{
public function setupHtml()
{
$this->setLayout('AdminMaster');
$this->setAttribute('_title', '車輛管理');
}
}
<?php // AdminMaster.php 片段 ?>
<ul class="sidebar-menu">
<?php $user = $this->getContext()->getSession()->getAttribute('admin_user'); ?>
<li><a href="<?= $ro->gen('admin.listing.index') ?>"><i class="fas fa-car"></i> 車輛列表</a></li>
<?php if ($user['role'] === 'admin'): ?>
<li><a href="<?= $ro->gen('admin.user.index') ?>"><i class="fas fa-users"></i> 用戶管理</a></li>
<li><a href="<?= $ro->gen('admin.settings') ?>"><i class="fas fa-cog"></i> 系統設定</a></li>
<?php endif; ?>
</ul>