906 lines
59 KiB
HTML
906 lines
59 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>FMS Admin — shadcn/ui Style</title>
|
||
<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=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
/* ============================================================
|
||
shadcn/ui design tokens (HSL)
|
||
============================================================ */
|
||
:root {
|
||
--background: 0 0% 100%;
|
||
--foreground: 222.2 84% 4.9%;
|
||
--card: 0 0% 100%;
|
||
--card-foreground: 222.2 84% 4.9%;
|
||
--popover: 0 0% 100%;
|
||
--popover-foreground:222.2 84% 4.9%;
|
||
--primary: 222.2 47.4% 11.2%;
|
||
--primary-foreground:210 40% 98%;
|
||
--secondary: 210 40% 96.1%;
|
||
--secondary-foreground:222.2 47.4% 11.2%;
|
||
--muted: 210 40% 96.1%;
|
||
--muted-foreground: 215.4 16.3% 46.9%;
|
||
--accent: 210 40% 96.1%;
|
||
--accent-foreground:222.2 47.4% 11.2%;
|
||
--destructive: 0 84.2% 60.2%;
|
||
--destructive-foreground:210 40% 98%;
|
||
--border: 214.3 31.8% 91.4%;
|
||
--input: 214.3 31.8% 91.4%;
|
||
--ring: 222.2 84% 4.9%;
|
||
--radius: 0.5rem;
|
||
--sidebar-background: 222.2 47.4% 8%;
|
||
--sidebar-foreground: 210 40% 90%;
|
||
--sidebar-primary: 210 40% 98%;
|
||
--sidebar-primary-foreground:222.2 47.4% 11.2%;
|
||
--sidebar-accent: 217.2 32.6% 15%;
|
||
--sidebar-accent-foreground:210 40% 90%;
|
||
--sidebar-border: 217.2 32.6% 17%;
|
||
--sidebar-ring: 224.3 76.3% 48%;
|
||
}
|
||
|
||
* { margin:0; padding:0; box-sizing:border-box; }
|
||
html, body { height:100%; overflow:hidden; }
|
||
|
||
body {
|
||
font-family: "Inter", -apple-system, BlinkMac系统管理Font, "Segoe UI",
|
||
"否to Sans SC", "Microsoft YaHei", sans-serif;
|
||
font-size: 13px; line-height: 1.6;
|
||
color: hsl(var(--foreground));
|
||
background: hsl(var(--background));
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
}
|
||
|
||
::-webkit-scrollbar { width:5px; height:5px; }
|
||
::-webkit-scrollbar-track { background:transparent; }
|
||
::-webkit-scrollbar-thumb { background:hsl(var(--border)); border-radius:3px; }
|
||
|
||
/* ============================================================
|
||
TOP BAR
|
||
============================================================ */
|
||
.topbar {
|
||
position:fixed; z-index:100; top:0; left:0; right:0; height:48px;
|
||
background:hsl(var(--background));
|
||
border-bottom:1px solid hsl(var(--border));
|
||
display:flex; align-items:center;
|
||
}
|
||
.topbar-logo {
|
||
width:230px; min-width:230px; height:100%; display:flex;
|
||
align-items:center; padding:0 20px; gap:10px;
|
||
border-right:1px solid hsl(var(--border));
|
||
transition:width .2s;
|
||
}
|
||
.s-collapsed .topbar-logo { width:60px; min-width:60px; justify-content:center; padding:0; }
|
||
.topbar-logo .logo-sq {
|
||
width:28px; height:28px; border-radius:6px;
|
||
background:hsl(var(--primary)); color:hsl(var(--primary-foreground));
|
||
display:flex; align-items:center; justify-content:center;
|
||
font-size:14px; font-weight:700; flex-shrink:0;
|
||
}
|
||
.topbar-logo .logo-text { font-size:15px; font-weight:700; white-space:nowrap; letter-spacing:-.01em; }
|
||
.s-collapsed .topbar-logo .logo-text { display:none; }
|
||
|
||
.topbar-search { position:relative; margin-left:24px; margin-right:auto; width:280px; }
|
||
.topbar-search svg { position:absolute; left:12px; top:8px; width:15px; height:15px; color:hsl(var(--muted-foreground)); }
|
||
.topbar-search input {
|
||
width:100%; height:34px; padding:0 14px 0 36px;
|
||
border:1px solid hsl(var(--input)); border-radius:calc(var(--radius) - 2px);
|
||
background:hsl(var(--background)); font:13px/1 inherit; color:hsl(var(--foreground));
|
||
outline:none; transition:all .15s;
|
||
}
|
||
.topbar-search input:focus {
|
||
border-color:hsl(var(--ring)); box-shadow:0 0 0 2px hsla(var(--ring)/.15);
|
||
width:340px;
|
||
}
|
||
.topbar-search input::placeholder { color:hsl(var(--muted-foreground)); }
|
||
|
||
.topbar-actions { display:flex; align-items:center; gap:2px; padding:0 12px; height:100%; }
|
||
.tb-btn {
|
||
width:34px; height:34px; border:0; border-radius:calc(var(--radius) - 2px);
|
||
background:transparent; cursor:pointer; display:flex; align-items:center; justify-content:center;
|
||
color:hsl(var(--muted-foreground)); position:relative; transition:all .12s;
|
||
}
|
||
.tb-btn:hover { background:hsl(var(--accent)); color:hsl(var(--accent-foreground)); }
|
||
.tb-btn .dot {
|
||
position:absolute; top:7px; right:7px; width:7px; height:7px; border-radius:50%;
|
||
background:hsl(var(--destructive)); border:1.5px solid hsl(var(--background));
|
||
}
|
||
.tb-btn svg { width:17px; height:17px; }
|
||
|
||
.user-btn {
|
||
display:flex; align-items:center; gap:8px; height:34px; padding:0 8px 0 4px;
|
||
border:0; border-radius:calc(var(--radius) - 2px); background:transparent; cursor:pointer;
|
||
font:13px/1 inherit; color:hsl(var(--muted-foreground)); transition:all .12s;
|
||
}
|
||
.user-btn:hover { background:hsl(var(--accent)); color:hsl(var(--accent-foreground)); }
|
||
.user-btn .av {
|
||
width:26px; height:26px; border-radius:50%;
|
||
background:hsl(var(--primary)); color:hsl(var(--primary-foreground));
|
||
display:flex; align-items:center; justify-content:center; font-size:11px; font-weight:600; flex-shrink:0;
|
||
}
|
||
.user-btn .nm { font-size:12px; font-weight:500; }
|
||
.user-btn .cr { font-size:9px; opacity:.5; }
|
||
|
||
.user-dd {
|
||
position:absolute; top:44px; right:4px; z-index:200; min-width:180px;
|
||
background:hsl(var(--popover)); border:1px solid hsl(var(--border));
|
||
border-radius:var(--radius); box-shadow:0 4px 16px rgba(0,0,0,.08);
|
||
padding:4px; display:none;
|
||
}
|
||
.user-dd.open { display:block; }
|
||
.user-dd button {
|
||
display:flex; align-items:center; gap:10px; width:100%; height:34px;
|
||
padding:0 12px; border:0; border-radius:calc(var(--radius) - 2px);
|
||
background:transparent; color:hsl(var(--foreground)); font:13px/1 inherit; cursor:pointer;
|
||
transition:all .1s;
|
||
}
|
||
.user-dd button:hover { background:hsl(var(--accent)); }
|
||
.user-dd button.dng { color:hsl(var(--destructive)); }
|
||
.user-dd hr { border:0; border-top:1px solid hsl(var(--border)); margin:3px 0; }
|
||
|
||
/* ============================================================
|
||
SIDEBAR — dark sidebar (shadcn signature)
|
||
============================================================ */
|
||
.sidebar {
|
||
position:fixed; z-index:90; top:48px; left:0; bottom:0; width:230px;
|
||
background:hsl(var(--sidebar-background));
|
||
display:flex; flex-direction:column; overflow:hidden; transition:width .2s;
|
||
}
|
||
.s-collapsed .sidebar { width:60px; }
|
||
|
||
.sidebar-nav { flex:1; overflow-y:auto; overflow-x:hidden; padding:8px 10px 12px; }
|
||
.s-collapsed .sidebar-nav { padding:8px 6px; }
|
||
|
||
.s-group {
|
||
padding:18px 12px 6px; font-size:10px; font-weight:600; letter-spacing:.06em;
|
||
text-transform:uppercase; color:hsla(var(--sidebar-foreground)/.45);
|
||
white-space:nowrap; overflow:hidden;
|
||
}
|
||
.s-collapsed .s-group { display:none; }
|
||
|
||
.s-item {
|
||
display:flex; align-items:center; gap:10px; width:100%; height:36px;
|
||
padding:0 12px; border:0; border-radius:calc(var(--radius) - 2px);
|
||
background:transparent; color:hsla(var(--sidebar-foreground)/.7);
|
||
font:13px/1 inherit; cursor:pointer; text-align:left;
|
||
white-space:nowrap; overflow:hidden; transition:all .1s;
|
||
}
|
||
.s-collapsed .s-item { justify-content:center; padding:0; width:44px; margin:2px auto; }
|
||
.s-item:hover { background:hsl(var(--sidebar-accent)); color:hsl(var(--sidebar-foreground)); }
|
||
.s-item.active {
|
||
background:hsl(var(--sidebar-accent));
|
||
color:hsl(var(--sidebar-primary));
|
||
font-weight:600;
|
||
}
|
||
.s-item svg { width:17px; height:17px; flex-shrink:0; opacity:.65; }
|
||
.s-item.active svg { opacity:1; }
|
||
.s-item:hover svg { opacity:.85; }
|
||
.s-item .s-label { flex:1; overflow:hidden; text-overflow:ellipsis; }
|
||
.s-collapsed .s-item .s-label { display:none; }
|
||
.s-item .s-caret { font-size:9px; opacity:.4; flex-shrink:0; transition:transform .2s; }
|
||
.s-collapsed .s-item .s-caret { display:none; }
|
||
.s-item.expanded .s-caret { transform:rotate(90deg); }
|
||
|
||
.s-sub { overflow:hidden; max-height:0; transition:max-height .25s ease; }
|
||
.s-sub.open { max-height:400px; }
|
||
.s-sub .s-item { padding-left:42px; font-size:12px; height:32px; }
|
||
.s-collapsed .s-sub .s-item { padding-left:0; }
|
||
.s-collapsed .s-sub { display:none; }
|
||
|
||
.sidebar-footer {
|
||
padding:10px 12px;
|
||
border-top:1px solid hsl(var(--sidebar-border));
|
||
display:flex;
|
||
}
|
||
.s-collapsed .sidebar-footer { justify-content:center; padding:10px 6px; }
|
||
.s-cc-btn {
|
||
flex:1; height:32px; border:0; border-radius:calc(var(--radius) - 2px);
|
||
background:transparent; color:hsla(var(--sidebar-foreground)/.4);
|
||
font:12px/1 inherit; cursor:pointer; display:flex; align-items:center; justify-content:center; gap:6px;
|
||
transition:all .12s;
|
||
}
|
||
.s-cc-btn:hover { background:hsl(var(--sidebar-accent)); color:hsl(var(--sidebar-foreground)); }
|
||
.s-collapsed .s-cc-btn span:last-child { display:none; }
|
||
.s-collapsed .s-cc-btn { flex:0; width:32px; }
|
||
.s-cc-btn svg { width:13px; height:13px; opacity:.5; }
|
||
|
||
/* ============================================================
|
||
MAIN
|
||
============================================================ */
|
||
.main {
|
||
position:fixed; top:48px; left:230px; right:0; bottom:0;
|
||
display:flex; flex-direction:column;
|
||
background:hsl(var(--background)); transition:left .2s; min-width:0;
|
||
}
|
||
.s-collapsed .main { left:60px; }
|
||
|
||
/* tab bar */
|
||
.tabbar {
|
||
height:38px; min-height:38px;
|
||
background:hsl(var(--background));
|
||
border-bottom:1px solid hsl(var(--border));
|
||
display:flex; align-items:stretch; overflow-x:auto; padding:0 4px;
|
||
}
|
||
.tabbar::-webkit-scrollbar { height:0; }
|
||
.tab-item {
|
||
display:flex; align-items:center; gap:6px; height:100%; padding:0 16px;
|
||
min-width:0; border:0; border-bottom:2px solid transparent;
|
||
background:transparent; color:hsl(var(--muted-foreground));
|
||
font:12px/1 inherit; font-weight:500; cursor:pointer; white-space:nowrap;
|
||
transition:all .12s;
|
||
}
|
||
.tab-item:hover { color:hsl(var(--foreground)); background:hsl(var(--accent)); }
|
||
.tab-item.active { color:hsl(var(--foreground)); font-weight:600; border-bottom-color:hsl(var(--foreground)); }
|
||
.tab-item .ti { font-size:12px; opacity:.6; }
|
||
.tab-close {
|
||
width:18px; height:18px; border:0; border-radius:3px; background:transparent;
|
||
color:inherit; opacity:.4; font:13px/1 inherit; cursor:pointer;
|
||
display:flex; align-items:center; justify-content:center; margin-left:2px;
|
||
}
|
||
.tab-close:hover { opacity:1; background:hsl(var(--border)); }
|
||
.tab-actions { display:flex; align-items:center; gap:2px; padding:0 8px; margin-left:auto; flex-shrink:0; }
|
||
.tab-actions button {
|
||
width:26px; height:26px; border:0; border-radius:calc(var(--radius) - 2px);
|
||
background:transparent; color:hsl(var(--muted-foreground)); cursor:pointer; font-size:15px;
|
||
display:flex; align-items:center; justify-content:center;
|
||
}
|
||
.tab-actions button:hover { background:hsl(var(--accent)); }
|
||
|
||
.content-wrap { flex:1; overflow:hidden; display:flex; flex-direction:column; }
|
||
.page { display:none; flex:1; overflow:hidden; flex-direction:column; }
|
||
.page.active { display:flex; }
|
||
|
||
/* ============================================================
|
||
SHARED
|
||
============================================================ */
|
||
.btn {
|
||
display:inline-flex; align-items:center; gap:6px; height:32px;
|
||
padding:0 14px;
|
||
border:1px solid hsl(var(--input)); border-radius:calc(var(--radius) - 2px);
|
||
background:hsl(var(--background)); color:hsl(var(--foreground));
|
||
font:13px/1 inherit; font-weight:500; cursor:pointer; white-space:nowrap;
|
||
transition:all .12s;
|
||
}
|
||
.btn:hover { background:hsl(var(--accent)); }
|
||
.btn.p { background:hsl(var(--primary)); border-color:hsl(var(--primary)); color:hsl(var(--primary-foreground)); }
|
||
.btn.p:hover { opacity:.9; }
|
||
.btn.d { border-color:transparent; color:hsl(var(--destructive)); }
|
||
.btn.d:hover { background:hsl(var(--destructive)/.1); }
|
||
.btn.g { border-color:transparent; color:hsl(var(--muted-foreground)); }
|
||
.btn.g:hover { background:hsl(var(--accent)); color:hsl(var(--foreground)); }
|
||
.btn.sm { height:28px; padding:0 10px; font-size:12px; }
|
||
|
||
.badge {
|
||
display:inline-flex; align-items:center; height:22px; padding:0 8px;
|
||
border-radius:11px; font-size:11px; font-weight:500; white-space:nowrap;
|
||
}
|
||
.badge.s { background:hsl(142 76% 90%); color:hsl(142 72% 29%); }
|
||
.badge.w { background:hsl(38 92% 90%); color:hsl(32 95% 30%); }
|
||
.badge.d { background:hsl(0 84% 93%); color:hsl(0 72% 45%); }
|
||
.badge.n { background:hsl(var(--secondary)); color:hsl(var(--secondary-foreground)); }
|
||
|
||
.tag {
|
||
display:inline-flex; align-items:center; height:22px; padding:0 8px;
|
||
border-radius:4px; font-size:11px; font-weight:500;
|
||
background:hsl(var(--secondary)); color:hsl(var(--secondary-foreground));
|
||
}
|
||
|
||
/* ---- page internals ---- */
|
||
.bc {
|
||
display:flex; align-items:center; justify-content:space-between;
|
||
padding:8px 28px; flex-shrink:0;
|
||
}
|
||
.bc-l { display:flex; align-items:center; gap:4px; font-size:12px; color:hsl(var(--muted-foreground)); }
|
||
.bc-l svg { width:13px; height:13px; color:hsl(var(--muted-foreground)); }
|
||
.bc-l .cur { color:hsl(var(--foreground)); font-weight:500; }
|
||
|
||
.ph {
|
||
display:flex; align-items:flex-start; justify-content:space-between;
|
||
gap:16px; padding:10px 28px 16px; flex-shrink:0;
|
||
}
|
||
.ph h2 { font-size:22px; font-weight:700; color:hsl(var(--foreground)); letter-spacing:-.02em; line-height:1.25; }
|
||
.ph .desc { font-size:12px; color:hsl(var(--muted-foreground)); margin-top:3px; }
|
||
.ph-actions { display:flex; gap:8px; align-items:center; flex-shrink:0; }
|
||
|
||
.pb { flex:1; overflow:hidden; display:flex; }
|
||
|
||
/* category tree */
|
||
.cat-panel {
|
||
width:228px; min-width:228px;
|
||
background:hsl(var(--background));
|
||
border-right:1px solid hsl(var(--border));
|
||
display:flex; flex-direction:column; overflow:hidden;
|
||
}
|
||
.cat-head { padding:12px 16px 6px; display:flex; align-items:center; justify-content:space-between; }
|
||
.cat-head h4 { font-size:12px; font-weight:600; }
|
||
.cat-head .n { font-size:11px; color:hsl(var(--muted-foreground)); }
|
||
.cat-search { padding:0 12px 8px; }
|
||
.cat-search input {
|
||
width:100%; height:28px; padding:0 8px; border:1px solid hsl(var(--input));
|
||
border-radius:calc(var(--radius) - 2px); font:12px/1 inherit; background:hsl(var(--background));
|
||
color:hsl(var(--foreground)); outline:none;
|
||
}
|
||
.cat-search input:focus { border-color:hsl(var(--ring)); box-shadow:0 0 0 2px hsla(var(--ring)/.15); }
|
||
.cat-list { flex:1; overflow-y:auto; padding:0 8px 10px; }
|
||
.cat-add { padding:8px 12px; border-top:1px solid hsl(var(--border)); }
|
||
.cat-add button {
|
||
width:100%; height:30px; border:0; border-radius:calc(var(--radius) - 2px);
|
||
background:transparent; color:hsl(var(--muted-foreground)); font:12px/1 inherit; cursor:pointer;
|
||
display:flex; align-items:center; justify-content:center; gap:4px;
|
||
}
|
||
.cat-add button:hover { background:hsl(var(--accent)); }
|
||
|
||
.c-node {
|
||
display:flex; align-items:center; gap:6px; width:100%; height:30px;
|
||
padding:0 8px; border:0; border-radius:calc(var(--radius) - 2px);
|
||
background:transparent; color:hsl(var(--muted-foreground)); font:12px/1 inherit;
|
||
cursor:pointer; text-align:left; white-space:nowrap; overflow:hidden;
|
||
transition:all .08s;
|
||
}
|
||
.c-node:hover { background:hsl(var(--accent)); color:hsl(var(--foreground)); }
|
||
.c-node.active { background:hsl(var(--accent)); color:hsl(var(--foreground)); font-weight:600; }
|
||
.c-node svg { width:13px; height:13px; flex-shrink:0; opacity:.5; }
|
||
.c-node.active svg { opacity:1; }
|
||
.c-node .c-label { flex:1; overflow:hidden; text-overflow:ellipsis; }
|
||
.c-node .c-n { font-size:10px; font-weight:600; color:hsl(var(--muted-foreground)); flex-shrink:0; }
|
||
.c-node .c-arrow { font-size:9px; opacity:.4; flex-shrink:0; width:10px; text-align:center; }
|
||
.c-node.child { padding-left:26px; }
|
||
|
||
.cp { flex:1; min-width:0; overflow-y:auto; padding:20px 28px; }
|
||
|
||
/* stats */
|
||
.stats { display:grid; grid-template-columns:repeat(4,1fr); gap:14px; margin-bottom:20px; }
|
||
.stat {
|
||
background:hsl(var(--card)); border:1px solid hsl(var(--border));
|
||
border-radius:var(--radius); padding:18px 20px;
|
||
display:flex; align-items:flex-start; gap:14px;
|
||
}
|
||
.stat-icon {
|
||
width:40px; height:40px; border-radius:calc(var(--radius) - 2px);
|
||
display:flex; align-items:center; justify-content:center; flex-shrink:0;
|
||
}
|
||
.stat-icon .s1 { background:hsl(var(--primary)/.1); }
|
||
.stat-icon svg { width:19px; height:19px; }
|
||
.stat .val { font-size:22px; font-weight:700; letter-spacing:-.01em; line-height:1.2; }
|
||
.stat .lbl { font-size:11px; color:hsl(var(--muted-foreground)); margin-top:1px; }
|
||
|
||
/* card */
|
||
.card {
|
||
background:hsl(var(--card)); border:1px solid hsl(var(--border));
|
||
border-radius:var(--radius); margin-bottom:16px; overflow:hidden;
|
||
}
|
||
.card-hd {
|
||
display:flex; align-items:center; justify-content:space-between;
|
||
padding:12px 18px; gap:16px;
|
||
border-bottom:1px solid hsl(var(--border));
|
||
}
|
||
.card-hd h3 { font-size:13px; font-weight:600; }
|
||
.card-hd .sub { font-size:12px; color:hsl(var(--muted-foreground)); font-weight:400; }
|
||
.card-bd { padding:18px; }
|
||
.card-bd.np { padding:0; }
|
||
|
||
/* filter bar */
|
||
.filter-bar {
|
||
display:flex; flex-wrap:wrap; gap:10px; align-items:flex-end;
|
||
padding:12px 18px; border-bottom:1px solid hsl(var(--border));
|
||
}
|
||
.filter-item { display:flex; flex-direction:column; gap:2px; }
|
||
.filter-item label { font-size:11px; color:hsl(var(--muted-foreground)); font-weight:500; }
|
||
.filter-item input, .filter-item select {
|
||
height:30px; padding:0 8px; width:160px;
|
||
border:1px solid hsl(var(--input)); border-radius:calc(var(--radius) - 2px);
|
||
font:12px/1 inherit; color:hsl(var(--foreground)); outline:none; background:hsl(var(--background));
|
||
}
|
||
.filter-item input:focus, .filter-item select:focus {
|
||
border-color:hsl(var(--ring)); box-shadow:0 0 0 2px hsla(var(--ring)/.15);
|
||
}
|
||
|
||
/* table */
|
||
.tbl { width:100%; border-collapse:collapse; }
|
||
.tbl thead th {
|
||
height:36px; padding:0 14px; text-align:left;
|
||
font-size:11px; font-weight:600; color:hsl(var(--muted-foreground));
|
||
background:hsl(var(--muted)); border-bottom:1px solid hsl(var(--border));
|
||
white-space:nowrap; letter-spacing:.01em;
|
||
}
|
||
.tbl tbody td {
|
||
height:40px; padding:0 14px; font-size:13px; border-bottom:1px solid hsl(var(--border));
|
||
}
|
||
.tbl tbody tr:last-child td { border-bottom:0; }
|
||
.tbl tbody tr:hover td { background:hsl(var(--accent)); }
|
||
.tbl td.dim { color:hsl(var(--muted-foreground)); }
|
||
|
||
.row-act { display:flex; gap:2px; justify-content:flex-end; }
|
||
.row-act button {
|
||
height:26px; padding:0 8px; border:0; border-radius:calc(var(--radius) - 2px);
|
||
background:transparent; color:hsl(var(--muted-foreground)); font:12px/1 inherit; cursor:pointer;
|
||
transition:all .08s;
|
||
}
|
||
.row-act button:hover { background:hsl(var(--accent)); color:hsl(var(--foreground)); }
|
||
.row-act button.del:hover { color:hsl(var(--destructive)); background:hsl(var(--destructive)/.1); }
|
||
|
||
/* pagination */
|
||
.pgn {
|
||
display:flex; align-items:center; justify-content:flex-end;
|
||
gap:4px; padding:10px 14px; font-size:12px; color:hsl(var(--muted-foreground));
|
||
}
|
||
.pgn button {
|
||
min-width:28px; height:28px; padding:0 6px;
|
||
border:1px solid hsl(var(--input)); border-radius:calc(var(--radius) - 2px);
|
||
background:hsl(var(--background)); color:hsl(var(--foreground)); cursor:pointer; font:12px/1 inherit;
|
||
}
|
||
.pgn button:hover { background:hsl(var(--accent)); }
|
||
.pgn button.active { background:hsl(var(--primary)); border-color:hsl(var(--primary)); color:hsl(var(--primary-foreground)); font-weight:600; }
|
||
.pgn button:disabled { opacity:.35; cursor:default; }
|
||
.pgn .info { margin:0 8px; }
|
||
|
||
/* form */
|
||
.f-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:14px 24px; }
|
||
.f-grid.c3 { grid-template-columns:repeat(3,1fr); }
|
||
.f-grid.c1 { grid-template-columns:1fr; }
|
||
.f-field { display:flex; flex-direction:column; gap:3px; }
|
||
.f-field.s2 { grid-column:span 2; }
|
||
.f-field.s3 { grid-column:span 3; }
|
||
.f-field label { font-size:12px; font-weight:500; color:hsl(var(--foreground)); display:flex; align-items:center; gap:3px; }
|
||
.f-field label .req { color:hsl(var(--destructive)); }
|
||
.f-field .hint { font-size:11px; color:hsl(var(--muted-foreground)); font-weight:400; }
|
||
.f-input {
|
||
width:100%; height:34px; padding:0 10px;
|
||
border:1px solid hsl(var(--input)); border-radius:calc(var(--radius) - 2px);
|
||
background:hsl(var(--background)); color:hsl(var(--foreground)); font:13px/1 inherit;
|
||
outline:none; transition:all .12s;
|
||
}
|
||
.f-input:focus { border-color:hsl(var(--ring)); box-shadow:0 0 0 2px hsla(var(--ring)/.15); }
|
||
.f-input.ro { background:hsl(var(--muted)); color:hsl(var(--muted-foreground)); }
|
||
select.f-input { appearance:none; padding-right:28px; background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%2371717a' fill='none' stroke-width='1.5'/%3E%3C/svg%3E"); background-repeat:no-repeat; background-position:right 10px center; }
|
||
textarea.f-input { height:64px; padding:7px 10px; resize:vertical; }
|
||
|
||
/* toggle */
|
||
.t-row { display:flex; align-items:center; gap:10px; height:34px; font-size:13px; }
|
||
.toggle { position:relative; width:34px; height:20px; flex-shrink:0; cursor:pointer; }
|
||
.toggle input { width:0; height:0; opacity:0; position:absolute; }
|
||
.toggle .track { position:absolute; inset:0; border-radius:10px; background:hsl(var(--input)); transition:.15s; }
|
||
.toggle .track::after {
|
||
content:""; position:absolute; top:2px; left:2px; width:16px; height:16px;
|
||
border-radius:50%; background:hsl(var(--background)); box-shadow:0 1px 2px rgba(0,0,0,.1); transition:.15s;
|
||
}
|
||
.toggle input:checked+.track { background:hsl(var(--primary)); }
|
||
.toggle input:checked+.track::after { transform:translateX(14px); }
|
||
|
||
/* callout */
|
||
.msg {
|
||
display:flex; gap:10px; padding:12px 14px; border-radius:var(--radius);
|
||
font-size:12px; line-height:1.55; margin-bottom:16px;
|
||
}
|
||
.msg.if { background:hsl(var(--primary)/.06); border:1px solid hsl(var(--primary)/.2); color:hsl(var(--primary)); }
|
||
.msg.wf { background:hsl(38 92% 95%); border:1px solid hsl(38 92% 80%); color:hsl(32 95% 30%); }
|
||
|
||
/* empty */
|
||
.empty { display:flex; flex-direction:column; align-items:center; justify-content:center; padding:60px; color:hsl(var(--muted-foreground)); gap:10px; }
|
||
.empty svg { width:40px; height:40px; opacity:.25; stroke:hsl(var(--muted-foreground)); }
|
||
|
||
/* footer */
|
||
.ft {
|
||
height:30px; min-height:30px;
|
||
border-top:1px solid hsl(var(--border));
|
||
display:flex; align-items:center; justify-content:center;
|
||
font-size:11px; color:hsl(var(--muted-foreground)); gap:16px; flex-shrink:0;
|
||
}
|
||
|
||
/* annotation toggle */
|
||
.anno-tg {
|
||
position:fixed; bottom:20px; right:20px; z-index:200;
|
||
width:36px; height:36px; border-radius:50%; border:1px solid hsl(var(--border));
|
||
background:hsl(var(--background)); color:hsl(var(--muted-foreground)); font-size:14px; cursor:pointer;
|
||
box-shadow:0 2px 10px rgba(0,0,0,.06); display:flex; align-items:center; justify-content:center;
|
||
transition:all .12s; font-weight:600;
|
||
}
|
||
.anno-tg:hover { border-color:hsl(var(--foreground)); color:hsl(var(--foreground)); }
|
||
.anno-tg.on { background:hsl(var(--primary)); color:hsl(var(--primary-foreground)); border-color:hsl(var(--primary)); }
|
||
|
||
.anno {
|
||
display:none; position:absolute; z-index:50; pointer-events:none;
|
||
background:hsl(var(--foreground)); color:hsl(var(--background));
|
||
font-size:11px; line-height:1.5; padding:8px 12px; border-radius:6px; max-width:200px;
|
||
}
|
||
.anno::after { content:""; position:absolute; border:6px solid transparent; }
|
||
.anno.t::after { top:100%; left:20px; border-top-color:hsl(var(--foreground)); }
|
||
.anno.b::after { bottom:100%; left:20px; border-bottom-color:hsl(var(--foreground)); }
|
||
.anno.r::after { right:100%; top:12px; border-right-color:hsl(var(--foreground)); }
|
||
.anno.l::after { left:100%; top:12px; border-left-color:hsl(var(--foreground)); }
|
||
|
||
body.show-a .anno { display:block; }
|
||
.annod { position:relative; }
|
||
.annod::before {
|
||
content:""; display:none; position:absolute; inset:-1px;
|
||
border:1.5px dashed hsl(var(--ring)); border-radius:inherit; pointer-events:none; z-index:1;
|
||
}
|
||
body.show-a .annod::before { display:block; }
|
||
|
||
@media (max-width:1024px) {
|
||
.stats { grid-template-columns:repeat(2,1fr); }
|
||
.f-grid { grid-template-columns:1fr; }
|
||
.f-grid.c3 { grid-template-columns:repeat(2,1fr); }
|
||
.f-field.s2,.f-field.s3 { grid-column:span 1; }
|
||
.cat-panel { width:190px; min-width:190px; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ==================== TOP BAR ==================== -->
|
||
<header class="topbar">
|
||
<div class="topbar-logo">
|
||
<div class="logo-sq">F</div>
|
||
<span class="logo-text">FMS 管理平台</span>
|
||
</div>
|
||
|
||
<div class="topbar-search">
|
||
<svg viewBox="0 0 15 15" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="6.5" cy="6.5" r="4"/><line x1="9.5" y1="9.5" x2="13" y2="13"/></svg>
|
||
<input type="search" placeholder="搜索模块、页面、功能...">
|
||
</div>
|
||
|
||
<div class="topbar-actions">
|
||
<button class="tb-btn" title="帮助">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6"/><path d="M6.5 6.7c0-.8.6-1.4 1.5-1.4s1.5.6 1.5 1.4c0 1-1.2 1.5-1.5 2.1"/><circle cx="8" cy="11.2" r=".6" fill="currentColor" stroke="none"/></svg>
|
||
</button>
|
||
<button class="tb-btn" title="通知">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6.5 13.5h3M8 3a3.5 3.5 0 013.5 3.5v2.5L13 10.5H3l1.5-1.5V6.5A3.5 3.5 0 018 3z"/></svg>
|
||
<span class="dot"></span>
|
||
</button>
|
||
<div style="position:relative">
|
||
<button class="user-btn" id="userBtn">
|
||
<div class="av">A</div>
|
||
<span class="nm">管理员</span>
|
||
<span class="cr">▼</span>
|
||
</button>
|
||
<div class="user-dd" id="userDrop">
|
||
<button>个人中心</button>
|
||
<button>系统设置</button>
|
||
<hr>
|
||
<button class="dng">退出登录</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- ==================== SIDEBAR ==================== -->
|
||
<aside class="sidebar" id="sidebar">
|
||
<nav class="sidebar-nav" id="sideNav">
|
||
<button class="s-item active" data-page="dashboard">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="1" width="5.5" height="6" rx="1"/><rect x="9.5" y="1" width="5.5" height="6" rx="1"/><rect x="1" y="9" width="5.5" height="6" rx="1"/><rect x="9.5" y="9" width="5.5" height="6" rx="1"/></svg>
|
||
<span class="s-label">工作台</span>
|
||
</button>
|
||
|
||
<div class="s-group">数据管理</div>
|
||
|
||
<button class="s-item expanded" data-sub="base">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1.5" y="2" width="13" height="12" rx="1.5"/><line x1="1.5" y1="6" x2="14.5" y2="6"/><line x1="5" y1="2" x2="5" y2="14"/></svg>
|
||
<span class="s-label">基础数据</span>
|
||
<span class="s-caret">▶</span>
|
||
</button>
|
||
<div class="s-sub open" id="sub-base">
|
||
<button class="s-item active" data-page="partners">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6"/><circle cx="8" cy="6.5" r="1.8"/><path d="M3.5 14c1.5-2.5 3-3.5 4.5-3.5s3 1 4.5 3.5"/></svg>
|
||
<span class="s-label">合作伙伴</span>
|
||
</button>
|
||
<button class="s-item" data-page="products">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="4" width="12" height="9" rx="1"/><line x1="2" y1="6.5" x2="14" y2="6.5"/><line x1="8" y1="6.5" x2="8" y2="13"/></svg>
|
||
<span class="s-label">产品管理</span>
|
||
</button>
|
||
<button class="s-item" data-page="contacts">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="3" width="14" height="10" rx="1.5"/><line x1="1" y1="7.5" x2="15" y2="7.5"/></svg>
|
||
<span class="s-label">联系人</span>
|
||
</button>
|
||
</div>
|
||
|
||
<button class="s-item expanded" data-sub="biz">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="2" width="14" height="12" rx="1.5"/><path d="M5 2v12M8 6.5h4.5M8 9.5h3.5"/></svg>
|
||
<span class="s-label">业务管理</span>
|
||
<span class="s-caret">▶</span>
|
||
</button>
|
||
<div class="s-sub open" id="sub-biz">
|
||
<button class="s-item" data-page="shipment">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="5" width="12" height="8" rx="1.5"/><circle cx="4.5" cy="13" r="1.2"/><circle cx="11.5" cy="13" r="1.2"/></svg>
|
||
<span class="s-label">出运单</span>
|
||
</button>
|
||
<button class="s-item" data-page="container">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="1.5" width="10" height="13" rx="1"/><line x1="3" y1="5.5" x2="13" y2="5.5"/><line x1="3" y1="10.5" x2="13" y2="10.5"/></svg>
|
||
<span class="s-label">集装箱</span>
|
||
</button>
|
||
<button class="s-item" data-page="customs">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="5" width="10" height="7" rx="1"/><line x1="3" y1="7.5" x2="13" y2="7.5"/><line x1="6" y1="7.5" x2="6" y2="12"/><line x1="9" y1="7.5" x2="9" y2="12"/></svg>
|
||
<span class="s-label">报关管理</span>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="s-group">系统管理</div>
|
||
|
||
<button class="s-item" data-page="modules">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2.5" y="2.5" width="11" height="11" rx="2"/><circle cx="8" cy="8" r="2"/></svg>
|
||
<span class="s-label">模块管理</span>
|
||
</button>
|
||
<button class="s-item" data-page="users">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="5" r="2.5"/><path d="M2.5 14c1.8-3 3.5-4.5 5.5-4.5s3.7 1.5 5.5 4.5"/></svg>
|
||
<span class="s-label">用户管理</span>
|
||
</button>
|
||
<button class="s-item" data-page="roles">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="2" width="10" height="12" rx="1.5"/><circle cx="8" cy="6.5" r="1.8"/><path d="M5 11.5c1-1.5 1.8-2 3-2s2 .5 3 2"/></svg>
|
||
<span class="s-label">角色权限</span>
|
||
</button>
|
||
<button class="s-item" data-page="i18n">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6"/><ellipse cx="8" cy="8" rx="2.5" ry="6"/><line x1="2" y1="8" x2="14" y2="8"/></svg>
|
||
<span class="s-label">多语言</span>
|
||
</button>
|
||
<button class="s-item" data-page="logs">
|
||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="2" width="12" height="12" rx="1.5"/><line x1="5.5" y1="5.5" x2="11.5" y2="5.5"/><line x1="5.5" y1="8.5" x2="10" y2="8.5"/><line x1="5.5" y1="11.5" x2="8.5" y2="11.5"/></svg>
|
||
<span class="s-label">操作日志</span>
|
||
</button>
|
||
</nav>
|
||
|
||
<div class="sidebar-footer">
|
||
<button class="s-cc-btn" id="collapseBtn">
|
||
<svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><polyline points="9,2 4,7 9,12"/></svg>
|
||
<span>收起菜单</span>
|
||
</button>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- ==================== MAIN ==================== -->
|
||
<div class="main" id="mainArea">
|
||
<div class="tabbar" id="tabBar">
|
||
<button class="tab-item active" data-page="dashboard">
|
||
<span class="ti">⌂</span> 工作台 <span class="tab-close" data-page="dashboard">×</span>
|
||
</button>
|
||
<button class="tab-item" data-page="partners">
|
||
<span class="ti">◉</span> 合作伙伴 <span class="tab-close" data-page="partners">×</span>
|
||
</button>
|
||
<div class="tab-actions"><button title="Close all">▼</button></div>
|
||
</div>
|
||
|
||
<div class="content-wrap" id="contentWrap">
|
||
|
||
<!-- ======== DASHBOARD ======== -->
|
||
<div class="page active" id="page-dashboard">
|
||
<div class="bc">
|
||
<div class="bc-l"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="1" width="5" height="5" rx="1"/><rect x="8" y="1" width="5" height="5" rx="1"/><rect x="1" y="8" width="5" height="5" rx="1"/><rect x="8" y="8" width="5" height="5" rx="1"/></svg> <span>/</span> <span class="cur">工作台</span></div>
|
||
<div><span style="font-size:11px;color:hsl(var(--muted-foreground))">最后登录: Aug 8, 2026 · 08:30</span></div>
|
||
</div>
|
||
<div style="flex:1;overflow-y:auto;padding:20px 28px">
|
||
|
||
<div class="stats annod"
|
||
data-anno="Stats row — shadcn/ui cards with subtle borders, not floating shadows."
|
||
data-anno-pos="b">
|
||
<div class="stat"><div class="stat-icon"><svg viewBox="0 0 20 20" fill="none" stroke="hsl(var(--primary))" stroke-width="1.5"><rect x="2" y="2.5" width="16" height="15" rx="2"/><line x1="7" y1="8" x2="13" y2="8"/><line x1="7" y1="12" x2="10" y2="12"/></svg></div><div><div class="val">1,286</div><div class="lbl">模块总数</div></div></div>
|
||
<div class="stat"><div class="stat-icon"><svg viewBox="0 0 20 20" fill="none" stroke="hsl(142 72% 29%)" stroke-width="1.5"><polyline points="4,12 8,8 12,11 16,5"/><polyline points="13,5 16,5 16,8"/></svg></div><div><div class="val">1,158</div><div class="lbl">已启用</div></div></div>
|
||
<div class="stat"><div class="stat-icon"><svg viewBox="0 0 20 20" fill="none" stroke="hsl(32 95% 30%)" stroke-width="1.5"><rect x="3" y="4" width="14" height="12" rx="1.5"/><line x1="3" y1="8.5" x2="17" y2="8.5"/></svg></div><div><div class="val">89</div><div class="lbl">草稿</div></div></div>
|
||
<div class="stat"><div class="stat-icon"><svg viewBox="0 0 20 20" fill="none" stroke="hsl(var(--destructive))" stroke-width="1.5"><circle cx="10" cy="10" r="7"/><line x1="10" y1="6.5" x2="10" y2="10.5"/><circle cx="10" cy="13.2" r=".6" fill="hsl(var(--destructive))" stroke="none"/></svg></div><div><div class="val">39</div><div class="lbl">异常</div></div></div>
|
||
</div>
|
||
|
||
<div class="card annod"
|
||
data-anno="shadcn card: always has a border (`border`). 否t borderless. The border is subtle — hsl(214 32% 91%)."
|
||
data-anno-pos="t">
|
||
<div class="card-hd"><h3>最近更新 <span class="sub">· 最近7天</span></h3><button class="btn sm g">查看全部 →</button></div>
|
||
<div class="card-bd np">
|
||
<table class="tbl">
|
||
<thead><tr><th>模块名称</th><th>编码</th><th>类型</th><th>状态</th><th>更新时间</th><th style="width:40px"></th></tr></thead>
|
||
<tbody>
|
||
<tr><td>合作伙伴</td><td class="dim">v_b_othercompany</td><td class="dim">数据模块</td><td><span class="badge s">已启用</span></td><td class="dim">Aug 8, 15:32</td><td class="row-act"><button>编辑</button></td></tr>
|
||
<tr><td>出运单</td><td class="dim">d_shipment</td><td class="dim">数据模块</td><td><span class="badge s">已启用</span></td><td class="dim">Aug 8, 11:15</td><td class="row-act"><button>编辑</button></td></tr>
|
||
<tr><td>用户管理</td><td class="dim">b_user</td><td class="dim">页面</td><td><span class="badge s">已启用</span></td><td class="dim">Aug 7, 16:48</td><td class="row-act"><button>编辑</button></td></tr>
|
||
<tr><td>集装箱</td><td class="dim">d_container</td><td class="dim">数据模块</td><td><span class="badge w">草稿</span></td><td class="dim">Aug 7, 09:20</td><td class="row-act"><button>编辑</button></td></tr>
|
||
<tr><td>多语言</td><td class="dim">b_i18n</td><td class="dim">页面</td><td><span class="badge s">已启用</span></td><td class="dim">Aug 6, 14:05</td><td class="row-act"><button>编辑</button></td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="ft"><span>FMS 管理平台 v3.2</span><span style="color:hsl(var(--border))">|</span><span>© 2026 FMS</span></div>
|
||
</div>
|
||
|
||
<!-- ======== PARTNERS ======== -->
|
||
<div class="page" id="page-partners">
|
||
<div class="bc">
|
||
<div class="bc-l"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="1" width="5" height="5" rx="1"/><rect x="8" y="1" width="5" height="5" rx="1"/><rect x="1" y="8" width="5" height="5" rx="1"/><rect x="8" y="8" width="5" height="5" rx="1"/></svg> <span>/</span> <span>数据管理</span> <span>/</span> <span>基础数据</span> <span>/</span> <span class="cur">合作伙伴</span></div>
|
||
<div><button class="btn sm g">刷新</button></div>
|
||
</div>
|
||
<div class="ph annod"
|
||
data-anno="shadcn page header: clean, minimal. Title uses Inter font — very geometric."
|
||
data-anno-pos="b">
|
||
<div><h2>合作伙伴</h2><div class="desc">管理所有合作伙伴数据与分类</div></div>
|
||
<div class="ph-actions"><button class="btn sm">导入</button><button class="btn sm">导出</button><button class="btn sm p">+ 新建</button></div>
|
||
</div>
|
||
|
||
<div class="pb">
|
||
<div class="cat-panel">
|
||
<div class="cat-head"><h4>分类</h4><span class="n">8 个分类</span></div>
|
||
<div class="cat-search"><input type="search" placeholder="搜索..."></div>
|
||
<div class="cat-list">
|
||
<button class="c-node active"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1.5" y="2" width="11" height="10" rx="1.5"/></svg><span class="c-label">全部 partners</span><span class="c-n">128</span></button>
|
||
<button class="c-node child"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="1.5" width="10" height="11" rx="1"/></svg><span class="c-label">客户s</span><span class="c-n">56</span></button>
|
||
<button class="c-node child"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="1.5" width="10" height="11" rx="1"/></svg><span class="c-label">供应商s</span><span class="c-n">42</span></button>
|
||
<button class="c-node child"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="1.5" width="10" height="11" rx="1"/></svg><span class="c-label">代理商s</span><span class="c-n">18</span></button>
|
||
<button class="c-node"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1.5" y="2" width="11" height="10" rx="1.5"/></svg><span class="c-label">承运商</span><span class="c-n">9</span></button>
|
||
<button class="c-node"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1.5" y="2" width="11" height="10" rx="1.5"/></svg><span class="c-label">报关行</span><span class="c-n">3</span></button>
|
||
</div>
|
||
<div class="cat-add"><button>+ 新建分类</button></div>
|
||
</div>
|
||
|
||
<div class="cp">
|
||
<div class="card">
|
||
<div class="filter-bar">
|
||
<div class="filter-item"><label>名称</label><input placeholder="查询 by name"></div>
|
||
<div class="filter-item"><label>类型</label><select><option>全部</option><option>客户</option><option>供应商</option></select></div>
|
||
<div class="filter-item"><label>状态</label><select><option>全部</option><option>已启用</option><option>停用</option></select></div>
|
||
<button class="btn sm p">查询</button>
|
||
<button class="btn sm">重置</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card annod"
|
||
data-anno="shadcn table: bordered between rows, header uses `--muted` background. Row hover = `--accent`."
|
||
data-anno-pos="t">
|
||
<div class="card-hd">
|
||
<h3>合作伙伴列表 <span class="sub">· 128 条记录</span></h3>
|
||
<div style="display:flex;gap:8px"><button class="btn sm">批量删除</button><button class="btn sm">导出 selected</button></div>
|
||
</div>
|
||
<div class="card-bd np">
|
||
<table class="tbl">
|
||
<thead><tr><th style="width:36px"><input type="checkbox"></th><th>名称</th><th>简称</th><th>类型</th><th>联系人</th><th>电话</th><th>状态</th><th style="width:100px"></th></tr></thead>
|
||
<tbody>
|
||
<tr><td><input type="checkbox"></td><td>Shanghai 远洋运输 Co.</td><td class="dim">远洋运输</td><td><span class="tag">客户</span></td><td>Zhang Wei</td><td class="dim">021-5832xxxx</td><td><span class="badge s">已启用</span></td><td class="row-act"><button>编辑</button><button>查看</button><button class="del">删除</button></td></tr>
|
||
<tr><td><input type="checkbox"></td><td>Shenzhen 港航物流 Group</td><td class="dim">港航物流</td><td><span class="tag">供应商</span></td><td>Li Na</td><td class="dim">0755-2689xxxx</td><td><span class="badge s">已启用</span></td><td class="row-act"><button>编辑</button><button>查看</button><button class="del">删除</button></td></tr>
|
||
<tr><td><input type="checkbox"></td><td>Guangzhou 通达 Customs</td><td class="dim">通达</td><td><span class="tag">代理商</span></td><td>Wang Qiang</td><td class="dim">020-8765xxxx</td><td><span class="badge s">已启用</span></td><td class="row-act"><button>编辑</button><button>查看</button><button class="del">删除</button></td></tr>
|
||
<tr><td><input type="checkbox"></td><td>宁波海运 Corp.</td><td class="dim">宁波海运</td><td><span class="tag">客户</span></td><td>Zhao Min</td><td class="dim">0574-8723xxxx</td><td><span class="badge w">停用</span></td><td class="row-act"><button>编辑</button><button>查看</button><button class="del">删除</button></td></tr>
|
||
<tr><td><input type="checkbox"></td><td>Tianjin 港航物流 Dept.</td><td class="dim">天津港</td><td><span class="tag">供应商</span></td><td>Liu Yang</td><td class="dim">022-2570xxxx</td><td><span class="badge s">已启用</span></td><td class="row-act"><button>编辑</button><button>查看</button><button class="del">删除</button></td></tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="pgn">
|
||
<span class="info">128 条</span>
|
||
<button disabled>‹</button><button class="active">1</button><button>2</button><button>3</button><button>…</button><button>13</button><button>›</button>
|
||
<select style="height:28px;border:1px solid hsl(var(--input));border-radius:calc(var(--radius) - 2px);font-size:12px;margin-left:8px">
|
||
<option>10 条/页</option><option>20 条/页</option><option>50 条/页</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="ft"><span>FMS 管理平台 v3.2</span><span style="color:hsl(var(--border))">|</span><span>© 2026 FMS</span></div>
|
||
</div>
|
||
|
||
<!-- ======== MODULE CONFIG ======== -->
|
||
<div class="page" id="page-modules">
|
||
<div class="bc"><div class="bc-l"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="1" width="5" height="5" rx="1"/><rect x="8" y="1" width="5" height="5" rx="1"/><rect x="1" y="8" width="5" height="5" rx="1"/><rect x="8" y="8" width="5" height="5" rx="1"/></svg> <span>/</span> <span>系统管理</span> <span>/</span> <span class="cur">模块管理</span></div></div>
|
||
<div class="ph"><div><h2>模块管理uration</h2><div class="desc">定义模块、字段与访问控制</div></div><div class="ph-actions"><button class="btn p">+ 新建模块</button></div></div>
|
||
|
||
<div class="pb">
|
||
<div class="cat-panel">
|
||
<div class="cat-head"><h4>模块名称 groups</h4><span class="n">5 个分组</span></div>
|
||
<div class="cat-search"><input type="search" placeholder="搜索..."></div>
|
||
<div class="cat-list">
|
||
<button class="c-node active"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1.5" y="2" width="11" height="10" rx="1.5"/></svg><span class="c-label">全部 modules</span><span class="c-n">42</span></button>
|
||
<button class="c-node child"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="1.5" width="10" height="11" rx="1"/></svg><span class="c-label">基础数据</span><span class="c-n">12</span></button>
|
||
<button class="c-node child"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="1.5" width="10" height="11" rx="1"/></svg><span class="c-label">业务管理</span><span class="c-n">18</span></button>
|
||
<button class="c-node child"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="1.5" width="10" height="11" rx="1"/></svg><span class="c-label">系统管理</span><span class="c-n">8</span></button>
|
||
<button class="c-node child"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="1.5" width="10" height="11" rx="1"/></svg><span class="c-label">报表</span><span class="c-n">4</span></button>
|
||
</div>
|
||
<div class="cat-add"><button>+ 新建分组</button></div>
|
||
</div>
|
||
|
||
<div class="cp">
|
||
<div class="card">
|
||
<div class="card-hd">
|
||
<h3>编辑: 合作伙伴 <span class="sub">· v_b_othercompany</span></h3>
|
||
<div style="display:flex;gap:8px"><button class="btn sm d">删除</button><button class="btn sm p">保存</button></div>
|
||
</div>
|
||
<div class="card-bd">
|
||
<div class="f-grid c3">
|
||
<div class="f-field"><label>模块名称 name <span class="req">*</span></label><input class="f-input" value="合作伙伴"></div>
|
||
<div class="f-field"><label>模块名称 code <span class="req">*</span></label><input class="f-input" value="v_b_othercompany"></div>
|
||
<div class="f-field"><label>类型</label><select class="f-input"><option selected>数据模块</option><option>页面</option><option>Folder</option></select></div>
|
||
<div class="f-field"><label>上级模块</label><input class="f-input ro" value="基础数据" readonly></div>
|
||
<div class="f-field"><label>查看 name</label><input class="f-input" value="v_b_othercompany"></div>
|
||
<div class="f-field"><label>保存 table</label><input class="f-input" value="b_othercompany"></div>
|
||
</div>
|
||
<div style="display:flex;gap:28px;margin-top:16px">
|
||
<div class="t-row"><label class="toggle"><input type="checkbox" checked><span class="track"></span></label> 已启用</div>
|
||
<div class="t-row"><label class="toggle"><input type="checkbox" checked><span class="track"></span></label> 在菜单显示</div>
|
||
<div class="t-row"><label class="toggle"><input type="checkbox"><span class="track"></span></label> 全部ow export</div>
|
||
<div class="t-row"><label class="toggle"><input type="checkbox"><span class="track"></span></label> 启用审计</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-hd"><h3>字段定义 <span class="sub">· 12 个字段</span></h3><button class="btn sm p">+ 添加字段</button></div>
|
||
<div class="card-bd np">
|
||
<table class="tbl">
|
||
<thead><tr><th>字段名</th><th>显示名称</th><th>类型</th><th>宽度</th><th>必填</th><th>状态</th><th style="width:40px"></th></tr></thead>
|
||
<tbody>
|
||
<tr><td><code style="font-size:12px">b_id</code></td><td>ID</td><td class="dim">数字</td><td class="dim">120</td><td>是</td><td><span class="badge s">已启用</span></td><td class="row-act"><button>编辑</button></td></tr>
|
||
<tr><td><code style="font-size:12px">b_name</code></td><td>名称</td><td class="dim">文本</td><td class="dim">200</td><td>是</td><td><span class="badge s">已启用</span></td><td class="row-act"><button>编辑</button></td></tr>
|
||
<tr><td><code style="font-size:12px">b_shortname</code></td><td>简称</td><td class="dim">文本</td><td class="dim">140</td><td>否</td><td><span class="badge s">已启用</span></td><td class="row-act"><button>编辑</button></td></tr>
|
||
<tr><td><code style="font-size:12px">b_type</code></td><td>类型</td><td class="dim">选择</td><td class="dim">150</td><td>否</td><td><span class="badge s">已启用</span></td><td class="row-act"><button>编辑</button></td></tr>
|
||
<tr><td><code style="font-size:12px">b_tel</code></td><td>电话</td><td class="dim">文本</td><td class="dim">130</td><td>否</td><td><span class="badge n">停用</span></td><td class="row-act"><button>编辑</button></td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="ft"><span>FMS 管理平台 v3.2</span><span style="color:hsl(var(--border))">|</span><span>© 2026 FMS</span></div>
|
||
</div>
|
||
|
||
<!-- ======== placeholder pages ======== -->
|
||
<div class="page" id="page-products"><div class="bc"><div class="bc-l"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="1" width="5" height="5" rx="1"/><rect x="8" y="1" width="5" height="5" rx="1"/><rect x="1" y="8" width="5" height="5" rx="1"/><rect x="8" y="8" width="5" height="5" rx="1"/></svg> <span>/</span> <span>数据管理</span> <span>/</span> <span class="cur">产品管理</span></div></div><div class="empty"><svg viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="6" y="10" width="28" height="22" rx="3"/><rect x="14" y="3" width="12" height="5" rx="1.5"/></svg><div style="font-weight:600;font-size:14px">产品管理</div><div style="font-size:12px">产品管理页面</div></div></div>
|
||
<div class="page" id="page-contacts"><div class="bc"><div class="bc-l"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="1" width="5" height="5" rx="1"/><rect x="8" y="1" width="5" height="5" rx="1"/><rect x="1" y="8" width="5" height="5" rx="1"/><rect x="8" y="8" width="5" height="5" rx="1"/></svg> <span>/</span> <span>数据管理</span> <span>/</span> <span class="cur">联系人</span></div></div><div class="empty"><svg viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="8" width="34" height="26" rx="3"/><line x1="3" y1="18" x2="37" y2="18"/></svg><div style="font-weight:600;font-size:14px">联系人</div><div style="font-size:12px">联系人s management page</div></div></div>
|
||
<div class="page" id="page-shipment"><div class="bc"><div class="bc-l"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="1" width="5" height="5" rx="1"/><rect x="8" y="1" width="5" height="5" rx="1"/><rect x="1" y="8" width="5" height="5" rx="1"/><rect x="8" y="8" width="5" height="5" rx="1"/></svg> <span>/</span> <span>业务管理</span> <span>/</span> <span class="cur">出运单</span></div></div><div class="empty"><svg viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="5" y="10" width="30" height="18" rx="2"/><circle cx="10" cy="28" r="3"/><circle cx="30" cy="28" r="3"/></svg><div style="font-weight:600;font-size:14px">出运单</div><div style="font-size:12px">出运单管理页面</div></div></div>
|
||
<div class="page" id="page-container"><div class="bc"><div class="bc-l"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="1" width="5" height="5" rx="1"/><rect x="8" y="1" width="5" height="5" rx="1"/><rect x="1" y="8" width="5" height="5" rx="1"/><rect x="8" y="8" width="5" height="5" rx="1"/></svg> <span>/</span> <span>业务管理</span> <span>/</span> <span class="cur">集装箱</span></div></div><div class="empty"><svg viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="8" y="5" width="24" height="30" rx="2"/><line x1="8" y1="15" x2="32" y2="15"/><line x1="8" y1="25" x2="32" y2="25"/></svg><div style="font-weight:600;font-size:14px">集装箱</div><div style="font-size:12px">集装箱管理页面</div></div></div>
|
||
<div class="page" id="page-customs"><div class="bc"><div class="bc-l"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="1" width="5" height="5" rx="1"/><rect x="8" y="1" width="5" height="5" rx="1"/><rect x="1" y="8" width="5" height="5" rx="1"/><rect x="8" y="8" width="5" height="5" rx="1"/></svg> <span>/</span> <span>业务管理</span> <span>/</span> <span class="cur">报关管理</span></div></div><div class="empty"><svg viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="8" y="12" width="24" height="18" rx="2"/><line x1="8" y1="18" x2="32" y2="18"/><line x1="14" y1="18" x2="14" y2="30"/><line x1="20" y1="18" x2="20" y2="30"/></svg><div style="font-weight:600;font-size:14px">报关管理</div><div style="font-size:12px">报关管理页面</div></div></div>
|
||
<div class="page" id="page-users"><div class="bc"><div class="bc-l"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="1" width="5" height="5" rx="1"/><rect x="8" y="1" width="5" height="5" rx="1"/><rect x="1" y="8" width="5" height="5" rx="1"/><rect x="8" y="8" width="5" height="5" rx="1"/></svg> <span>/</span> <span>系统管理</span> <span>/</span> <span class="cur">用户管理</span></div></div><div class="empty"><svg viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="20" cy="14" r="6"/><path d="M6 34c4-6 8-10 14-10s10 4 14 10"/></svg><div style="font-weight:600;font-size:14px">用户管理</div><div style="font-size:12px">用户管理页面</div></div></div>
|
||
<div class="page" id="page-roles"><div class="bc"><div class="bc-l"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="1" width="5" height="5" rx="1"/><rect x="8" y="1" width="5" height="5" rx="1"/><rect x="1" y="8" width="5" height="5" rx="1"/><rect x="8" y="8" width="5" height="5" rx="1"/></svg> <span>/</span> <span>系统管理</span> <span>/</span> <span class="cur">角色权限</span></div></div><div class="empty"><svg viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="8" y="6" width="24" height="28" rx="3"/><circle cx="20" cy="17" r="4"/><path d="M13 28c2-3 4-5 7-5s5 2 7 5"/></svg><div style="font-weight:600;font-size:14px">角色权限 & Permissions</div><div style="font-size:12px">角色管理页面</div></div></div>
|
||
<div class="page" id="page-i18n"><div class="bc"><div class="bc-l"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="1" width="5" height="5" rx="1"/><rect x="8" y="1" width="5" height="5" rx="1"/><rect x="1" y="8" width="5" height="5" rx="1"/><rect x="8" y="8" width="5" height="5" rx="1"/></svg> <span>/</span> <span>系统管理</span> <span>/</span> <span class="cur">多语言</span></div></div><div class="empty"><svg viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="20" cy="20" r="15"/><ellipse cx="20" cy="20" rx="7" ry="15"/><line x1="5" y1="20" x2="35" y2="20"/></svg><div style="font-weight:600;font-size:14px">多语言</div><div style="font-size:12px">多语言管理页面</div></div></div>
|
||
<div class="page" id="page-logs"><div class="bc"><div class="bc-l"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="1" width="5" height="5" rx="1"/><rect x="8" y="1" width="5" height="5" rx="1"/><rect x="1" y="8" width="5" height="5" rx="1"/><rect x="8" y="8" width="5" height="5" rx="1"/></svg> <span>/</span> <span>系统管理</span> <span>/</span> <span class="cur">操作日志</span></div></div><div class="empty"><svg viewBox="0 0 40 40" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="6" y="4" width="28" height="32" rx="3"/><line x1="14" y1="13" x2="28" y2="13"/><line x1="14" y1="20" x2="24" y2="20"/><line x1="14" y1="27" x2="20" y2="27"/></svg><div style="font-weight:600;font-size:14px">操作日志</div><div style="font-size:12px">系统管理 audit log</div></div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- annotation toggle -->
|
||
<button class="anno-tg" id="annoToggle">?</button>
|
||
|
||
<script>
|
||
const S={tabs:['dashboard','partners'],active:'dashboard',collapsed:false};
|
||
const $=s=>document.query选择or(s),$$=s=>document.query选择or全部(s);
|
||
function switchTab(id){
|
||
$$('.tab-item').forEach(t=>t.classList.remove('active'));
|
||
const tb=$(`.tab-item[data-page="${id}"]`); if(tb)tb.classList.add('active');
|
||
$$('.page').forEach(p=>p.classList.remove('active'));
|
||
const pg=$(`#page-${id}`); if(pg)pg.classList.add('active');
|
||
$$('.s-item').forEach(m=>m.classList.remove('active'));
|
||
const sm=$(`.s-item[data-page="${id}"]`); if(sm){sm.classList.add('active');const sub=sm.closest('.s-sub');if(sub&&!sub.classList.contains('open')){sub.classList.add('open');const pm=$(`[data-sub="${sub.id.replace('sub-','')}"]`);if(pm)pm.classList.add('expanded');}}
|
||
S.active=id;
|
||
}
|
||
function openTab(id){
|
||
if(S.tabs.includes(id)){switchTab(id);return;}
|
||
S.tabs.push(id);
|
||
const lbs={dashboard:'⌂ 工作台',partners:'◉ 合作伙伴',products:'◫ 产品管理',contacts:'◳ 联系人s',shipment:'◷ 出运单',container:'◫ 集装箱',customs:'▦ 报关管理',modules:'○ 模块管理',users:'◉ 用户管理',roles:'◈ 角色权限',i18n:'◎ 多语言',logs:'≡ 操作日志'};
|
||
const lb=lbs[id]||id;
|
||
const tb=document.createElement('button'); tb.class名称='tab-item'; tb.dataset.page=id;
|
||
tb.innerHTML=`<span class="ti">${lb[0]}</span> ${lb.slice(2)} <span class="tab-close" data-page="${id}">×</span>`;
|
||
tb.addEventListener('click',e=>{if(!e.target.classList.contains('tab-close'))switchTab(id);});
|
||
$('#tabBar').insertBefore(tb,$('.tab-actions')); switchTab(id);
|
||
}
|
||
function closeTab(id){
|
||
if(S.tabs.length<=1)return; const i=S.tabs.indexOf(id); if(i===-1)return;
|
||
S.tabs.splice(i,1); $(`.tab-item[data-page="${id}"]`).remove();
|
||
if(S.active===id)switchTab(S.tabs[Math.min(i,S.tabs.length-1)]);
|
||
}
|
||
$('#tabBar').addEventListener('click',e=>{if(e.target.classList.contains('tab-close')){e.stopPropagation();closeTab(e.target.dataset.page);}});
|
||
$('#sideNav').addEventListener('click',e=>{
|
||
const it=e.target.closest('.s-item'); if(!it)return;
|
||
const pid=it.dataset.page,sid=it.dataset.sub;
|
||
if(sid){const s=$(`#sub-${sid}`);s.classList.toggle('open');it.classList.toggle('expanded');return;}
|
||
if(pid)openTab(pid);
|
||
});
|
||
$('#collapseBtn').addEventListener('click',()=>{
|
||
document.body.classList.toggle('s-collapsed'); S.collapsed=document.body.classList.contains('s-collapsed');
|
||
const b=$('#collapseBtn'),sv=b.query选择or('svg');
|
||
if(S.collapsed){sv.style.transform='rotate(180deg)';b.query选择or('span:last-child').textContent='';}
|
||
else{sv.style.transform='';b.query选择or('span:last-child').textContent='收起菜单';}
|
||
});
|
||
$('#userBtn').addEventListener('click',e=>{e.stopPropagation();$('#userDrop').classList.toggle('open');});
|
||
document.addEventListener('click',e=>{if(!e.target.closest('#userBtn'))$('#userDrop').classList.remove('open');});
|
||
$$('.c-node').forEach(n=>n.addEventListener('click',()=>{
|
||
const a=n.query选择or('.c-arrow'); if(a){a.textContent=a.textContent.trim()==='▾'?'▸':'▾';let nx=n.nextElementSibling;while(nx&&nx.classList.contains('child')){nx.style.display=nx.style.display==='none'?'':'none';nx=nx.nextElementSibling;}}
|
||
$$('.c-node').forEach(x=>x.classList.remove('active'));n.classList.add('active');
|
||
}));
|
||
$('#annoToggle').addEventListener('click',()=>{document.body.classList.toggle('show-a');$('#annoToggle').classList.toggle('on');});
|
||
console.log('shadcn/ui style admin demo ready.');
|
||
</script>
|
||
</body>
|
||
</html>
|