UI 设计 e3afa6659b
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5s
feat(team): banner 右栏空白补「团队动态」卡 · 最近 7 条成员行为流
2026-05-21 17:15:45 +08:00

606 lines
33 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">
<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 · 上标题行 + 下统计行)─── */
/* 顶部行:banner(左)+ 团队动态(右),与下方 team-grid 同列宽对齐 */
.team-top { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 24px; margin-bottom: 24px; align-items: stretch; }
.team-banner {
background: var(--accent-black);
color: var(--accent-white);
padding: 22px 28px 24px;
position: relative;
border: 1px solid var(--accent-black);
border-radius: var(--r-md);
}
/* ─── 团队动态卡(贴 banner 右边)─── */
.team-feed { background: var(--surface); border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 16px 18px; display: flex; flex-direction: column; min-width: 0; }
.team-feed .h { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
.team-feed .h h3 { font-size: 13.5px; font-weight: 600; margin: 0; }
.team-feed .h .ct { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); }
.team-feed .h .more { margin-left: auto; font-family: var(--font-mono); font-size: 11px; color: var(--heat); text-decoration: none; cursor: pointer; }
.team-feed .feed-list { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; max-height: 240px; padding-right: 4px; scrollbar-width: thin; }
.team-feed .feed-list::-webkit-scrollbar { width: 4px; }
.team-feed .feed-list::-webkit-scrollbar-thumb { background: var(--border-faint); border-radius: 2px; }
.team-feed .feed-item { display: grid; grid-template-columns: 24px minmax(0, 1fr); gap: 10px; align-items: start; }
.team-feed .feed-item .av { width: 24px; height: 24px; border-radius: 50%; background: var(--background-lighter); border: 1px solid var(--border-faint); display: grid; place-items: center; font-size: 11px; font-weight: 600; color: var(--accent-black); }
.team-feed .feed-item .txt { font-size: 12.5px; line-height: 1.45; color: var(--accent-black); min-width: 0; }
.team-feed .feed-item .txt .who { font-weight: 600; }
.team-feed .feed-item .txt .act { color: var(--black-alpha-56); margin: 0 3px; }
.team-feed .feed-item .txt .obj { color: var(--heat); }
.team-feed .feed-item .txt .obj-money { color: var(--accent-forest); font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
.team-feed .feed-item .ts { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); margin-top: 2px; letter-spacing: .02em; }
/* 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-top">
<!-- 团队信息 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>
<!-- 团队动态(banner 右栏)-->
<div class="team-feed">
<div class="h">
<h3>团队动态</h3>
<span class="ct">// 最近 12 条</span>
<a class="more" onclick="Shell.toast('完整动态', '/team/feed')">全部 →</a>
</div>
<div class="feed-list">
<div class="feed-item">
<div class="av"></div>
<div>
<div class="txt"><span class="who">张运营</span><span class="act">完成视频</span><span class="obj">补水面膜 · v3</span></div>
<div class="ts">10 分钟前</div>
</div>
</div>
<div class="feed-item">
<div class="av"></div>
<div>
<div class="txt"><span class="who">王小姐</span><span class="act">上传到资产库</span><span class="obj">林夕 · 主播图</span></div>
<div class="ts">28 分钟前</div>
</div>
</div>
<div class="feed-item">
<div class="av"></div>
<div>
<div class="txt"><span class="who">小李</span><span class="act">邀请新成员</span><span class="obj">林新人</span></div>
<div class="ts">2 小时前</div>
</div>
</div>
<div class="feed-item">
<div class="av"></div>
<div>
<div class="txt"><span class="who">陈策划</span><span class="act">创建项目</span><span class="obj">蓝牙耳机 · 开箱测评</span></div>
<div class="ts">4 小时前</div>
</div>
</div>
<div class="feed-item">
<div class="av"></div>
<div>
<div class="txt"><span class="who">张运营</span><span class="act">采用故事板</span><span class="obj">补水面膜 · 场 3 · v2</span></div>
<div class="ts">昨天 18:32</div>
</div>
</div>
<div class="feed-item">
<div class="av"></div>
<div>
<div class="txt"><span class="who">小李</span><span class="act">团队充值</span><span class="obj-money">+¥500.00</span></div>
<div class="ts">昨天 11:02</div>
</div>
</div>
<div class="feed-item">
<div class="av"></div>
<div>
<div class="txt"><span class="who">王小姐</span><span class="act">删除资产</span><span class="obj">透真防晒 · 旧版主图</span></div>
<div class="ts">2 天前</div>
</div>
</div>
</div>
</div>
</div><!-- /.team-top -->
<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>