521 lines
28 KiB
HTML
521 lines
28 KiB
HTML
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title>团队 · 流·Studio</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="assets/restraint.css?v=202605211643">
|
||
<style>
|
||
/* ─── 团队信息卡(深色 banner · 上标题行 + 下统计行)─── */
|
||
.team-banner {
|
||
background: var(--accent-black);
|
||
color: var(--accent-white);
|
||
padding: 22px 28px 24px;
|
||
margin-bottom: 24px;
|
||
position: relative;
|
||
border: 1px solid var(--accent-black);
|
||
border-radius: var(--r-md);
|
||
}
|
||
/* 4 个装订线小十字(2 个 pseudo + 2 个 span)*/
|
||
.team-banner::before, .team-banner::after,
|
||
.team-banner > .corner-tr, .team-banner > .corner-bl {
|
||
content: ''; position: absolute; width: 14px; height: 14px;
|
||
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 22 21' fill='%23e8e8e8'%3E%3Cpath d='M10.5 4C10.5 7.31371 7.81371 10 4.5 10H0.5V11H4.5C7.81371 11 10.5 13.6863 10.5 17V21H11.5V17C11.5 13.6863 14.1863 11 17.5 11H21.5V10H17.5C14.1863 10 11.5 7.31371 11.5 4V0H10.5V4Z'/%3E%3C/svg%3E") no-repeat center;
|
||
background-size: contain; pointer-events: none;
|
||
}
|
||
.team-banner::before { top: -7px; left: -7px; }
|
||
.team-banner::after { bottom: -7px; right: -7px; }
|
||
.team-banner > .corner-tr { top: -7px; right: -7px; }
|
||
.team-banner > .corner-bl { bottom: -7px; left: -7px; }
|
||
|
||
/* 第 1 行:标题 + 主操作 */
|
||
.banner-head { display: flex; align-items: flex-start; gap: 20px; }
|
||
.banner-id { flex: 1; min-width: 0; }
|
||
.banner-id .lbl { font-family: var(--font-mono); font-size: 10.5px; color: rgba(255,255,255,.55); letter-spacing: .06em; text-transform: uppercase; }
|
||
.banner-id .nm { font-size: 22px; font-weight: 700; letter-spacing: -.012em; margin-top: 4px; display: flex; align-items: baseline; gap: 10px; }
|
||
.banner-id .nm .tag { font-size: 10.5px; font-family: var(--font-mono); padding: 2px 8px; background: rgba(255,255,255,.12); border-radius: var(--r-pill); letter-spacing: .04em; font-weight: 500; }
|
||
.banner-id .meta { font-size: 12px; color: rgba(255,255,255,.5); margin-top: 6px; font-family: var(--font-mono); letter-spacing: .02em; }
|
||
.banner-actions { display: flex; gap: 8px; flex-shrink: 0; }
|
||
.banner-actions .btn { background: var(--accent-white); color: var(--accent-black); border-color: var(--accent-white); }
|
||
.banner-actions .btn:hover { background: var(--background-base); }
|
||
.banner-actions .btn-ghost { background: transparent; color: var(--accent-white); border: 1px solid rgba(255,255,255,.25); }
|
||
.banner-actions .btn-ghost:hover { background: rgba(255,255,255,.08); color: var(--accent-white); }
|
||
|
||
/* 分隔线 */
|
||
.banner-divider { height: 1px; background: rgba(255,255,255,.1); margin: 20px 0 18px; }
|
||
|
||
/* 第 2 行:4 列统计 */
|
||
.banner-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 24px; }
|
||
.banner-stats .stat { min-width: 0; }
|
||
.banner-stats .stat .lbl { font-family: var(--font-mono); font-size: 10.5px; color: rgba(255,255,255,.55); letter-spacing: .06em; text-transform: uppercase; }
|
||
.banner-stats .stat .v { font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -.012em; margin-top: 6px; }
|
||
/* color 必须显式写,否则会被 restraint.css 全局 .stat .v 的 color: var(--accent-black) 覆盖成黑字 */
|
||
.banner-stats .stat .v { color: var(--accent-white); }
|
||
.banner-stats .stat .v.warn { color: #FFB870; }
|
||
.banner-stats .stat .sub { font-size: 11px; color: rgba(255,255,255,.5); margin-top: 4px; font-family: var(--font-mono); letter-spacing: .02em; }
|
||
|
||
/* ─── 主体两栏 ─── */
|
||
.team-grid { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 24px; align-items: start; }
|
||
|
||
.pane { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 20px; margin-bottom: 16px; }
|
||
.pane h3 { font-size: 14px; font-weight: 600; margin-bottom: 14px; display: flex; align-items: center; gap: 8px; }
|
||
.pane h3 .ct { font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-48); font-weight: 400; }
|
||
.pane h3 .spacer { margin-left: auto; }
|
||
|
||
/* ─── 成员表 ─── */
|
||
.members-table .av { width: 32px; height: 32px; border-radius: 50%; background: var(--background-lighter); display: inline-grid; place-items: center; font-weight: 600; font-size: 13px; color: var(--accent-black); border: 1px solid var(--border-faint); }
|
||
.members-table .who { display: flex; align-items: center; gap: 10px; }
|
||
.members-table .nm { font-weight: 500; font-size: 13.5px; line-height: 1.2; }
|
||
.members-table .em { font-size: 11.5px; color: var(--black-alpha-48); font-family: var(--font-mono); }
|
||
.members-table .role-pill { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: var(--r-pill); font-size: 11px; font-weight: 500; }
|
||
.members-table .role-pill .dot { width: 6px; height: 6px; border-radius: 50%; }
|
||
.members-table .role-super { background: var(--heat-12); color: var(--heat); }
|
||
.members-table .role-super .dot { background: var(--heat); }
|
||
.members-table .role-admin { background: rgba(30,64,175,.1); color: #1E40AF; }
|
||
.members-table .role-admin .dot { background: #1E40AF; }
|
||
.members-table .role-member { background: var(--background-lighter); color: var(--black-alpha-56); }
|
||
.members-table .role-member .dot { background: var(--black-alpha-56); }
|
||
.members-table .quota-cell { font-variant-numeric: tabular-nums; font-family: var(--font-mono); font-size: 12px; }
|
||
.members-table .quota-cell .lbl { color: var(--black-alpha-48); }
|
||
.members-table .quota-cell .v { color: var(--accent-black); font-weight: 600; }
|
||
.members-table .used-bar { width: 80px; height: 4px; background: var(--background-lighter); border-radius: 2px; overflow: hidden; margin-top: 4px; }
|
||
.members-table .used-bar > span { display: block; height: 100%; background: var(--heat); }
|
||
.members-table .used-bar > span.ok { background: var(--accent-forest); }
|
||
.members-table .used-bar > span.warn { background: #B45309; }
|
||
.members-table .acts { display: flex; gap: 4px; justify-content: flex-end; }
|
||
.members-table .icon-btn-sm { width: 28px; height: 28px; display: inline-grid; place-items: center; border: 1px solid var(--border-faint); border-radius: var(--r-sm); background: var(--surface); cursor: pointer; color: var(--black-alpha-56); transition: all var(--t-base); }
|
||
.members-table .icon-btn-sm:hover { color: var(--heat); border-color: var(--heat-20); }
|
||
.members-table .icon-btn-sm svg { width: 14px; height: 14px; }
|
||
.members-table .icon-btn-sm.danger:hover { color: var(--accent-crimson); border-color: var(--accent-crimson); }
|
||
.members-table tr.pending td { opacity: .65; }
|
||
.members-table tr.pending .nm::after { content: '· 待激活'; font-size: 11px; color: var(--black-alpha-48); margin-left: 6px; font-weight: 400; font-family: var(--font-mono); }
|
||
|
||
/* ─── 角色权限矩阵 ─── */
|
||
.perm-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
|
||
.perm-table th, .perm-table td { padding: 8px 4px; border-bottom: 1px solid var(--border-faint); }
|
||
.perm-table th { font-family: var(--font-mono); font-size: 10.5px; font-weight: 500; color: var(--black-alpha-48); letter-spacing: .04em; text-transform: uppercase; text-align: left; }
|
||
.perm-table th:not(:first-child), .perm-table td:not(:first-child) { text-align: center; }
|
||
.perm-table tbody td:first-child { color: var(--accent-black); }
|
||
.perm-table .yes { color: var(--accent-forest); font-weight: 600; }
|
||
.perm-table .no { color: var(--black-alpha-32); }
|
||
|
||
/* ─── 额度检查规则 ─── */
|
||
.quota-rules { font-size: 12.5px; color: var(--black-alpha-56); line-height: 1.8; }
|
||
.quota-rules .step { display: flex; gap: 10px; padding: 6px 0; align-items: flex-start; }
|
||
.quota-rules .num { width: 18px; height: 18px; border-radius: 50%; background: var(--heat-12); color: var(--heat); font-family: var(--font-mono); font-size: 10.5px; font-weight: 600; display: grid; place-items: center; flex: 0 0 18px; margin-top: 1px; }
|
||
.quota-rules .v { color: var(--accent-black); font-weight: 500; }
|
||
.quota-rules .formula { font-family: var(--font-mono); font-size: 11px; color: var(--heat); background: var(--heat-12); padding: 1px 6px; }
|
||
|
||
/* ─── 邀请 modal ─── */
|
||
.invite-modal { width: min(480px, 92vw); }
|
||
.invite-modal .field { margin-bottom: 14px; }
|
||
.invite-modal label.field-label { display: block; font-size: 12px; color: var(--black-alpha-56); margin-bottom: 6px; font-family: var(--font-mono); letter-spacing: .02em; }
|
||
.invite-modal label.field-label .req { color: var(--accent-crimson); margin-left: 2px; }
|
||
.role-choices { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
||
.role-choice { padding: 12px 14px; border: 1px solid var(--border-faint); border-radius: var(--r-md); cursor: pointer; transition: all var(--t-base); }
|
||
.role-choice:hover { background: var(--background-lighter); }
|
||
.role-choice.selected { border-color: var(--heat); background: var(--heat-12); }
|
||
.role-choice .title { font-size: 13px; font-weight: 600; color: var(--accent-black); }
|
||
.role-choice .desc { font-size: 11px; color: var(--black-alpha-56); margin-top: 2px; font-family: var(--font-mono); letter-spacing: .02em; }
|
||
.quota-input-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
||
.quota-input-row .input { font-variant-numeric: tabular-nums; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="page">
|
||
|
||
<div class="page-head">
|
||
<div>
|
||
<h1>团队管理</h1>
|
||
<div class="sub"><span class="mono">// 成员 · 角色 · 额度 · 共享资产库</span></div>
|
||
</div>
|
||
<div class="actions">
|
||
<button class="btn" onclick="Shell.toast('团队设置', '/team/settings')">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8 2 2 0 0 1-2.8 2.8 1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5 2 2 0 0 1-4 0 1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3 2 2 0 0 1-2.8-2.8 1.7 1.7 0 0 0 .3-1.8"/></svg>
|
||
团队设置
|
||
</button>
|
||
<button class="btn btn-primary" id="open-invite">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M19 8v6M22 11h-6"/></svg>
|
||
邀请成员
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 团队信息 banner -->
|
||
<div class="team-banner">
|
||
<span class="corner-tr" aria-hidden></span><span class="corner-bl" aria-hidden></span>
|
||
|
||
<div class="banner-head">
|
||
<div class="banner-id">
|
||
<div class="lbl">[ TEAM ]</div>
|
||
<div class="nm">小李的店 <span class="tag">企业</span></div>
|
||
<div class="meta">// 团队 ID: T-2026-A8F2 · 创建于 2026-04-12 · 5 名成员</div>
|
||
</div>
|
||
<div class="banner-actions">
|
||
<button class="btn btn-sm" onclick="location.href='account.html'">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v10M9 10h4a1.5 1.5 0 0 1 0 3h-3a1.5 1.5 0 0 0 0 3h4"/></svg>
|
||
充值
|
||
</button>
|
||
<button class="btn btn-ghost btn-sm" onclick="Shell.toast('编辑月限额', '/team/limit')">设置月限额</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="banner-divider"></div>
|
||
|
||
<div class="banner-stats">
|
||
<div class="stat">
|
||
<div class="lbl">[ 充值余额 ]</div>
|
||
<div class="v">¥327.40</div>
|
||
<div class="sub">// 团队总池</div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="lbl">[ 月限额 ]</div>
|
||
<div class="v">¥3,000</div>
|
||
<div class="sub">// 自然月重置</div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="lbl">[ 当月已用 ]</div>
|
||
<div class="v">¥162.60</div>
|
||
<div class="sub">// 占月限 5.4%</div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="lbl">[ 当月剩余 ]</div>
|
||
<div class="v warn">¥2,837.40</div>
|
||
<div class="sub">// 还可生成约 280 个项目</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="team-grid">
|
||
<!-- 左:成员表 -->
|
||
<div>
|
||
<div class="pane" style="padding: 0;">
|
||
<div style="display: flex; align-items: center; padding: 16px 20px; border-bottom: 1px solid var(--border-faint);">
|
||
<h3 style="margin: 0;">成员列表 <span class="ct">// 5 人 · 1 超管 / 1 团管 / 3 成员</span></h3>
|
||
<span class="spacer"></span>
|
||
<input class="input" id="member-search" placeholder="搜索姓名 / 手机号" style="height: 32px; font-size: 12px; width: 220px;">
|
||
</div>
|
||
<table class="t members-table" style="border: 0; border-radius: 0;">
|
||
<thead>
|
||
<tr>
|
||
<th>成员</th>
|
||
<th>角色</th>
|
||
<th>每日额度</th>
|
||
<th>月度额度</th>
|
||
<th style="width: 140px;">当月已用</th>
|
||
<th style="text-align: right; width: 88px;">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="members-tbody">
|
||
<!-- JS 注入 -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右:权限矩阵 + 额度规则 -->
|
||
<div>
|
||
<div class="pane">
|
||
<h3>角色权限</h3>
|
||
<div style="font-size: 12px; color: var(--black-alpha-48); margin-top: -10px; margin-bottom: 12px; font-family: var(--font-mono); letter-spacing: .02em;">// PRD §10.2 权限矩阵节选</div>
|
||
<table class="perm-table">
|
||
<thead>
|
||
<tr><th>能力</th><th>超管</th><th>团管</th><th>成员</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr><td>邀请 / 移除成员</td><td class="yes">✓</td><td class="yes">✓</td><td class="no">—</td></tr>
|
||
<tr><td>设置成员额度</td><td class="yes">✓</td><td class="yes">✓</td><td class="no">—</td></tr>
|
||
<tr><td>团队充值</td><td class="yes">✓</td><td class="no">—</td><td class="no">—</td></tr>
|
||
<tr><td>设置月限额</td><td class="yes">✓</td><td class="no">—</td><td class="no">—</td></tr>
|
||
<tr><td>编辑别人项目</td><td class="yes">✓</td><td class="yes">✓</td><td class="no">—</td></tr>
|
||
<tr><td>团队共享资产库管理</td><td class="yes">✓</td><td class="yes">✓</td><td class="no">仅自传</td></tr>
|
||
<tr><td>查看团队消费明细</td><td class="yes">✓</td><td class="yes">✓</td><td class="no">仅自己</td></tr>
|
||
<tr style="border-bottom: 0;"><td>创建项目 / 用 AI 流程</td><td class="yes">✓</td><td class="yes">✓</td><td class="yes">✓</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="pane">
|
||
<h3>额度预检规则</h3>
|
||
<div style="font-size: 12px; color: var(--black-alpha-48); margin-top: -10px; margin-bottom: 12px; font-family: var(--font-mono); letter-spacing: .02em;">// 任一不通过即拦截</div>
|
||
<div class="quota-rules">
|
||
<div class="step"><span class="num">1</span><span><span class="v">个人日剩余</span> ≥ 任务预估 × <span class="formula">1.2</span></span></div>
|
||
<div class="step"><span class="num">2</span><span><span class="v">个人月剩余</span> ≥ 同上</span></div>
|
||
<div class="step"><span class="num">3</span><span><span class="v">团队月剩余</span> ≥ 同上</span></div>
|
||
<div class="step"><span class="num">4</span><span><span class="v">团队总余额</span> ≥ 同上</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="pane" style="background: var(--heat-12); border-color: var(--heat-20);">
|
||
<h3 style="color: var(--heat);">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/></svg>
|
||
失败不扣费
|
||
</h3>
|
||
<div style="font-size: 12.5px; color: var(--black-alpha-56); line-height: 1.7;">
|
||
所有生成任务<strong style="color: var(--accent-black);">仅在用户 <span class="mono" style="background: var(--surface); padding: 1px 5px; font-family: var(--font-mono); color: var(--heat); font-size: 11.5px;">[ 通过 ]</span> 时才扣费</strong>。失败 / 超时 / 重跑(旧版本作废)一律不扣。
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 邀请成员 modal -->
|
||
<div class="modal-bg" id="invite-bg" onclick="if(event.target===this)Shell.closeModal('invite-bg')">
|
||
<div class="modal invite-modal">
|
||
<span class="corner-tr" aria-hidden></span><span class="corner-bl" aria-hidden></span>
|
||
<div class="modal-h">
|
||
<div class="ic-m">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M19 8v6M22 11h-6"/></svg>
|
||
</div>
|
||
<div class="ti">邀请成员<span>// 短信邀请 · 接受后自动加入团队</span></div>
|
||
</div>
|
||
<div class="modal-b">
|
||
<div class="field">
|
||
<label class="field-label">手机号 <span class="req">*</span></label>
|
||
<input class="input" id="inv-phone" placeholder="例: 138 0013 8000">
|
||
</div>
|
||
<div class="field">
|
||
<label class="field-label">备注姓名(可选)</label>
|
||
<input class="input" id="inv-name" placeholder="例: 张某">
|
||
</div>
|
||
<div class="field">
|
||
<label class="field-label">分配角色 <span class="req">*</span></label>
|
||
<div class="role-choices">
|
||
<div class="role-choice selected" data-role="member">
|
||
<div class="title">成员</div>
|
||
<div class="desc">// 创建项目 + 用资产</div>
|
||
</div>
|
||
<div class="role-choice" data-role="admin">
|
||
<div class="title">团管</div>
|
||
<div class="desc">// 管成员 + 改额度</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="field" style="margin-bottom: 0;">
|
||
<label class="field-label">默认额度</label>
|
||
<div class="quota-input-row">
|
||
<input class="input" id="inv-daily" placeholder="每日 (¥)" value="100">
|
||
<input class="input" id="inv-monthly" placeholder="每月 (¥)" value="2000">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-f">
|
||
<button class="btn" type="button" onclick="Shell.closeModal('invite-bg')">取消</button>
|
||
<button class="btn btn-primary" type="button" id="inv-send">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m22 2-7 20-4-9-9-4z"/><path d="m22 2-11 11"/></svg>
|
||
发送邀请
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 编辑成员 modal -->
|
||
<div class="modal-bg" id="edit-member-bg" onclick="if(event.target===this)Shell.closeModal('edit-member-bg')">
|
||
<div class="modal invite-modal">
|
||
<span class="corner-tr" aria-hidden></span><span class="corner-bl" aria-hidden></span>
|
||
<div class="modal-h">
|
||
<div class="ic-m">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4z"/></svg>
|
||
</div>
|
||
<div class="ti" id="edit-title">编辑成员<span id="edit-sub">// 调整角色 / 额度</span></div>
|
||
</div>
|
||
<div class="modal-b">
|
||
<div class="field">
|
||
<label class="field-label">角色</label>
|
||
<div class="role-choices">
|
||
<div class="role-choice" data-edit-role="member">
|
||
<div class="title">成员</div>
|
||
<div class="desc">// 创建项目 + 用资产</div>
|
||
</div>
|
||
<div class="role-choice" data-edit-role="admin">
|
||
<div class="title">团管</div>
|
||
<div class="desc">// 管成员 + 改额度</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="field" style="margin-bottom: 0;">
|
||
<label class="field-label">额度</label>
|
||
<div class="quota-input-row">
|
||
<input class="input" id="edit-daily" placeholder="每日 (¥)">
|
||
<input class="input" id="edit-monthly" placeholder="每月 (¥)">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-f">
|
||
<button class="btn" type="button" style="margin-right: auto; color: var(--accent-crimson); border-color: var(--accent-crimson);" id="edit-remove">移出团队</button>
|
||
<button class="btn" type="button" onclick="Shell.closeModal('edit-member-bg')">取消</button>
|
||
<button class="btn btn-primary" type="button" id="edit-save">保存</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
<script src="assets/shell.js?v=202605211643"></script>
|
||
<script>
|
||
Shell.render({
|
||
active: 'team',
|
||
crumbs: [{ label: '工作台', href: 'index.html' }, { label: '团队' }]
|
||
});
|
||
|
||
/* ─── 团队成员 mock + 渲染 ─── */
|
||
const ROLE_META = {
|
||
super: { cls: 'role-super', label: '超管' },
|
||
admin: { cls: 'role-admin', label: '团管' },
|
||
member: { cls: 'role-member', label: '成员' },
|
||
};
|
||
|
||
const MEMBERS = [
|
||
{ id: 'u1', av: '李', name: '小李', email: 'li@shop.com', role: 'super', daily: 500, monthly: 10000, used: 162.60, pending: false, creator: true },
|
||
{ id: 'u2', av: '张', name: '张运营', email: 'zhang@shop.com', role: 'admin', daily: 300, monthly: 6000, used: 98.40, pending: false },
|
||
{ id: 'u3', av: '王', name: '王小姐', email: 'wang@shop.com', role: 'member', daily: 100, monthly: 2000, used: 45.20, pending: false },
|
||
{ id: 'u4', av: '陈', name: '陈策划', email: 'chen@shop.com', role: 'member', daily: 100, monthly: 2000, used: 12.80, pending: false },
|
||
{ id: 'u5', av: '林', name: '林新人', email: '186****1102', role: 'member', daily: 100, monthly: 2000, used: 0, pending: true },
|
||
];
|
||
|
||
function fmtMoney(n) { return '¥' + n.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ','); }
|
||
function usedClass(pct) { if (pct < 0.5) return 'ok'; if (pct < 0.85) return ''; return 'warn'; }
|
||
|
||
function renderMembers(filter = '') {
|
||
const tb = document.getElementById('members-tbody');
|
||
const list = MEMBERS.filter(m => {
|
||
if (!filter) return true;
|
||
const q = filter.toLowerCase();
|
||
return m.name.toLowerCase().includes(q) || m.email.toLowerCase().includes(q);
|
||
});
|
||
tb.innerHTML = list.map(m => {
|
||
const r = ROLE_META[m.role];
|
||
const pct = m.monthly > 0 ? m.used / m.monthly : 0;
|
||
return `
|
||
<tr data-id="${m.id}"${m.pending ? ' class="pending"' : ''}>
|
||
<td>
|
||
<div class="who">
|
||
<div class="av">${m.av}</div>
|
||
<div>
|
||
<div class="nm">${m.name}${m.creator ? ' <span style="font-family:var(--font-mono);font-size:10px;color:var(--black-alpha-48);">· 创建者</span>' : ''}</div>
|
||
<div class="em">${m.email}</div>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
<td><span class="role-pill ${r.cls}"><span class="dot"></span>${r.label}</span></td>
|
||
<td><span class="quota-cell"><span class="v">${fmtMoney(m.daily)}</span></span></td>
|
||
<td><span class="quota-cell"><span class="v">${fmtMoney(m.monthly)}</span></span></td>
|
||
<td>
|
||
<div class="quota-cell"><span class="v">${fmtMoney(m.used)}</span> <span class="lbl">/ ${(pct * 100).toFixed(0)}%</span></div>
|
||
<div class="used-bar"><span class="${usedClass(pct)}" style="width: ${Math.min(100, pct * 100).toFixed(1)}%"></span></div>
|
||
</td>
|
||
<td>
|
||
<div class="acts">
|
||
${m.creator ? '<span style="font-family:var(--font-mono);font-size:10.5px;color:var(--black-alpha-32);align-self:center;">不可编辑</span>' : `
|
||
<button class="icon-btn-sm" data-act="edit" data-id="${m.id}" title="编辑">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4z"/></svg>
|
||
</button>
|
||
<button class="icon-btn-sm danger" data-act="remove" data-id="${m.id}" title="移出">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
||
</button>
|
||
`}
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
}).join('');
|
||
|
||
tb.querySelectorAll('[data-act="edit"]').forEach(btn => {
|
||
btn.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
openEdit(btn.dataset.id);
|
||
});
|
||
});
|
||
tb.querySelectorAll('[data-act="remove"]').forEach(btn => {
|
||
btn.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
const m = MEMBERS.find(x => x.id === btn.dataset.id);
|
||
if (!m) return;
|
||
if (confirm('确定将「' + m.name + '」移出团队?')) {
|
||
const i = MEMBERS.findIndex(x => x.id === m.id);
|
||
MEMBERS.splice(i, 1);
|
||
Shell.toast('已移除成员', m.name);
|
||
renderMembers(document.getElementById('member-search').value);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
renderMembers();
|
||
|
||
document.getElementById('member-search').addEventListener('input', e => {
|
||
renderMembers(e.target.value);
|
||
});
|
||
|
||
/* ─── 邀请 modal ─── */
|
||
document.getElementById('open-invite').addEventListener('click', () => {
|
||
Shell.openModal('invite-bg');
|
||
});
|
||
document.querySelectorAll('#invite-bg .role-choice').forEach(c => {
|
||
c.addEventListener('click', () => {
|
||
document.querySelectorAll('#invite-bg .role-choice').forEach(x => x.classList.remove('selected'));
|
||
c.classList.add('selected');
|
||
});
|
||
});
|
||
document.getElementById('inv-send').addEventListener('click', () => {
|
||
const phone = document.getElementById('inv-phone').value.trim();
|
||
if (!phone) { Shell.toast('请填手机号', '邀请失败'); return; }
|
||
const role = document.querySelector('#invite-bg .role-choice.selected')?.dataset.role || 'member';
|
||
const name = document.getElementById('inv-name').value.trim() || '待激活成员';
|
||
const daily = Number(document.getElementById('inv-daily').value) || 100;
|
||
const monthly = Number(document.getElementById('inv-monthly').value) || 2000;
|
||
MEMBERS.push({
|
||
id: 'u' + Date.now(),
|
||
av: name[0] || '新',
|
||
name, email: phone, role,
|
||
daily, monthly, used: 0, pending: true,
|
||
});
|
||
renderMembers();
|
||
Shell.closeModal('invite-bg');
|
||
Shell.toast('邀请已发送', phone);
|
||
document.getElementById('inv-phone').value = '';
|
||
document.getElementById('inv-name').value = '';
|
||
});
|
||
|
||
/* ─── 编辑 modal ─── */
|
||
let editingId = null;
|
||
function openEdit(id) {
|
||
const m = MEMBERS.find(x => x.id === id);
|
||
if (!m) return;
|
||
editingId = id;
|
||
document.getElementById('edit-title').innerHTML = '编辑「' + m.name + '」<span id="edit-sub">// ' + m.email + '</span>';
|
||
document.querySelectorAll('#edit-member-bg .role-choice').forEach(c => {
|
||
c.classList.toggle('selected', c.dataset.editRole === (m.role === 'super' ? 'admin' : m.role));
|
||
});
|
||
document.getElementById('edit-daily').value = m.daily;
|
||
document.getElementById('edit-monthly').value = m.monthly;
|
||
Shell.openModal('edit-member-bg');
|
||
}
|
||
document.querySelectorAll('#edit-member-bg .role-choice').forEach(c => {
|
||
c.addEventListener('click', () => {
|
||
document.querySelectorAll('#edit-member-bg .role-choice').forEach(x => x.classList.remove('selected'));
|
||
c.classList.add('selected');
|
||
});
|
||
});
|
||
document.getElementById('edit-save').addEventListener('click', () => {
|
||
const m = MEMBERS.find(x => x.id === editingId);
|
||
if (!m) return;
|
||
m.role = document.querySelector('#edit-member-bg .role-choice.selected')?.dataset.editRole || m.role;
|
||
m.daily = Number(document.getElementById('edit-daily').value) || m.daily;
|
||
m.monthly = Number(document.getElementById('edit-monthly').value) || m.monthly;
|
||
Shell.closeModal('edit-member-bg');
|
||
Shell.toast('已保存', m.name);
|
||
renderMembers(document.getElementById('member-search').value);
|
||
});
|
||
document.getElementById('edit-remove').addEventListener('click', () => {
|
||
const m = MEMBERS.find(x => x.id === editingId);
|
||
if (!m) return;
|
||
if (confirm('确定将「' + m.name + '」移出团队?')) {
|
||
const i = MEMBERS.findIndex(x => x.id === m.id);
|
||
MEMBERS.splice(i, 1);
|
||
Shell.closeModal('edit-member-bg');
|
||
Shell.toast('已移除', m.name);
|
||
renderMembers(document.getElementById('member-search').value);
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|