mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 10:00:30 +08:00
feat(web): apply Figma design tokens to MainLayout (white sidebar, 60px header, breadcrumb, global search, theme variables)
This commit is contained in:
@@ -1,88 +1,213 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-container class="shell">
|
<div class="app-shell">
|
||||||
<el-aside width="220px" class="aside">
|
<!-- ===== HEADER 60px ===== -->
|
||||||
<div class="brand">创飞 · 交付平台</div>
|
<header class="app-header">
|
||||||
<el-menu router :default-active="route.path" background-color="#001529" text-color="#fff" active-text-color="#ffd04b">
|
<div class="header-left">
|
||||||
<el-menu-item index="/">
|
<div class="header-logo">
|
||||||
<span>首页</span>
|
<div class="logo-icon"></div>
|
||||||
</el-menu-item>
|
<span class="logo-text">CraftLabs</span>
|
||||||
<el-menu-item v-if="auth.hasAnyRole(['SYS_ADMIN', 'DEVELOPER'])" index="/customers">
|
</div>
|
||||||
<span>客户管理</span>
|
<nav class="header-nav">
|
||||||
</el-menu-item>
|
<span class="nav-item active">授权平台</span>
|
||||||
<el-menu-item v-if="auth.hasAnyRole(['SYS_ADMIN', 'DEVELOPER'])" index="/projects">
|
</nav>
|
||||||
<span>项目管理</span>
|
</div>
|
||||||
</el-menu-item>
|
<div class="header-right">
|
||||||
<el-menu-item v-if="auth.hasAnyRole(['SYS_ADMIN', 'DEVELOPER'])" index="/contracts">
|
<div class="global-search">
|
||||||
<span>合同管理</span>
|
<span class="search-icon">🔍</span>
|
||||||
</el-menu-item>
|
<input class="search-input" placeholder="搜索许可证 / 客户 / 合同…" @keyup.enter="onGlobalSearch">
|
||||||
<el-menu-item v-if="auth.hasAnyRole(['SYS_ADMIN', 'DEVELOPER'])" index="/deliveries">
|
</div>
|
||||||
<span>交付管理</span>
|
<div class="icon-btn" title="通知" @click="onNotifications">
|
||||||
</el-menu-item>
|
<span style="position:relative">🔔<span class="badge-dot">3</span></span>
|
||||||
<el-menu-item v-if="auth.hasAnyRole(['SYS_ADMIN', 'DEVELOPER'])" index="/licenses/sn">
|
</div>
|
||||||
<span>许可 SN</span>
|
<div class="icon-btn" title="设置" @click="$router.push('/')">
|
||||||
</el-menu-item>
|
<span>⚙️</span>
|
||||||
<el-menu-item v-if="auth.hasAnyRole(['SYS_ADMIN', 'OPS'])" index="/callbacks">
|
</div>
|
||||||
<span>Callback 收件箱</span>
|
<div class="user-area" @click="onUserMenu">
|
||||||
</el-menu-item>
|
<span class="user-avatar">{{ (auth.displayName || 'U')[0] }}</span>
|
||||||
<el-menu-item v-if="auth.hasAnyRole(['SYS_ADMIN', 'DEVELOPER', 'OPS'])" index="/integration/environments">
|
<span class="user-name">{{ auth.displayName || '—' }}</span>
|
||||||
<span>集成环境</span>
|
</div>
|
||||||
</el-menu-item>
|
<el-button type="danger" link size="small" @click="onLogout">退出</el-button>
|
||||||
<el-menu-item v-if="auth.hasAnyRole(['SYS_ADMIN', 'DEVELOPER', 'OPS'])" index="/integration/product-lines">
|
</div>
|
||||||
<span>产品线</span>
|
</header>
|
||||||
</el-menu-item>
|
|
||||||
</el-menu>
|
<!-- ===== BODY ===== -->
|
||||||
</el-aside>
|
<div class="app-body">
|
||||||
<el-container>
|
<!-- SIDEBAR 232px WHITE -->
|
||||||
<el-header class="top">
|
<aside class="app-sidebar">
|
||||||
<span class="user">{{ auth.displayName || "—" }}</span>
|
<div class="sidebar-section-label">业务管理</div>
|
||||||
<el-button type="danger" link @click="logout">退出</el-button>
|
<div
|
||||||
</el-header>
|
v-for="item in menuItems"
|
||||||
<el-main class="main">
|
:key="item.path"
|
||||||
|
:class="['sidebar-item', { active: isActive(item) }]"
|
||||||
|
@click="$router.push(item.path)"
|
||||||
|
>
|
||||||
|
<span class="sidebar-item-icon">{{ item.icon }}</span>
|
||||||
|
<span class="sidebar-item-text">{{ item.label }}</span>
|
||||||
|
<span v-if="item.badge" class="sidebar-item-badge">{{ item.badge }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-footer">CraftLabs Platform v0.1.0</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- CONTENT -->
|
||||||
|
<div class="app-content">
|
||||||
|
<!-- Breadcrumb -->
|
||||||
|
<div class="breadcrumb" v-if="breadcrumb.length">
|
||||||
|
<span v-for="(b, i) in breadcrumb" :key="i">
|
||||||
|
<span v-if="i > 0" class="bc-sep">›</span>
|
||||||
|
<span :class="{ 'bc-current': i === breadcrumb.length - 1 }">{{ b }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="content-body">
|
||||||
<router-view />
|
<router-view />
|
||||||
</el-main>
|
</div>
|
||||||
</el-container>
|
</div>
|
||||||
</el-container>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { computed } from "vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { useAuthStore } from "../stores/auth";
|
import { useAuthStore } from "../stores/auth";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
|
|
||||||
function logout() {
|
const menuItems = [
|
||||||
auth.logout();
|
{ path: "/", icon: "📊", label: "首页", roles: ["SYS_ADMIN","DEVELOPER","OPS"] },
|
||||||
router.push({ name: "login" });
|
{ path: "/customers", icon: "👥", label: "客户管理", roles: ["SYS_ADMIN","DEVELOPER"] },
|
||||||
|
{ path: "/contracts", icon: "📋", label: "合同管理", roles: ["SYS_ADMIN","DEVELOPER"] },
|
||||||
|
{ path: "/deliveries", icon: "📦", label: "交付管理", roles: ["SYS_ADMIN","DEVELOPER"] },
|
||||||
|
{ path: "/licenses/sn", icon: "🔑", label: "许可 SN", roles: ["SYS_ADMIN","DEVELOPER"] },
|
||||||
|
{ path: "/license-compare", icon: "🛡️", label: "许可证管理", badge: "NEW", roles: ["SYS_ADMIN","DEVELOPER"] },
|
||||||
|
{ path: "/callbacks", icon: "📨", label: "Callback 收件箱", roles: ["SYS_ADMIN","OPS"] },
|
||||||
|
{ path: "/integration/environments", icon: "🌐", label: "集成环境", roles: ["SYS_ADMIN","DEVELOPER","OPS"] },
|
||||||
|
{ path: "/integration/product-lines", icon: "📱", label: "产品线", roles: ["SYS_ADMIN","DEVELOPER","OPS"] },
|
||||||
|
];
|
||||||
|
|
||||||
|
const visibleMenu = computed(() => menuItems.filter(m => auth.hasAnyRole(m.roles)));
|
||||||
|
|
||||||
|
function isActive(item) {
|
||||||
|
if (item.path === "/") return route.path === "/";
|
||||||
|
return route.path.startsWith(item.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const breadcrumbLabels = {
|
||||||
|
"/": "首页",
|
||||||
|
"/customers": "客户管理",
|
||||||
|
"/projects": "项目管理",
|
||||||
|
"/contracts": "合同管理",
|
||||||
|
"/deliveries": "交付管理",
|
||||||
|
"/licenses": "许可证管理",
|
||||||
|
"/callbacks": "Callback 收件箱",
|
||||||
|
"/integration": "集成配置",
|
||||||
|
"/license-compare": "许可证管理",
|
||||||
|
};
|
||||||
|
|
||||||
|
const breadcrumb = computed(() => {
|
||||||
|
const parts = route.path.split("/").filter(Boolean);
|
||||||
|
if (parts.length === 0) return ["首页"];
|
||||||
|
const result = ["授权运营"];
|
||||||
|
let current = "";
|
||||||
|
for (const p of parts) {
|
||||||
|
current += "/" + p;
|
||||||
|
result.push(breadcrumbLabels[current] || breadcrumbLabels["/" + p] || p);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
function onGlobalSearch() { ElMessage.info("全局搜索(开发中)") }
|
||||||
|
function onNotifications() { ElMessage.info("通知中心(开发中)") }
|
||||||
|
function onUserMenu() { ElMessage.info("用户设置(开发中)") }
|
||||||
|
function onLogout() { auth.logout(); router.push({ name: "login" }); }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.shell {
|
/* ===== SHELL ===== */
|
||||||
height: 100vh;
|
.app-shell { height: 100vh; display: flex; flex-direction: column; background: var(--page-bg, #EAEFFA); }
|
||||||
|
|
||||||
|
/* ===== HEADER ===== */
|
||||||
|
.app-header {
|
||||||
|
height: 60px; flex-shrink: 0;
|
||||||
|
background: #fff; border-bottom: 1px solid #E8ECF1;
|
||||||
|
display: flex; align-items: center; padding: 0 20px; gap: 12px; z-index: 100;
|
||||||
}
|
}
|
||||||
.aside {
|
.header-left { display: flex; align-items: center; gap: 32px; }
|
||||||
background: #001529;
|
.header-logo { display: flex; align-items: center; gap: 8px; }
|
||||||
color: #fff;
|
.logo-icon {
|
||||||
|
width: 28px; height: 28px;
|
||||||
|
background: linear-gradient(135deg, #2C3E6B, #3D5A99); border-radius: 6px;
|
||||||
}
|
}
|
||||||
.brand {
|
.logo-text { font-weight: 700; font-size: 16px; color: #2C3E6B; letter-spacing: 0.5px; }
|
||||||
padding: 16px;
|
.header-nav { display: flex; gap: 0; }
|
||||||
font-weight: 600;
|
.nav-item { padding: 0 16px; height: 60px; line-height: 60px; cursor: pointer; font-size: 14px; color: #606266; }
|
||||||
border-bottom: 1px solid #ffffff22;
|
.nav-item.active { color: #2C3E6B; font-weight: 600; border-bottom: 2px solid #2C3E6B; }
|
||||||
|
|
||||||
|
.header-right { margin-left: auto; display: flex; align-items: center; gap: 10px; }
|
||||||
|
.global-search {
|
||||||
|
display: flex; align-items: center; border: 1px solid #E0E3E8; border-radius: 6px;
|
||||||
|
padding: 4px 10px; gap: 6px; width: 220px; background: #F8F9FB;
|
||||||
}
|
}
|
||||||
.top {
|
.search-icon { color: #C0C4CC; font-size: 13px; }
|
||||||
display: flex;
|
.search-input { border: none; outline: none; flex: 1; font-size: 13px; background: transparent; color: #303133; font-family: inherit; }
|
||||||
align-items: center;
|
.search-input::placeholder { color: #C0C4CC; }
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 12px;
|
.icon-btn {
|
||||||
border-bottom: 1px solid #ebeef5;
|
width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
|
||||||
|
border-radius: 6px; cursor: pointer; font-size: 14px; position: relative; color: #606266;
|
||||||
}
|
}
|
||||||
.user {
|
.icon-btn:hover { background: #F2F5FC; }
|
||||||
color: #606266;
|
.badge-dot {
|
||||||
font-size: 14px;
|
position: absolute; top: -3px; right: -6px;
|
||||||
|
width: 16px; height: 16px; background: #D54941; border-radius: 50%;
|
||||||
|
font-size: 10px; color: #fff; line-height: 16px; text-align: center; font-weight: 600;
|
||||||
}
|
}
|
||||||
.main {
|
.user-area {
|
||||||
background: #f0f2f5;
|
display: flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 6px; cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.user-area:hover { background: #F2F5FC; }
|
||||||
|
.user-avatar {
|
||||||
|
width: 28px; height: 28px; border-radius: 50%; background: #2C3E6B; color: #fff;
|
||||||
|
text-align: center; line-height: 28px; font-size: 12px; font-weight: 600; flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.user-name { color: #303133; font-weight: 500; font-size: 13px; }
|
||||||
|
|
||||||
|
/* ===== BODY ===== */
|
||||||
|
.app-body { flex: 1; display: flex; overflow: hidden; }
|
||||||
|
|
||||||
|
/* ===== SIDEBAR WHITE ===== */
|
||||||
|
.app-sidebar {
|
||||||
|
width: 232px; flex-shrink: 0; overflow-y: auto;
|
||||||
|
background: #fff; border-right: 1px solid #E8ECF1;
|
||||||
|
display: flex; flex-direction: column; padding-top: 8px;
|
||||||
|
}
|
||||||
|
.sidebar-section-label {
|
||||||
|
padding: 6px 20px; font-size: 11px; color: #C0C4CC; text-transform: uppercase; font-weight: 600; letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
.sidebar-item {
|
||||||
|
display: flex; align-items: center; gap: 10px;
|
||||||
|
padding: 9px 20px; cursor: pointer; font-size: 14px; color: #606266;
|
||||||
|
border-right: 3px solid transparent; transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.sidebar-item:hover { background: #F2F5FC; color: #2C3E6B; }
|
||||||
|
.sidebar-item.active { background: #F2F5FC; color: #2C3E6B; font-weight: 600; border-right-color: #2C3E6B; }
|
||||||
|
.sidebar-item-icon { width: 20px; text-align: center; font-size: 14px; }
|
||||||
|
.sidebar-item-text { flex: 1; }
|
||||||
|
.sidebar-item-badge { background: #D54941; color: #fff; font-size: 10px; padding: 1px 6px; border-radius: 10px; font-weight: 600; }
|
||||||
|
.sidebar-footer {
|
||||||
|
margin-top: auto; padding: 12px 20px; border-top: 1px solid #F2F5FC;
|
||||||
|
font-size: 11px; color: #C0C4CC;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== CONTENT ===== */
|
||||||
|
.app-content { flex: 1; display: flex; flex-direction: column; overflow: hidden; background: #EAEFFA; }
|
||||||
|
.breadcrumb {
|
||||||
|
height: 46px; flex-shrink: 0; background: #fff; border-bottom: 1px solid #E8ECF1;
|
||||||
|
display: flex; align-items: center; padding: 0 20px; gap: 6px; font-size: 13px;
|
||||||
|
}
|
||||||
|
.bc-sep { color: #C0C4CC; }
|
||||||
|
.bc-current { color: #2C3E6B; font-weight: 600; }
|
||||||
|
.content-body { flex: 1; overflow-y: auto; padding: 16px 20px; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createApp } from "vue";
|
|||||||
import { createPinia } from "pinia";
|
import { createPinia } from "pinia";
|
||||||
import ElementPlus from "element-plus";
|
import ElementPlus from "element-plus";
|
||||||
import "element-plus/dist/index.css";
|
import "element-plus/dist/index.css";
|
||||||
|
import "./theme.css";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/* CraftLabs Design System v1.0 — Global Theme Variables */
|
||||||
|
/* Figma「安徽地质博物馆 v2.0」Token → Element Plus 映射 */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* Primary */
|
||||||
|
--el-color-primary: #2C3E6B;
|
||||||
|
--el-color-primary-light-3: #3D5A99;
|
||||||
|
--el-color-primary-light-5: #5A78B5;
|
||||||
|
--el-color-primary-light-7: #8BA0CC;
|
||||||
|
--el-color-primary-light-9: #D6DFF0;
|
||||||
|
--el-color-primary-dark-2: #1D2D4A;
|
||||||
|
|
||||||
|
/* Background */
|
||||||
|
--el-bg-color-page: #EAEFFA;
|
||||||
|
--el-bg-color: #FFFFFF;
|
||||||
|
--el-bg-color-overlay: #FFFFFF;
|
||||||
|
|
||||||
|
/* Border */
|
||||||
|
--el-border-color: #E8ECF1;
|
||||||
|
--el-border-color-light: #F2F5FC;
|
||||||
|
--el-border-radius-base: 6px;
|
||||||
|
--el-border-radius-small: 4px;
|
||||||
|
|
||||||
|
/* Text */
|
||||||
|
--el-text-color-primary: #303133;
|
||||||
|
--el-text-color-regular: #606266;
|
||||||
|
--el-text-color-secondary: #909399;
|
||||||
|
|
||||||
|
/* Table */
|
||||||
|
--el-table-header-bg-color: #F2F5FC;
|
||||||
|
--el-table-header-text-color: #2C3E6B;
|
||||||
|
|
||||||
|
/* Tag */
|
||||||
|
--el-color-success: #1A7A3A;
|
||||||
|
--el-color-success-light-3: #2EA04E;
|
||||||
|
--el-color-success-light-9: #E6F7EE;
|
||||||
|
|
||||||
|
--el-color-danger: #F56C6C;
|
||||||
|
--el-color-danger-light-9: #FEF0F0;
|
||||||
|
|
||||||
|
--el-color-warning: #E6A23C;
|
||||||
|
--el-color-warning-light-9: #FDF6EC;
|
||||||
|
|
||||||
|
/* Dialog */
|
||||||
|
--el-dialog-border-radius: 8px;
|
||||||
|
--el-overlay-color-lighter: rgba(0, 0, 0, 0.45);
|
||||||
|
|
||||||
|
/* Menu */
|
||||||
|
--el-menu-bg-color: #FFFFFF;
|
||||||
|
--el-menu-text-color: #606266;
|
||||||
|
--el-menu-hover-bg-color: #F2F5FC;
|
||||||
|
--el-menu-active-color: #2C3E6B;
|
||||||
|
|
||||||
|
/* Button */
|
||||||
|
--el-button-font-weight: 500;
|
||||||
|
--el-button-border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Body defaults */
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
background: var(--el-bg-color-page);
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user