Files
workspace/code/fms/admin-panel-full-demo.html
T
2026-08-09 22:34:28 +08:00

906 lines
59 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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 &middot; 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">&middot; 最近7天</span></h3><button class="btn sm g">查看全部 &rarr;</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>&copy; 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">&middot; 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>&lsaquo;</button><button class="active">1</button><button>2</button><button>3</button><button>&hellip;</button><button>13</button><button>&rsaquo;</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>&copy; 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">&middot; 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">&middot; 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>&copy; 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">角色权限 &amp; 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}">&times;</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>