Files
Store.Management/DesignDemo2/index.html
T
2026-11-10 16:33:51 +08:00

254 lines
40 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>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=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[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>')==='&lt;script&gt;','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>