254 lines
40 KiB
HTML
254 lines
40 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>29 供应链 · 桌面工作台 Demo 2</title>
|
||
<style>
|
||
:root{--brand:#98638f;--brand-dark:#784b70;--tint:#f3ebf2;--ink:#353a44;--muted:#9299a4;--line:#e0e4e9;--canvas:#f4f5f7;--green:#3b876e;--red:#bc626b;--amber:#aa7b35}
|
||
*{box-sizing:border-box}
|
||
body{margin:0;min-width:1040px;font:12px/1.5 "Segoe UI","Microsoft YaHei",sans-serif;color:var(--ink);background:var(--canvas)}
|
||
button,input,select{font:inherit;color:inherit}
|
||
button{cursor:pointer;white-space:nowrap;height:28px;padding:0 10px;background:white;border:1px solid #d8dde4;border-radius:4px}
|
||
button:hover:not(:disabled){background:#f2f4f7;border-color:#bcc4ce}
|
||
button:disabled{color:#abb2bc;cursor:default}
|
||
button:focus-visible,input:focus-visible,select:focus-visible{outline:2px solid #98638f;outline-offset:2px}
|
||
button.primary{background:var(--brand);color:white;border-color:transparent}
|
||
button.primary:hover:not(:disabled){background:var(--brand-dark);border-color:transparent}
|
||
input,select{height:28px;padding:0 8px;border:1px solid #d8dde4;border-radius:4px;background:white;min-width:0}
|
||
input[type=checkbox]{width:13px;height:13px;margin:0;vertical-align:middle;accent-color:var(--brand);cursor:pointer}
|
||
[hidden]{display:none!important}
|
||
svg{display:block;width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
|
||
.sprite{position:absolute;width:0;height:0;overflow:hidden}
|
||
.app{height:100dvh;min-height:580px;display:grid;grid-template-rows:32px 60px minmax(0,1fr)}
|
||
.window-titlebar{display:flex;align-items:center;justify-content:space-between;background:#f5f3f6;border-bottom:1px solid #e8e4ea;user-select:none}
|
||
.window-caption{display:flex;align-items:center;gap:8px;padding-left:12px;font-size:11px;color:#77717c}
|
||
.window-caption svg{width:16px;height:16px;color:var(--brand)}
|
||
.window-actions{display:flex;height:32px}
|
||
.window-actions button{display:grid;place-items:center;width:46px;height:32px;padding:0;border:0;border-radius:0;background:transparent;color:#55525b}
|
||
.window-actions button svg{width:12px;height:12px;stroke-width:1}
|
||
.window-actions button:hover{background:#e7e4e9;border:0}
|
||
.window-actions .window-close:hover{background:#c42b1c;color:#fff}
|
||
.app-header{display:flex;align-items:stretch;background:#fff;border-bottom:1px solid #e5e3e9}
|
||
.brand{width:180px;min-width:180px;display:flex;align-items:center;gap:10px;padding:0 18px;background:#faf8fb}
|
||
.brand-mark{height:34px;width:34px;display:grid;place-items:center;color:var(--brand);flex-shrink:0}
|
||
.brand-mark svg{width:32px;height:32px;stroke-width:1.35}
|
||
.brand strong{display:block;font-size:16px;font-weight:650;letter-spacing:.5px;color:#60445c}
|
||
.brand small{display:none}
|
||
.modulebar{display:flex;gap:8px;align-items:center;padding:0 16px}
|
||
.module{display:flex;align-items:center;gap:9px;height:40px;padding:0 16px;border:1px solid transparent;border-radius:6px;background:transparent;color:#788191;font-size:12px}
|
||
.module svg{width:21px;height:21px}
|
||
.module:hover:not(:disabled){background:#f4f3f7;border-color:transparent}
|
||
.module.active{color:var(--brand-dark);background:#eee5ed;font-weight:600}
|
||
.account{display:flex;align-items:center;gap:15px;margin-left:auto;padding:0 18px;font-size:11px;color:#8a929e}
|
||
.account b{font-weight:600;color:#746071}
|
||
.account .avatar{display:grid;place-items:center;width:27px;height:27px;background:#eee8ef;color:#8e6e89;border-radius:50%;font-size:11px}
|
||
.account button{background:transparent;border:0;color:#8a929e;padding:0 2px;font-size:11px}
|
||
.desktop-body{display:grid;grid-template-columns:180px minmax(0,1fr);min-height:0}
|
||
.function-panel{display:flex;flex-direction:column;background:#faf8fb;border-right:1px solid #e7e3e9;padding:16px 10px 12px}
|
||
.panel-heading{display:flex;justify-content:space-between;align-items:center;padding:0 10px 16px;color:#929aa7;font-size:11px}
|
||
.panel-heading b{font-weight:500}
|
||
.panel-heading span{font-size:10px;color:#a49aa9;letter-spacing:1px}
|
||
.nav{display:flex;align-items:center;gap:10px;text-align:left;width:100%;height:35px;margin-bottom:5px;border:0;background:transparent;border-radius:5px;padding:0 11px;color:#6c7685}
|
||
.nav svg{width:16px;height:16px}
|
||
.nav.active{background:#eee5ed;color:var(--brand-dark);font-weight:600}
|
||
.nav.active:after{content:"";width:5px;height:5px;border-right:1px solid currentColor;border-top:1px solid currentColor;transform:rotate(45deg);margin-left:auto;opacity:.6}
|
||
.panel-bottom{margin-top:auto;padding:12px 10px 0;border-top:1px solid #e6e9ee;color:#91a095;font-size:10px}
|
||
.panel-bottom small{display:block;color:#a8afb9;font-size:9px;margin-top:4px}
|
||
.shell{display:flex;min-width:0;min-height:0}
|
||
.content{display:flex;flex-direction:column;flex:1;min-width:0;min-height:0;padding:0 12px}
|
||
.page-heading{height:44px;min-height:44px;display:flex;align-items:center;gap:12px;padding:0 1px}
|
||
h1{font-size:14px;font-weight:600;margin:0}
|
||
.page-heading p{display:none}
|
||
.workspace{display:flex;flex-direction:column;flex:1;min-height:0;background:#fff;border:1px solid #dce1e7;border-radius:5px;overflow:hidden}
|
||
.toolbar{display:flex;align-items:center;gap:9px;min-height:43px;padding:7px 10px;border-bottom:1px solid #e6e9ee}
|
||
.command-group{display:flex;gap:5px;align-items:center;padding-right:9px;border-right:1px solid #e6e9ee}
|
||
.command-group:last-child{border:0;margin-left:auto;padding:0}
|
||
.toolbar button{display:flex;align-items:center;gap:6px;font-size:11px;border-color:transparent;background:transparent;padding:0 8px}
|
||
.toolbar button.primary{background:var(--brand);color:white;padding:0 10px}
|
||
.toolbar button:before{content:"";height:15px;width:15px;background:currentColor;mask:var(--icon) center/contain no-repeat}
|
||
.filters{display:flex;align-items:center;gap:10px;min-height:44px;margin:0;padding:7px 10px;background:#fcfcfd;border-bottom:1px solid #e8ebef;flex-wrap:wrap}
|
||
.field{display:flex;flex-direction:column;gap:5px}
|
||
.filters .field{flex-direction:row;align-items:center;gap:7px}
|
||
.field label{font-size:11px;color:#89929f;white-space:nowrap}
|
||
.search-field input{width:230px}
|
||
.filters select{width:105px}
|
||
.filters button{font-size:11px}
|
||
.list-header{min-height:35px;height:35px;display:flex;align-items:center;justify-content:space-between;padding:0 11px;gap:8px}
|
||
.tabs{display:flex;align-items:stretch;height:35px;gap:18px}
|
||
.tab{height:35px;border:0;padding:0;background:transparent;border-radius:0;color:#9199a5;font-size:11px;position:relative}
|
||
.tab.active{color:var(--brand-dark);font-weight:600}
|
||
.tab.active:after{content:"";position:absolute;bottom:0;left:0;right:0;background:var(--brand);height:2px}
|
||
.tab small{font-size:10px;margin-left:6px;background:#f2f4f6;border-radius:3px;padding:0 4px}
|
||
.selection-info{display:flex;gap:10px;align-items:center;font-size:10px;white-space:nowrap}
|
||
.selection-info>span+span{padding-left:10px;border-left:1px solid #e5e8ed}
|
||
.selection{color:var(--brand-dark)}
|
||
.muted{color:var(--muted)}
|
||
.table-scroll{flex:1;min-height:100px;overflow:auto;border-top:1px solid #dce1e7}
|
||
table{border-collapse:separate;border-spacing:0;width:max-content;min-width:100%;font-size:12px;white-space:nowrap}
|
||
th{text-align:left;position:sticky;top:0;z-index:2;background:#f1f3f6;font-weight:500;color:#66717f;height:30px;border-right:1px solid #e0e5eb;border-bottom:1px solid #d9dfe6;padding:0 10px}
|
||
td{height:36px;padding:2px 10px;border-bottom:1px solid #edf0f3;border-right:1px solid #edf0f3;background:#fff}
|
||
.compact td{height:29px;padding-top:1px;padding-bottom:1px}
|
||
tbody tr:nth-child(even) td{background:#fcfcfd}
|
||
tbody tr:hover td{background:#f6f3f7}
|
||
tbody tr.selected td{background:#f0e6ef}
|
||
th:first-child,td:first-child{width:31px;min-width:31px;text-align:center;padding:0 8px;position:sticky;left:0;z-index:1}
|
||
th:first-child{z-index:3}
|
||
th:last-child,td:last-child{position:sticky;right:0;border-left:1px solid #dfe4ea;box-shadow:-3px 0 5px #00000004}
|
||
th:last-child{z-index:3}
|
||
.row-number{text-align:right;color:#9ba4b0;font-size:11px}
|
||
.entity-mark,.entity small{display:none}
|
||
.entity-name{font-weight:400}
|
||
.mono{font:11px Consolas,monospace;color:#838d9c}
|
||
.date{font-size:11px;color:#778291;font-variant-numeric:tabular-nums}
|
||
.subline{display:inline;font-size:10px;margin-left:5px}
|
||
.pill{font-size:10px;color:var(--green);background:#edf6f1;display:inline-flex;gap:5px;align-items:center;padding:1px 5px;border-radius:3px}
|
||
.pill:before{content:"";width:4px;height:4px;background:currentColor;border-radius:50%}
|
||
.pill.warn{color:var(--amber);background:#faf3e9}
|
||
.pill.error{color:var(--red);background:#fbefef}
|
||
.tag{color:#7e8794;font-size:11px}
|
||
button.text{background:transparent;border:0;color:#7c6a7b;padding:0 5px;height:24px;font-size:11px}
|
||
.sort{border:0;background:transparent;padding:0;height:auto;color:inherit;font:inherit}
|
||
.empty{text-align:center;height:150px!important;color:var(--muted)}
|
||
.pagination{height:36px;min-height:36px;display:flex;justify-content:space-between;align-items:center;padding:0 10px;border-top:1px solid #dfe4ea;background:#fcfcfd;font-size:10px;color:#959daa}
|
||
.pagination-controls{display:flex;align-items:center;gap:6px}
|
||
.pagination button,.pagination select{height:24px;font-size:11px}
|
||
.current-page{padding:2px 7px;background:var(--tint);color:var(--brand-dark);border-radius:3px}
|
||
.statusbar{height:23px;min-height:23px;display:flex;align-items:center;justify-content:space-between;font-size:9px;color:#a4abb5;padding:0 1px}
|
||
dialog{border:1px solid #dce1e7;border-radius:7px;padding:0;color:var(--ink);box-shadow:0 18px 70px #30233330;max-width:90vw;max-height:90vh}
|
||
dialog::backdrop{background:#20232c45}
|
||
.drawer{width:620px}
|
||
.dialog-head{padding:12px 16px;background:#fafbfc;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between}
|
||
.dialog-head h2{font-size:14px;margin:0}
|
||
.dialog-head p{font-size:10px;color:var(--muted);margin:3px 0 0}
|
||
.dialog-body{padding:16px;display:flex;flex-direction:column;gap:13px}
|
||
.drawer .dialog-body{display:grid;grid-template-columns:1fr 1fr}
|
||
.dialog-body input:not([type=checkbox]),.dialog-body select{width:100%;height:30px}
|
||
.dialog-footer{padding:10px 16px;display:flex;justify-content:flex-end;gap:8px;border-top:1px solid var(--line)}
|
||
.help{grid-column:1/-1;background:var(--canvas);color:var(--muted);padding:10px;font-size:11px}
|
||
.permissions{display:grid;gap:7px;font-size:11px}
|
||
.permissions label,.columns-dialog label{display:flex;align-items:center;gap:8px}
|
||
.columns-dialog{width:330px}
|
||
.notice{padding:10px;background:var(--tint);color:var(--brand-dark);border-radius:4px}
|
||
.toast{position:fixed;z-index:10;bottom:35px;left:50%;transform:translateX(-50%);padding:10px 18px;background:#4a3c49;color:white;border-radius:5px;box-shadow:0 5px 20px #0002}
|
||
@media(max-width:1180px){.account{gap:10px;padding:0 12px}.account .points{display:none}.module{padding:0 13px}.page-heading p{display:none}.content{padding:0 8px}.toolbar{gap:5px}.command-group{gap:3px;padding-right:5px}.toolbar button{padding:0 6px}.search-field input{width:190px}.filters{gap:8px}.selection-info #resultCount{display:none}}
|
||
#add{--icon:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.7%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M12%205v14M5%2012h14%22%2F%3E%3C%2Fsvg%3E")}
|
||
#refresh{--icon:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.7%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M20%207v5h-5M4%2017v-5h5M5%207a8%208%200%200%201%2014-1l1%206M4%2012l1%206a8%208%200%200%200%2014-1%22%2F%3E%3C%2Fsvg%3E")}
|
||
#batch{--icon:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.7%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M8%203h13v14H8zM3%208v13h13M11%207h7M11%2011h7%22%2F%3E%3C%2Fsvg%3E")}
|
||
#detect{--icon:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.7%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M4%2012l5%205L20%206%22%2F%3E%3C%2Fsvg%3E")}
|
||
#login{--icon:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.7%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M14%203h7v18h-7M3%2012h13M11%207l5%205-5%205%22%2F%3E%3C%2Fsvg%3E")}
|
||
#density{--icon:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.7%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M3%205h18M3%2012h18M3%2019h18%22%2F%3E%3C%2Fsvg%3E")}
|
||
#columnSettings{--icon:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%221.7%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M3%204h18v16H3zM9%204v16M15%204v16%22%2F%3E%3C%2Fsvg%3E")}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<svg class="sprite" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><defs>
|
||
<symbol id="i-box" viewBox="0 0 24 24"><path d="m12 2 9 5v10l-9 5-9-5V7zM3 7l9 5 9-5M12 12v10M7.5 4.5l9 5V14"/></symbol>
|
||
<symbol id="i-shop" viewBox="0 0 24 24"><path d="M3 10v11h18V10M2 10l2-7h16l2 7zM9 21v-7h6v7M7 3v7M17 3v7"/></symbol>
|
||
<symbol id="i-shield" viewBox="0 0 24 24"><path d="M12 2l9 4v6c0 5-9 10-9 10S3 17 3 12V6zM8 12l3 3 5-6"/></symbol>
|
||
<symbol id="i-brand" viewBox="0 0 24 24"><path d="M4 3h10l7 7-11 11-7-7V4zM8 7h.01"/></symbol>
|
||
<symbol id="i-people" viewBox="0 0 24 24"><path d="M16 21v-3a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v3M9 10a4 4 0 1 0 0-8 4 4 0 0 0 0 8M18 3a4 4 0 0 1 0 8M22 21v-3a4 4 0 0 0-3-4"/></symbol>
|
||
</defs></svg>
|
||
<div class="app">
|
||
<div class="window-titlebar">
|
||
<div class="window-caption"><svg aria-hidden="true"><use href="#i-box"/></svg><span>29 供应链</span></div>
|
||
<div class="window-actions" aria-label="窗口按钮外观演示">
|
||
<button type="button" aria-label="最小化(外观演示)" title="最小化 · 仅外观演示"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M1 6h10"/></svg></button>
|
||
<button type="button" aria-label="最大化(外观演示)" title="最大化 · 仅外观演示"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="M1.5 1.5h9v9h-9z"/></svg></button>
|
||
<button type="button" class="window-close" aria-label="关闭(外观演示)" title="关闭 · 仅外观演示"><svg viewBox="0 0 12 12" aria-hidden="true"><path d="m1.5 1.5 9 9m0-9-9 9"/></svg></button>
|
||
</div>
|
||
</div>
|
||
<header class="app-header">
|
||
<div class="brand"><span class="brand-mark"><svg aria-hidden="true"><use href="#i-box"/></svg></span><div><strong>29 供应链</strong></div></div>
|
||
<nav class="modulebar" aria-label="业务模块">
|
||
<button class="module" data-module="operation" data-start="stores"><svg aria-hidden="true"><use href="#i-shop"/></svg>店铺运营</button>
|
||
<button class="module" data-module="admin" data-start="users"><svg aria-hidden="true"><use href="#i-shield"/></svg>超级管理</button>
|
||
<button class="module" data-module="brand" data-start="oem"><svg aria-hidden="true"><use href="#i-brand"/></svg>品牌管理</button>
|
||
</nav>
|
||
<div class="account"><span class="points">账户积分 <b>9,759</b></span><button id="designNotes">布局说明</button><span class="avatar" aria-hidden="true">管</span><span>管理员</span></div>
|
||
</header>
|
||
<div class="desktop-body">
|
||
<aside class="function-panel" aria-label="模块内功能">
|
||
<div class="panel-heading"><span>工作区</span><b id="moduleTitle" hidden>店铺运营</b></div>
|
||
<button class="nav" data-area="operation" data-page="stores"><svg aria-hidden="true"><use href="#i-shop"/></svg><span>店铺管理</span></button>
|
||
<button class="nav" data-area="operation" data-page="accounts"><svg aria-hidden="true"><use href="#i-people"/></svg><span>子账号管理</span></button>
|
||
<button class="nav" data-area="admin" data-page="superstores"><svg aria-hidden="true"><use href="#i-shop"/></svg><span>超级店铺</span></button>
|
||
<button class="nav" data-area="admin" data-page="users"><svg aria-hidden="true"><use href="#i-people"/></svg><span>超级用户</span></button>
|
||
<button class="nav" data-area="brand" data-page="oem"><svg aria-hidden="true"><use href="#i-brand"/></svg><span>OEM 贴牌</span></button>
|
||
<div class="panel-bottom">● 本地演示环境<small>数据仅在本次浏览中生效</small></div>
|
||
</aside>
|
||
<div class="shell"><main class="content">
|
||
<section class="page-heading"><h1 id="pageTitle">店铺管理</h1><p id="pageDescription"></p><span id="crumb" hidden></span><span id="eyebrow" hidden></span></section>
|
||
<section class="workspace" aria-label="管理列表">
|
||
<div class="toolbar" aria-label="当前页面操作">
|
||
<div class="command-group" role="group" aria-label="记录操作"><button class="primary" id="add">添加店铺</button><button id="refresh">刷新列表</button></div>
|
||
<div class="command-group" role="group" aria-label="选中记录操作"><button id="batch" disabled>批量续期</button><button id="detect" disabled>检测选中</button><button id="login" disabled>登录选中</button></div>
|
||
<div class="command-group" role="group" aria-label="表格视图"><button id="density" aria-pressed="true">舒适视图</button><button id="columnSettings">列设置</button></div>
|
||
</div>
|
||
<form class="filters" id="filterForm">
|
||
<div class="field search-field"><label for="keyword">关键词</label><input id="keyword" maxlength="100" placeholder="搜索店铺名称 / 店铺 ID"></div>
|
||
<div class="field"><label for="groupFilter">分组</label><select id="groupFilter"></select></div>
|
||
<div class="field"><label for="statusFilter">状态</label><select id="statusFilter"><option value="">全部状态</option></select></div>
|
||
<button class="primary" type="submit">查询</button><button type="button" id="reset">重置</button>
|
||
</form>
|
||
<div class="list-header"><div class="tabs" id="tabs"></div><div class="selection-info"><span id="selection" class="muted">选择记录后可批量操作</span><span id="resultCount" class="muted"></span></div></div>
|
||
<div class="table-scroll"><table id="table" class="compact"><thead id="thead"></thead><tbody id="tbody"></tbody></table></div>
|
||
<footer class="pagination"><span id="range"></span><div class="pagination-controls"><label for="pageSize">每页</label><select id="pageSize"><option>20</option><option selected>50</option><option>100</option></select><button id="prev" aria-label="上一页">上一页</button><span class="current-page" id="pageNumber">1</span><button id="next" aria-label="下一页">下一页</button></div></footer>
|
||
</section>
|
||
<footer class="statusbar"><span id="loadStatus">数据已就绪 · 本地演示</span><span>Demo 2 · 桌面工作台</span></footer>
|
||
</main></div></div></div>
|
||
<dialog class="drawer" id="editor"><form id="editForm"><header class="dialog-head"><div><h2 id="editorTitle"></h2><p>修改仅在本次演示中生效</p></div><button type="button" class="text" data-close="editor">关闭</button></header><div class="dialog-body" id="editorFields"></div><footer class="dialog-footer"><button type="button" data-close="editor">取消</button><button class="primary" type="submit">保存</button></footer></form></dialog>
|
||
<dialog class="columns-dialog" id="columns"><header class="dialog-head"><h2>显示的列</h2><button class="text" data-close="columns">完成</button></header><div class="dialog-body" id="columnOptions"></div></dialog>
|
||
<dialog id="details" style="width:520px;max-width:95vw"><header class="dialog-head"><h2 id="detailTitle"></h2><button class="text" data-close="details">关闭</button></header><div class="dialog-body" id="detailBody"></div></dialog><div class="toast" role="status" id="toast" hidden></div>
|
||
<script>
|
||
'use strict';
|
||
const $=id=>document.getElementById(id);
|
||
const escapeHtml=value=>String(value??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||
const names=['乐乐生活馆','清麦日用百货','栖木家居旗舰店','佐薇服饰小铺','青禾优选','云朵母婴生活','拾光好物集合店','橙意食品专营店','木子家居日用','浅湾服饰店','山野户外生活','简物生活旗舰店','暖晨家纺店','知味食品小铺','白露日用百货','云间服饰集合店','小满优选','春序家居店','鹿鸣母婴店','青山好物','星野百货','暮色生活馆','沐光服饰','初禾日用品'];
|
||
const stores=Array.from({length:72},(_,i)=>({name:names[i%names.length]+(i>=24?" · "+(Math.floor(i/24)+1)+"店":""),i})).map(({name,i})=>({id:1001+i,name,code:'wx'+(316129135100+i*74031).toString(16)+'a'+i,group:['精选店铺','服饰鞋包','生活百货'][i%3],account:['运营一组','运营二组','未分配'][i%3],owner:'demo_owner_'+(i%4+1),phone:'138****'+String(1200+i),status:i%7===3?'已过期':i%5===2?'即将到期':'正常',expires:i%7===3?'2026-10-20':i%5===2?'2026-11-02':'2027-05-09',note:i%3===0?'重点运营店铺':'',permissions:['店铺列表','订单管理'],created:'2026-08-14 15:16',login:i%7===3?'未登录':'登录成功',api:i%4===0?'未购买':'已开通',permanent:i%9===0?'是':'否',secondary:i%6===0?'已启用':'未启用'}));
|
||
const data={stores,superstores:stores.map(r=>({...r,id:r.id+3000})),accounts:['林小夏','陈晓','周宁','许安','李可','宋悦'].map((name,i)=>({id:2001+i,name,code:'operator_'+(i+1),phone:'139****'+(2300+i),group:['运营','客服','仓储'][i%3],status:i===4?'已停用':'正常',account:'主账号 · 演示企业',note:'',permissions:['店铺列表','订单管理']})),users:Array.from({length:18},(_,i)=>({id:3001+i,name:['青禾供应链','云飞商贸','开兴科技','花下海','众店联源','微擎科技'][i%6]+(i>5?' '+(Math.floor(i/6)+1):''),code:'user_'+(801+i),phone:'137****'+(5600+i),group:i%4===0?'代理商':'主账户',status:i===5?'已停用':'正常',company:['青禾商贸有限公司','云飞供应链有限公司','开兴网络科技'][i%3],points:300+i*20,balance:(140+i*37.5).toFixed(2),fee:'2.50',monthly:30,returnFee:'3.00',agent:['云飞','大牛','花下海'][i%3],type:'普通用户',permissions:['商品采集','在线寄件'],note:''})),oem:['桃花源供应链','店速通','开茂科技','枫谷拍单','洋总代理','臻和网络','贝塔一体仓','佳俊商贸'].map((name,i)=>({id:5001+i,name,code:'26'+(701+i)+'TW'+i,group:'品牌合作',status:'正常',account:'demo_brand_'+(i+1),phone:'136****'+(7400+i),note:'',permissions:[]}))};
|
||
const configs={stores:{title:'店铺管理',en:'STORE MANAGEMENT',desc:'集中管理店铺、账号与授权,让日常运营井然有序。',add:'添加店铺',batch:'批量续期',search:'搜索店铺名称 / 店铺 ID',columns:[['name','店铺信息'],['group','所属分组'],['account','子账号'],['status','授权状态'],['expires','到期时间'],['note','备注']],groups:['精选店铺','服饰鞋包','生活百货']},superstores:{title:'超级店铺管理',en:'STORE ADMINISTRATION',desc:'跨账号查看全部店铺,统一管理店铺归属与授权状态。',add:'添加店铺',batch:'批量检测',search:'搜索店铺 / 所属账号 / 店铺 ID',columns:[['name','店铺信息'],['owner','所属账号'],['group','店铺分组'],['account','子账号'],['status','授权状态'],['expires','到期时间']],groups:['精选店铺','服饰鞋包','生活百货']},accounts:{title:'子账号管理',en:'TEAM & ACCESS',desc:'为团队分配独立账号,以清晰的访问权限协同管理店铺。',add:'添加子账号',batch:'批量启用',search:'搜索昵称 / 登录账号',columns:[['name','用户信息'],['phone','登录手机号'],['group','角色分组'],['status','账号状态'],['permissions','可访问页面'],['account','所属主账号']],groups:['运营','客服','仓储']},users:{title:'超级用户管理',en:'USER ADMINISTRATION',desc:'统一管理用户、功能权限与账户信息。',add:'添加用户',batch:'批量启用',search:'搜索用户 / 手机号 / 公司',columns:[['name','用户信息'],['group','用户分组'],['company','公司'],['points','积分'],['balance','快递余额'],['fee','每单快递费'],['status','账号状态']],groups:['主账户','代理商']},oem:{title:'OEM 贴牌管理',en:'BRAND PARTNERS',desc:'管理合作品牌标识与主体账号,建立统一的品牌工作空间。',add:'添加贴牌',batch:'批量启用',search:'搜索贴牌名称 / 贴牌编码',columns:[['name','贴牌名称'],['code','贴牌编码'],['account','主体账号'],['status','状态'],['assets','品牌素材']],groups:['品牌合作']}};
|
||
configs.stores.columns=[['name','店铺名称'],['code','店铺 ID'],['created','添加时间'],['expires','到期时间'],['status','授权状态'],['group','分组'],['account','子账号'],['phone','子账号手机号'],['login','登录状态'],['api','API 购买'],['note','备注']];
|
||
configs.superstores.columns=[['name','店铺名称'],['code','店铺 ID / 登录号码'],['owner','所属账号'],['account','子账号'],['group','店铺分组'],['login','登录状态'],['status','授权状态'],['api','API 购买'],['secondary','二级超管'],['permanent','永久'],['note','备注']];
|
||
configs.accounts.columns=[['name','用户昵称'],['code','登录账号'],['phone','手机号'],['group','分组'],['account','所属主账号'],['status','状态'],['permissions','可访问页面'],['note','备注']];
|
||
configs.users.columns=[['name','用户昵称'],['phone','手机 / 登录账号'],['group','分组'],['company','公司'],['type','账户类型'],['permissions','功能权限'],['agent','代理名称'],['points','积分'],['monthly','月费耗点'],['balance','快递余额'],['fee','每单快递费'],['returnFee','每单退货费'],['status','状态']];
|
||
configs.oem.columns=[['code','贴牌编码'],['name','贴牌名称'],['account','主体账号'],['assets','图标'],['assetsTitle','题图'],['status','状态'],['note','备注']];
|
||
let current='stores',tab='',page=1,query='',group='',status='',sortAsc=true,editingId=null,selected=new Set(),hiddenColumns=new Set(),toastTimer;
|
||
function isStore(){return current==='stores'||current==='superstores'}
|
||
function filtered(){return data[current].filter(r=>(!query||Object.values(r).join(' ').toLowerCase().includes(query))&&(!group||r.group===group)&&(!status||r.status===status)&&(!tab||r.status===tab)).sort((a,b)=>sortAsc?a.name.localeCompare(b.name,'zh-CN'):b.name.localeCompare(a.name,'zh-CN'))}
|
||
function visibleRows(){const rows=filtered();return rows.slice((page-1)*Number($('pageSize').value),page*Number($('pageSize').value))}
|
||
function showToast(message){$('toast').textContent=message;$('toast').hidden=false;clearTimeout(toastTimer);toastTimer=setTimeout(()=>$('toast').hidden=true,3200)}
|
||
function renderTabs(){const tabs=isStore()?[['','全部店铺'],['正常','授权正常'],['即将到期','即将到期'],['已过期','已过期']]:[['','全部'+(current==='oem'?'品牌':'账号')],['正常','正常'],['已停用','已停用']];$('tabs').innerHTML=tabs.map(([value,label])=>`<button class="tab ${tab===value?'active':''}" data-tab="${value}" aria-pressed="${tab===value}">${label}<small>${data[current].filter(r=>!value||r.status===value).length}</small></button>`).join('')}
|
||
function cell(r,key){const v=escapeHtml(r[key]);switch(key){case'name':return `<div class="entity"><span class="entity-mark ${r.id%3===0?'alt':''}">${escapeHtml(r.name.slice(0,1))}</span><div><div class="entity-name">${v}</div><small>${escapeHtml(r.code)}</small></div></div>`;case'login':return `<span style="color:${r.login==='登录成功'?'var(--green)':'var(--muted)'}">${v}</span>`;case'status':return `<span class="pill ${r.status==='即将到期'?'warn':r.status==='正常'?'':'error'}">${v==='正常'&&isStore()?'授权正常':v}</span>`;case'group':return `<span class="tag">${v}</span>`;case'expires':return `<span class="date">${v}</span>${r.status==='即将到期'?'<div class="subline" style="color:var(--amber)">5 天后到期</div>':''}`;case'permissions':return `<span class="muted">${escapeHtml(r.permissions.join('、'))||'尚未分配'}</span>`;case'assetsTitle':case'assets':return '<span class="muted">暂未上传</span>';case'balance':case'fee':case'returnFee':return `<span class="date">¥ ${v}</span>`;case'code':case'owner':return `<span class="mono">${v}</span>`;case'note':return `<span class="muted">${v||'—'}</span>`;default:return v||'<span class="muted">—</span>'}}
|
||
function renderTable(){const cfg=configs[current],rows=filtered(),size=Number($('pageSize').value);page=Math.max(1,Math.min(page,Math.ceil(rows.length/size)||1));const visible=visibleRows(),cols=cfg.columns.filter(([key])=>!hiddenColumns.has(key));$('thead').innerHTML=`<tr><th><input type="checkbox" id="selectAll" aria-label="选择当前页全部记录" ${visible.length&&visible.every(r=>selected.has(r.id))?'checked':''}></th><th class="row-number">序号</th>${cols.map(([key,label])=>`<th>${key==='name'?`<button class="sort" id="sortName">${label} ${sortAsc?'↑':'↓'}</button>`:label}</th>`).join('')}<th>操作</th></tr>`;$('tbody').innerHTML=visible.length?visible.map((r,index)=>`<tr class="${selected.has(r.id)?'selected':''}"><td><input type="checkbox" data-select="${r.id}" aria-label="选择${escapeHtml(r.name)}" ${selected.has(r.id)?'checked':''}></td><td class="row-number">${(page-1)*size+index+1}</td>${cols.map(([key])=>`<td>${cell(r,key)}</td>`).join('')}<td>${isStore()?`<button class="text row-action" data-store-action="enter" data-id="${r.id}">进入</button><button class="text row-action" data-store-action="renew" data-id="${r.id}">续期</button>`:""}<button class="text row-action" data-detail="${r.id}">${isStore()?'查看':'详情'}</button><button class="text row-action" data-edit="${r.id}">编辑</button></td></tr>`).join(''):`<tr><td class="empty" colspan="${cols.length+3}">没有匹配的记录,请调整筛选条件或重置。</td></tr>`;const all=$('selectAll');all.indeterminate=visible.some(r=>selected.has(r.id))&&!visible.every(r=>selected.has(r.id));$('selection').textContent=selected.size?`已选择 ${selected.size} 项`:'选择记录后可批量操作';$('selection').className=selected.size?'selection':'muted';$('batch').disabled=!selected.size;$('detect').disabled=$('login').disabled=!selected.size;$('resultCount').textContent=`共 ${rows.length} 条记录`;$('range').textContent=rows.length?`显示 ${(page-1)*size+1}–${Math.min(page*size,rows.length)} 条,共 ${rows.length} 条`:'共 0 条记录';$('pageNumber').textContent=`${page} / ${Math.ceil(rows.length/size)||1}`;$('prev').disabled=page===1;$('next').disabled=page*size>=rows.length}
|
||
function render(){renderTabs();renderTable()}
|
||
function switchPage(key){const area=key==='stores'||key==='accounts'?'operation':key==='oem'?'brand':'admin';document.querySelectorAll('[data-module]').forEach(b=>{b.classList.toggle('active',b.dataset.module===area);b.setAttribute('aria-pressed',b.dataset.module===area)});document.querySelectorAll('[data-area]').forEach(b=>b.hidden=b.dataset.area!==area);$('moduleTitle').textContent={operation:'店铺运营',admin:'超级管理',brand:'品牌管理'}[area];current=key;tab='';page=1;query='';group='';status='';selected.clear();hiddenColumns.clear();const cfg=configs[key];$('detect').hidden=$('login').hidden=!isStore();document.querySelectorAll('[data-page]').forEach(b=>{b.classList.toggle('active',b.dataset.page===key);b.setAttribute('aria-current',b.dataset.page===key?'page':'false')});$('crumb').textContent=$('pageTitle').textContent=cfg.title;$('eyebrow').textContent=cfg.en;$('pageDescription').textContent=cfg.desc;$('add').textContent=cfg.add;$('batch').textContent=cfg.batch;$('keyword').value='';$('keyword').placeholder=cfg.search;$('groupFilter').innerHTML='<option value="">全部分组</option>'+cfg.groups.map(g=>`<option>${g}</option>`).join('');$('statusFilter').innerHTML='<option value="">全部状态</option>'+(isStore()?['正常','即将到期','已过期']:['正常','已停用']).map(s=>`<option>${s}</option>`).join('');render()}
|
||
function field(label,name,value='',required=false,type='text'){return `<div class="field"><label for="edit_${name}">${label}${required?' <span style="color:var(--brand)">*</span>':''}</label><input id="edit_${name}" name="${name}" type="${type}" value="${escapeHtml(value)}" maxlength="100" ${required?'required':''}></div>`}
|
||
function openEditor(id=null){editingId=id;const r=data[current].find(r=>r.id===id)||{},cfg=configs[current];$('editorTitle').textContent=id?'编辑'+(isStore()?'店铺':current==='oem'?'贴牌':'用户'):cfg.add;let fields=field(isStore()?'店铺名称':current==='oem'?'贴牌名称':'用户昵称','name',r.name,true)+field(isStore()?'店铺 ID':current==='oem'?'贴牌编码':'登录账号','code',r.code,true);if(current==='accounts'||current==='users')fields+=field('手机号','phone',r.phone,true)+field(id?'新密码(留空则不修改)':'密码','password','',!id,'password');fields+=`<div class="field"><label for="edit_group">所属分组</label><select name="group" id="edit_group">${cfg.groups.map(g=>`<option ${r.group===g?'selected':''}>${g}</option>`).join('')}</select></div>`;if(isStore()||current==='oem')fields+=field(current==='oem'?'主体账号':'子账号','account',r.account,current==='oem');if(current==='users')fields+=field('公司','company',r.company);if(!isStore())fields+=`<div class="field"><label for="edit_status">账号状态</label><select name="status" id="edit_status"><option ${r.status!=='已停用'?'selected':''}>正常</option><option ${r.status==='已停用'?'selected':''}>已停用</option></select></div>`;if(current==='accounts'||current==='users')fields+=`<div class="field"><label>可访问页面</label><div class="permissions">${['店铺列表','订单管理','商品采集','在线寄件','评分考核','税务申报'].map(p=>`<label><input type="checkbox" name="permission" value="${p}" ${(r.permissions||[]).includes(p)?'checked':''}>${p}</label>`).join('')}</div></div>`;fields+=field('备注','note',r.note);if(isStore()&&!id)fields+='<div class="help">此处演示手动添加流程。正式系统可在此接入扫码授权;新记录默认使用演示授权。</div>';$('editorFields').innerHTML=fields;$('editor').showModal()}
|
||
document.querySelectorAll('[data-page]').forEach(b=>b.onclick=()=>switchPage(b.dataset.page));
|
||
document.querySelectorAll('[data-module]').forEach(b=>b.onclick=()=>switchPage(b.dataset.start));
|
||
$('filterForm').onsubmit=e=>{e.preventDefault();query=$('keyword').value.trim().toLowerCase();group=$('groupFilter').value;status=$('statusFilter').value;page=1;selected.clear();renderTable()};
|
||
$('reset').onclick=()=>{query=group=status=tab='';page=1;selected.clear();$('keyword').value=$('groupFilter').value=$('statusFilter').value='';render()};
|
||
$('tabs').onclick=e=>{const b=e.target.closest('[data-tab]');if(b){tab=b.dataset.tab;page=1;selected.clear();renderTabs();renderTable()}};
|
||
$('prev').onclick=()=>{page--;renderTable()};$('next').onclick=()=>{page++;renderTable()};$('pageSize').onchange=()=>{page=1;renderTable()};
|
||
$('table').onchange=e=>{if(e.target.id==='selectAll')visibleRows().forEach(r=>e.target.checked?selected.add(r.id):selected.delete(r.id));else if(e.target.dataset.select){const id=Number(e.target.dataset.select);e.target.checked?selected.add(id):selected.delete(id)}renderTable()};
|
||
$('table').onclick=e=>{const action=e.target.closest('[data-store-action]');if(action){const r=data[current].find(r=>r.id===Number(action.dataset.id));if(action.dataset.storeAction==='renew'){r.status='正常';r.expires='2027-05-09';render();showToast('已模拟续期:'+r.name)}else{showToast('模拟进入店铺:'+r.name+'(未连接真实店铺)')}return}const edit=e.target.closest('[data-edit]'),detail=e.target.closest('[data-detail]');if(edit)openEditor(Number(edit.dataset.edit));if(detail){const r=data[current].find(r=>r.id===Number(detail.dataset.detail));$('detailTitle').textContent=r.name;$('detailBody').innerHTML=configs[current].columns.map(([key,label])=>`<div><div class="muted" style="font-size:11px;margin-bottom:6px">${label}</div>${cell(r,key)}</div>`).join('')+'<div class="notice">此为本地演示记录,不会连接店铺或读取真实账号。</div>';$('details').showModal()}if(e.target.id==='sortName'){sortAsc=!sortAsc;renderTable()}};
|
||
$('density').onclick=()=>{const compact=$('table').classList.toggle('compact');$('density').textContent=compact?'舒适视图':'紧凑视图';$('density').setAttribute('aria-pressed',compact)};
|
||
$('columnSettings').onclick=()=>{$('columnOptions').innerHTML=configs[current].columns.map(([key,label])=>`<label><input type="checkbox" value="${key}" ${hiddenColumns.has(key)?'':'checked'} ${key==='name'?'disabled':''}>${label}${key==='name'?'(固定)':''}</label>`).join('');$('columns').showModal()};
|
||
$('columnOptions').onchange=e=>{e.target.checked?hiddenColumns.delete(e.target.value):hiddenColumns.add(e.target.value);renderTable()};
|
||
$('add').onclick=()=>openEditor();document.querySelectorAll('[data-close]').forEach(b=>b.onclick=()=>$(b.dataset.close).close());
|
||
$('editForm').onsubmit=e=>{e.preventDefault();const values=new FormData(e.target),name=String(values.get('name')).trim(),code=String(values.get('code')).trim();if(!name||!code){showToast('名称和编号不能为空');return}if(data[current].some(r=>r.code===code&&r.id!==editingId)){showToast('该编号已存在,请使用其他编号');return}const existing=data[current].find(r=>r.id===editingId);const record={...(existing||{id:Date.now(),status:'正常',expires:'2027-05-09',owner:'demo_owner_1',points:0,balance:'0.00',fee:'2.50'}),name,code,group:values.get('group'),note:values.get('note'),permissions:values.getAll('permission')};for(const key of ['phone','account','company','status'])if(values.has(key))record[key]=values.get(key);if(existing)Object.assign(existing,record);else data[current].unshift(record);$('editor').close();render();showToast(editingId?'已保存修改(演示)':'已添加记录(演示)')};
|
||
$('batch').onclick=()=>{let count=selected.size;data[current].filter(r=>selected.has(r.id)).forEach(r=>{if(current!=='superstores'){r.status='正常';if(current==='stores')r.expires='2027-05-09'}});selected.clear();render();showToast(current==='superstores'?`已完成 ${count} 家店铺的模拟检测`:`已更新 ${count} 条演示记录`)};
|
||
$('detect').onclick=()=>{showToast('已模拟检测 '+selected.size+' 家店铺;登录状态已显示在表格中')};
|
||
$('login').onclick=()=>{data[current].filter(r=>selected.has(r.id)).forEach(r=>r.login='登录成功');renderTable();showToast('已模拟登录 '+selected.size+' 家店铺')};
|
||
$('refresh').onclick=()=>{render();$('loadStatus').textContent='已刷新 · '+new Date().toLocaleTimeString('zh-CN')+' · 本地演示';showToast('列表已刷新')};
|
||
$('designNotes').onclick=()=>{$('detailTitle').textContent='为桌面工作流而设计';$('detailBody').innerHTML='<div class="notice">品牌主色 #98638f · 梅紫</div><p>顶部以统一线性图标区分店铺运营、超级管理与品牌管理;左侧仅展示当前业务域内的功能。</p><p>工具栏按记录、选中记录、视图分组。新增和刷新位于左侧,批量操作居中,列设置与行密度位于右侧。筛选和状态切换贴近表格,选择数量在列表右上角显示。</p><p>默认紧凑行高 29px,舒适行高 36px;默认每页 50 条。表头与操作列固定,适合长列表与横向浏览。</p><p>后续 WPF 可使用 DockPanel / Grid 组织顶部模块栏、内容区与底部状态栏,业务页面复用 DataGridEx。此 HTML 仅演示交互,不包含权限校验、真实登录、支付续期或 API 接入。</p>';$('details').showModal()};
|
||
switchPage('stores');
|
||
console.assert(escapeHtml('<script>')==='<script>','HTML escaping');
|
||
console.assert(filtered().length===72,'Initial store count');
|
||
query='不存在的店铺';console.assert(filtered().length===0,'Empty search');query='';
|
||
status='已过期';console.assert(filtered().every(r=>r.status==='已过期'),'Status filter');status='';
|
||
console.assert(new Set(stores.map(r=>r.id)).size===stores.length,'Unique record IDs');
|
||
</script>
|
||
</body>
|
||
</html>
|