All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 6s
页面 (电商AI平台/) - account / team / settings / index / products / projects: 累积迭代 - restraint.css: 设计 token 补充 - login.html / register.html: 新增登录注册页 - _ARCHIVE.md: 归档说明 Next.js 工程骨架 - app/ + components/: 新一代 SPA 雏形 (page / layout / sidebar / topbar / GridBg / Icon) - package.json / package-lock.json / next.config.mjs / tsconfig.json / postcss.config.mjs / next-env.d.ts 历史归档 / 文档 - v1/: 原 V1 静态稿镜像 (含 mockup-A/B/C) - PRD.md / deployment-guide.md / _check.html - ui参考/ / screenshots/ 杂项 - .gitignore 调整 - 删除根 README.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1088 lines
61 KiB
HTML
1088 lines
61 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 · 上标题行 + 下统计行)─── */
|
|
/* 顶部行: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); }
|
|
|
|
/* ─── 成员详情 modal ─── */
|
|
.member-detail-modal { width: min(760px, 92vw); max-width: min(760px, 92vw); }
|
|
/* 给 modal-h 留出右侧 X 的位置 */
|
|
.member-detail-modal .modal-h { padding-right: 56px; }
|
|
/* 模态右上角关闭 X */
|
|
.md-x { position: absolute; top: 14px; right: 16px; width: 28px; height: 28px; border-radius: var(--r-sm); border: 1px solid var(--border-faint); background: var(--surface); color: var(--black-alpha-56); display: grid; place-items: center; cursor: pointer; transition: all var(--t-base); z-index: 2; }
|
|
.md-x:hover { color: var(--accent-black); border-color: var(--black-alpha-32); }
|
|
.md-x svg { width: 13px; height: 13px; }
|
|
|
|
.md-header { display: flex; align-items: center; gap: 14px; padding: 4px 0 16px; border-bottom: 1px solid var(--border-faint); margin-bottom: 18px; }
|
|
.md-header .av-big { width: 52px; height: 52px; border-radius: 50%; background: var(--background-lighter); border: 1px solid var(--border-faint); display: grid; place-items: center; font-size: 20px; font-weight: 600; color: var(--accent-black); flex-shrink: 0; }
|
|
.md-header .who-main { min-width: 0; flex: 1; }
|
|
.md-header .nm-big { font-size: 16px; font-weight: 600; color: var(--accent-black); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
.md-header .em-big { font-size: 12px; color: var(--black-alpha-48); font-family: var(--font-mono); margin-top: 4px; letter-spacing: .02em; }
|
|
.md-header .role-pill { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: var(--r-pill); font-size: 11px; font-weight: 500; }
|
|
.md-header .role-pill .dot { width: 6px; height: 6px; border-radius: 50%; }
|
|
.md-header .role-pill.role-super { background: var(--heat-12); color: var(--heat); }
|
|
.md-header .role-pill.role-super .dot { background: var(--heat); }
|
|
.md-header .role-pill.role-admin { background: rgba(30,64,175,.1); color: #1E40AF; }
|
|
.md-header .role-pill.role-admin .dot { background: #1E40AF; }
|
|
.md-header .role-pill.role-member { background: var(--background-lighter); color: var(--black-alpha-56); }
|
|
.md-header .role-pill.role-member .dot { background: var(--black-alpha-56); }
|
|
.md-header .tag-pending { font-family: var(--font-mono); font-size: 10.5px; padding: 1px 6px; background: rgba(180,83,9,.12); color: #B45309; border-radius: var(--r-sm); letter-spacing: .04em; }
|
|
/* 创建者:mono 小 tag,不再用 "·" 字符串拼接 */
|
|
.md-header .tag-creator { font-family: var(--font-mono); font-size: 10.5px; padding: 1px 6px; background: var(--background-lighter); color: var(--black-alpha-56); border: 1px solid var(--border-faint); border-radius: var(--r-sm); letter-spacing: .04em; font-weight: 500; }
|
|
|
|
.md-section { margin-bottom: 18px; }
|
|
.md-section:last-child { margin-bottom: 0; }
|
|
.md-section-h { font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .06em; text-transform: uppercase; margin-bottom: 10px; }
|
|
.md-section-h .hint { margin-left: 6px; font-family: var(--font-mono); font-size: 10px; color: var(--black-alpha-32); font-weight: 400; letter-spacing: .02em; text-transform: none; }
|
|
|
|
/* 角色权限速览 */
|
|
.md-perm-list { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 14px; background: var(--background-lighter); border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 12px 14px; }
|
|
.md-perm-list .pi { display: flex; align-items: baseline; gap: 8px; font-size: 12.5px; color: var(--accent-black); line-height: 1.4; }
|
|
.md-perm-list .pi .ck { flex: 0 0 12px; color: var(--accent-forest); font-family: var(--font-mono); font-size: 11px; font-weight: 600; }
|
|
.md-perm-list .pi.deny .ck { color: var(--black-alpha-32); }
|
|
.md-perm-list .pi.deny { color: var(--black-alpha-48); }
|
|
.md-perm-list .pi .lb { flex: 1; min-width: 0; }
|
|
.md-perm-list .pi .lb em { font-style: normal; color: var(--black-alpha-48); font-family: var(--font-mono); font-size: 11px; margin-left: 4px; }
|
|
|
|
/* 额度日消耗子行 */
|
|
.md-daily-row { display: flex; gap: 16px; align-items: center; margin-top: 8px; font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-56); letter-spacing: .02em; }
|
|
.md-daily-row .k { color: var(--black-alpha-48); }
|
|
.md-daily-row .v { color: var(--accent-black); font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
.md-daily-row .v.warn { color: #B45309; }
|
|
.md-daily-row .sep { color: var(--black-alpha-32); }
|
|
|
|
.md-quota-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; }
|
|
.md-quota-box { background: var(--background-lighter); border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 12px 14px; }
|
|
.md-quota-box .lbl { font-size: 11px; color: var(--black-alpha-48); font-family: var(--font-mono); letter-spacing: .02em; }
|
|
.md-quota-box .v { font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--accent-black); margin-top: 4px; }
|
|
.md-quota-box .v.warn { color: #B45309; }
|
|
.md-progress { margin-top: 10px; }
|
|
.md-progress .label { display: flex; justify-content: space-between; font-size: 11.5px; color: var(--black-alpha-56); font-family: var(--font-mono); margin-bottom: 4px; }
|
|
.md-progress .bar { height: 6px; background: var(--background-lighter); border-radius: 3px; overflow: hidden; border: 1px solid var(--border-faint); }
|
|
.md-progress .bar > span { display: block; height: 100%; background: var(--heat); }
|
|
.md-progress .bar > span.ok { background: var(--accent-forest); }
|
|
.md-progress .bar > span.warn { background: #B45309; }
|
|
|
|
.md-stats-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
|
|
.md-stat { background: var(--background-lighter); border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 10px 12px; text-align: center; }
|
|
.md-stat .v { font-size: 18px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--accent-black); }
|
|
.md-stat .lbl { font-size: 11px; color: var(--black-alpha-48); font-family: var(--font-mono); margin-top: 2px; letter-spacing: .02em; }
|
|
|
|
.md-activity-list { display: flex; flex-direction: column; gap: 8px; max-height: 180px; overflow-y: auto; padding-right: 4px; }
|
|
.md-activity-list::-webkit-scrollbar { width: 4px; }
|
|
.md-activity-list::-webkit-scrollbar-thumb { background: var(--border-faint); border-radius: 2px; }
|
|
.md-activity-item { display: grid; grid-template-columns: 70px 1fr; gap: 10px; align-items: baseline; font-size: 12.5px; line-height: 1.4; }
|
|
.md-activity-item .ts { font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-48); letter-spacing: .02em; }
|
|
.md-activity-item .act { color: var(--black-alpha-56); }
|
|
.md-activity-item .obj { color: var(--heat); }
|
|
|
|
/* ─── 消费明细 section ─── */
|
|
.md-section-h .right { margin-left: auto; font-family: var(--font-mono); font-size: 11.5px; color: var(--accent-black); font-weight: 600; letter-spacing: 0; text-transform: none; font-variant-numeric: tabular-nums; }
|
|
.md-section-h { display: flex; align-items: baseline; }
|
|
|
|
.md-stage-list { display: flex; flex-direction: column; gap: 10px; background: var(--background-lighter); border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 14px 16px; }
|
|
.md-stage-row { display: grid; grid-template-columns: 12px 1fr; column-gap: 10px; row-gap: 5px; align-items: center; }
|
|
.md-stage-row .swatch { width: 8px; height: 8px; border-radius: 2px; align-self: center; }
|
|
.md-stage-row .swatch.s-video { background: var(--heat); }
|
|
.md-stage-row .swatch.s-storyboard { background: var(--accent-forest); }
|
|
.md-stage-row .swatch.s-asset { background: var(--black-alpha-56); }
|
|
.md-stage-row .swatch.s-script { background: var(--black-alpha-32); }
|
|
.md-stage-row .line { display: flex; align-items: baseline; font-size: 12.5px; min-width: 0; }
|
|
.md-stage-row .nm { color: var(--accent-black); }
|
|
.md-stage-row .pct { margin-left: 8px; font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); font-variant-numeric: tabular-nums; }
|
|
.md-stage-row .amt { margin-left: auto; font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: 600; color: var(--accent-black); font-size: 12.5px; }
|
|
.md-stage-row .bar { grid-column: 2; height: 4px; background: var(--surface); border-radius: 2px; overflow: hidden; }
|
|
.md-stage-row .bar > span { display: block; height: 100%; transition: width .3s ease; }
|
|
.md-stage-row .bar > span.s-video { background: var(--heat); }
|
|
.md-stage-row .bar > span.s-storyboard { background: var(--accent-forest); }
|
|
.md-stage-row .bar > span.s-asset { background: var(--black-alpha-56); }
|
|
.md-stage-row .bar > span.s-script { background: var(--black-alpha-32); }
|
|
|
|
/* 流水明细切换:按钮化 */
|
|
.md-tx-bar { display: flex; align-items: center; margin-top: 14px; padding: 8px 10px; background: var(--background-lighter); border: 1px solid var(--border-faint); border-radius: var(--r-sm); }
|
|
.md-tx-bar .ct { font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-56); letter-spacing: .02em; }
|
|
.md-tx-bar .ct b { color: var(--accent-black); font-weight: 600; }
|
|
.md-tx-toggle { display: inline-flex; align-items: center; gap: 5px; font-family: var(--font-mono); font-size: 11px; color: var(--heat); cursor: pointer; margin-left: auto; user-select: none; padding: 4px 10px; border-radius: var(--r-sm); transition: background var(--t-base); }
|
|
.md-tx-toggle:hover { background: var(--heat-12); }
|
|
.md-tx-toggle svg { width: 11px; height: 11px; transition: transform var(--t-base); }
|
|
.md-tx-toggle.expanded svg { transform: rotate(180deg); }
|
|
|
|
.md-tx-table { display: none; flex-direction: column; max-height: 220px; overflow-y: auto; margin-top: 10px; border: 1px solid var(--border-faint); border-radius: var(--r-sm); background: var(--surface); }
|
|
.md-tx-table.show { display: flex; }
|
|
.md-tx-table::-webkit-scrollbar { width: 4px; }
|
|
.md-tx-table::-webkit-scrollbar-thumb { background: var(--border-faint); border-radius: 2px; }
|
|
.md-tx-row { display: grid; grid-template-columns: 96px 1fr 110px 78px; gap: 10px; padding: 9px 12px; font-size: 12px; border-bottom: 1px solid var(--border-faint); align-items: center; }
|
|
.md-tx-row:last-child { border-bottom: 0; }
|
|
.md-tx-row.head { background: var(--black-alpha-3); font-family: var(--font-mono); font-size: 10.5px; color: var(--black-alpha-48); letter-spacing: .04em; text-transform: uppercase; padding-top: 8px; padding-bottom: 8px; }
|
|
.md-tx-row .ts { font-family: var(--font-mono); font-size: 11px; color: var(--black-alpha-48); }
|
|
.md-tx-row .proj { color: var(--accent-black); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
.md-tx-row .type { color: var(--black-alpha-56); font-size: 11.5px; }
|
|
.md-tx-row .amt { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: 600; color: var(--accent-black); }
|
|
.md-tx-row .amt.pos { color: var(--accent-forest); }
|
|
.md-tx-row .amt.zero { color: var(--black-alpha-32); font-weight: 500; }
|
|
|
|
.md-meta { font-size: 12px; color: var(--black-alpha-56); font-family: var(--font-mono); letter-spacing: .02em; padding-top: 14px; margin-top: 14px; border-top: 1px solid var(--border-faint); }
|
|
|
|
/* ─── 角色权限矩阵 ─── */
|
|
.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>
|
|
</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-email" type="email" autocomplete="off" placeholder="例: name@company.com">
|
|
</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>
|
|
|
|
<!-- 成员详情 modal · 点击成员行打开 -->
|
|
<div class="modal-bg" id="member-detail-bg" onclick="if(event.target===this)Shell.closeModal('member-detail-bg')">
|
|
<div class="modal member-detail-modal">
|
|
<span class="corner-tr" aria-hidden></span><span class="corner-bl" aria-hidden></span>
|
|
<button class="md-x" type="button" aria-label="关闭" onclick="Shell.closeModal('member-detail-bg')">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
|
</button>
|
|
<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"><circle cx="12" cy="8" r="4"/><path d="M4 21c0-4.4 3.6-8 8-8s8 3.6 8 8"/></svg>
|
|
</div>
|
|
<div class="ti">成员详情<span id="md-sub">// 角色权限 / 额度 / 消费明细 / 团队贡献 / 最近活动</span></div>
|
|
</div>
|
|
<div class="modal-b">
|
|
<div class="md-header">
|
|
<div class="av-big" id="md-av">—</div>
|
|
<div class="who-main">
|
|
<div class="nm-big">
|
|
<span id="md-name">—</span>
|
|
<span class="role-pill" id="md-role"><span class="dot"></span><span id="md-role-label">—</span></span>
|
|
<span class="tag-creator" id="md-creator" style="display:none">创建者</span>
|
|
<span class="tag-pending" id="md-pending" style="display:none">待激活</span>
|
|
</div>
|
|
<div class="em-big" id="md-email">—</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="md-section">
|
|
<div class="md-section-h">// 角色权限 · <span id="md-perm-role">—</span> 可以<span class="hint">PRD §10.2 权限矩阵</span></div>
|
|
<div class="md-perm-list" id="md-perm-list">
|
|
<!-- JS 注入 4 条 -->
|
|
</div>
|
|
</div>
|
|
|
|
<div class="md-section">
|
|
<div class="md-section-h">// 额度使用<span class="hint">PRD §7.2 四层额度</span></div>
|
|
<div class="md-quota-grid">
|
|
<div class="md-quota-box"><div class="lbl">每日额度</div><div class="v" id="md-daily">—</div></div>
|
|
<div class="md-quota-box"><div class="lbl">月度额度</div><div class="v" id="md-monthly">—</div></div>
|
|
<div class="md-quota-box"><div class="lbl">当月已用</div><div class="v" id="md-used">—</div></div>
|
|
</div>
|
|
<div class="md-daily-row">
|
|
<span><span class="k">今日已用</span> <span class="v" id="md-used-today">¥0.00</span></span>
|
|
<span class="sep">·</span>
|
|
<span><span class="k">今日剩余</span> <span class="v" id="md-left-today">¥0.00</span></span>
|
|
</div>
|
|
<div class="md-progress">
|
|
<div class="label"><span>当月消耗</span><span id="md-pct">0%</span></div>
|
|
<div class="bar"><span id="md-bar" style="width: 0%"></span></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="md-section" id="md-cost-section">
|
|
<div class="md-section-h">// 消费明细 · 当月按阶段拆分<span class="hint">仅超管 / 团管可见</span><span class="right" id="md-cost-total">¥0.00</span></div>
|
|
<div class="md-stage-list" id="md-stage-list">
|
|
<!-- JS 注入 -->
|
|
</div>
|
|
<div class="md-tx-bar">
|
|
<span class="ct">// 流水明细 · 共 <b id="md-tx-count">0</b> 条</span>
|
|
<span class="md-tx-toggle" id="md-tx-toggle" role="button" tabindex="0">
|
|
<span class="md-tx-label">展开</span>
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M4 6l4 4 4-4"/></svg>
|
|
</span>
|
|
</div>
|
|
<div class="md-tx-table" id="md-tx-table">
|
|
<div class="md-tx-row head">
|
|
<span>时间</span>
|
|
<span>项目</span>
|
|
<span>类型</span>
|
|
<span style="text-align:right">金额</span>
|
|
</div>
|
|
<!-- JS 注入 rows -->
|
|
</div>
|
|
</div>
|
|
|
|
<div class="md-section">
|
|
<div class="md-section-h">// 团队贡献</div>
|
|
<div class="md-stats-row">
|
|
<div class="md-stat"><div class="v" id="md-projects-active">0</div><div class="lbl">进行中项目</div></div>
|
|
<div class="md-stat"><div class="v" id="md-projects-done">0</div><div class="lbl">已完成项目</div></div>
|
|
<div class="md-stat"><div class="v" id="md-assets">0</div><div class="lbl">上传共享素材</div></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="md-section">
|
|
<div class="md-section-h">// 最近活动<span class="hint" id="md-last-active">· 最近活跃 —</span></div>
|
|
<div class="md-activity-list" id="md-activity">
|
|
<!-- JS 注入 -->
|
|
</div>
|
|
</div>
|
|
|
|
<div class="md-meta" id="md-meta">// —</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="md-remove" data-creator-hide>移出团队</button>
|
|
<button class="btn" type="button" onclick="Shell.closeModal('member-detail-bg')">关闭</button>
|
|
<button class="btn btn-primary" type="button" id="md-edit" data-creator-hide>编辑成员</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: '成员' },
|
|
};
|
|
|
|
/* PRD §10.2 权限矩阵 · 4 条精简到弹窗速览(grant + deny 各 2 条) */
|
|
const ROLE_PERMS = {
|
|
super: [
|
|
{ ok: true, txt: '团队设置 · 月限额 / 名称' },
|
|
{ ok: true, txt: '充值 · 划拨团队总额度' },
|
|
{ ok: true, txt: '任命团管 · 调整任意成员额度' },
|
|
{ ok: true, txt: '查看团队全部消费明细 + 财务' },
|
|
],
|
|
admin: [
|
|
{ ok: true, txt: '邀请 / 移除成员 + 设置额度' },
|
|
{ ok: true, txt: '管理团队共享资产库' },
|
|
{ ok: true, txt: '查看团队全部消费明细' },
|
|
{ ok: false, txt: '团队设置 / 充值 · 仅超管' },
|
|
],
|
|
member: [
|
|
{ ok: true, txt: '创建项目 + 流水线操作' },
|
|
{ ok: true, txt: '上传 / 引用团队共享资产' },
|
|
{ ok: false, txt: '邀请 / 移除成员 · 仅超管 / 团管' },
|
|
{ ok: false, txt: '消费明细 · 仅本人可见' },
|
|
],
|
|
};
|
|
|
|
const MEMBERS = [
|
|
{ id: 'u1', av: '李', name: '小李', email: 'li@shop.com', role: 'super', daily: 500, monthly: 10000, used: 162.60, usedToday: 0.45, lastActive: '15 分钟前', pending: false, creator: true,
|
|
joinDate: '2026-04-12', inviter: '—', projectsActive: 2, projectsDone: 14, assetsUploaded: 32,
|
|
activity: [
|
|
{ ts: '昨天 11:02', act: '团队充值', obj: '+¥500.00' },
|
|
{ ts: '2 天前', act: '邀请新成员', obj: '林新人' },
|
|
{ ts: '3 天前', act: '创建项目', obj: '春日新品 · 立体口红' },
|
|
],
|
|
byStage: { video: 98.40, storyboard: 36.00, asset: 21.00, script: 7.20 },
|
|
transactions: [
|
|
{ ts: '05.21 14:32', proj: '补水面膜 · v3', type: '视频片段 · 1 镜', amount: -0.45 },
|
|
{ ts: '05.20 18:21', proj: '透真防晒 · 通勤对比', type: '视频片段 · 6 镜', amount: -1.20 },
|
|
{ ts: '05.20 11:02', proj: '充值', type: '微信支付', amount: 500.00 },
|
|
{ ts: '05.19 16:08', proj: '补水面膜 · v3', type: '故事板 image-2', amount: -0.45 },
|
|
{ ts: '05.19 14:02', proj: '补水面膜 · v3', type: '脚本 LLM · 2.4k', amount: -0.04 },
|
|
{ ts: '05.19 13:38', proj: '补水面膜 · v3', type: '基础资产 · 5 张', amount: -1.05 },
|
|
{ ts: '05.18 15:42', proj: '咖啡冻干 · 早八', type: '故事板生成失败', amount: 0 },
|
|
{ ts: '05.17 10:30', proj: '瑜伽裤 · 通勤穿搭', type: '项目导出', amount: -3.20 },
|
|
] },
|
|
{ id: 'u2', av: '张', name: '张运营', email: 'zhang@shop.com', role: 'admin', daily: 300, monthly: 6000, used: 98.40, usedToday: 0.45, lastActive: '10 分钟前', pending: false,
|
|
joinDate: '2026-04-18', inviter: '小李', projectsActive: 3, projectsDone: 8, assetsUploaded: 18,
|
|
activity: [
|
|
{ ts: '10 分钟前', act: '完成视频', obj: '补水面膜 · v3' },
|
|
{ ts: '昨天 18:32', act: '采用故事板', obj: '场 3 · v2' },
|
|
{ ts: '2 天前', act: '创建项目', obj: '蓝牙耳机 · 开箱测评' },
|
|
],
|
|
byStage: { video: 60.00, storyboard: 22.00, asset: 12.00, script: 4.40 },
|
|
transactions: [
|
|
{ ts: '05.21 14:08', proj: '补水面膜 · v3', type: '视频片段 · 1 镜', amount: -0.45 },
|
|
{ ts: '05.20 21:42', proj: '蓝牙耳机 · 开箱测评', type: '视频片段 · 6 镜', amount: -1.20 },
|
|
{ ts: '05.20 16:00', proj: '蓝牙耳机 · 开箱测评', type: '故事板 image-2', amount: -0.45 },
|
|
{ ts: '05.19 11:18', proj: '补水面膜 · v3', type: '故事板 image-2', amount: -0.45 },
|
|
{ ts: '05.18 09:42', proj: '蓝牙耳机 · 开箱测评', type: '基础资产 · 4 张', amount: -0.84 },
|
|
{ ts: '05.17 14:38', proj: '蓝牙耳机 · 开箱测评', type: '脚本 LLM · 1.8k', amount: -0.03 },
|
|
] },
|
|
{ id: 'u3', av: '王', name: '王小姐', email: 'wang@shop.com', role: 'member', daily: 100, monthly: 2000, used: 45.20, usedToday: 0, lastActive: '28 分钟前', pending: false,
|
|
joinDate: '2026-04-22', inviter: '小李', projectsActive: 1, projectsDone: 4, assetsUploaded: 12,
|
|
activity: [
|
|
{ ts: '28 分钟前', act: '上传到资产库', obj: '林夕 · 主播图' },
|
|
{ ts: '2 天前', act: '删除资产', obj: '透真防晒 · 旧版主图' },
|
|
{ ts: '5 天前', act: '完成视频', obj: '透真防晒 · 通勤对比' },
|
|
],
|
|
byStage: { video: 28.00, storyboard: 10.00, asset: 5.00, script: 2.20 },
|
|
transactions: [
|
|
{ ts: '05.16 19:38', proj: '透真防晒 · 通勤对比', type: '视频片段 · 4 镜', amount: -0.80 },
|
|
{ ts: '05.16 11:42', proj: '透真防晒 · 通勤对比', type: '故事板 image-2', amount: -0.45 },
|
|
{ ts: '05.15 16:08', proj: '透真防晒 · 通勤对比', type: '基础资产 · 3 张', amount: -0.63 },
|
|
{ ts: '05.15 09:30', proj: '透真防晒 · 通勤对比', type: '脚本 LLM · 1.5k', amount: -0.02 },
|
|
] },
|
|
{ id: 'u4', av: '陈', name: '陈策划', email: 'chen@shop.com', role: 'member', daily: 100, monthly: 2000, used: 12.80, usedToday: 0, lastActive: '4 小时前', pending: false,
|
|
joinDate: '2026-05-02', inviter: '张运营', projectsActive: 1, projectsDone: 1, assetsUploaded: 5,
|
|
activity: [
|
|
{ ts: '4 小时前', act: '创建项目', obj: '蓝牙耳机 · 开箱测评' },
|
|
{ ts: '昨天', act: '完成视频', obj: '速食面 · 加班场景' },
|
|
],
|
|
byStage: { video: 8.00, storyboard: 3.00, asset: 1.20, script: 0.60 },
|
|
transactions: [
|
|
{ ts: '05.20 14:32', proj: '速食面 · 加班场景', type: '视频片段 · 3 镜', amount: -0.60 },
|
|
{ ts: '05.19 18:08', proj: '速食面 · 加班场景', type: '故事板 image-2', amount: -0.30 },
|
|
{ ts: '05.19 11:12', proj: '速食面 · 加班场景', type: '基础资产 · 2 张', amount: -0.42 },
|
|
] },
|
|
{ id: 'u5', av: '林', name: '林新人', email: '186****1102', role: 'member', daily: 100, monthly: 2000, used: 0, usedToday: 0, lastActive: '尚未激活', pending: true,
|
|
joinDate: '2026-05-19', inviter: '小李', projectsActive: 0, projectsDone: 0, assetsUploaded: 0,
|
|
activity: [],
|
|
byStage: { video: 0, storyboard: 0, asset: 0, script: 0 },
|
|
transactions: [] },
|
|
];
|
|
|
|
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);
|
|
}
|
|
});
|
|
});
|
|
// 行点击 → 成员详情 modal(按钮区已 stopPropagation,不会冲突)
|
|
tb.querySelectorAll('tr[data-id]').forEach(row => {
|
|
row.style.cursor = 'pointer';
|
|
row.addEventListener('click', () => openMemberDetail(row.dataset.id));
|
|
});
|
|
}
|
|
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 email = document.getElementById('inv-email').value.trim();
|
|
if (!email) { Shell.toast('请填邮箱', '邀请失败'); return; }
|
|
// 简单邮箱格式校验:必须含 @ 和域名后缀
|
|
const emailOk = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
if (!emailOk) { 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, role,
|
|
daily, monthly, used: 0, pending: true,
|
|
});
|
|
renderMembers();
|
|
Shell.closeModal('invite-bg');
|
|
Shell.toast('邀请邮件已发送', email);
|
|
document.getElementById('inv-email').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);
|
|
}
|
|
});
|
|
|
|
/* ─── 成员详情 modal · 点击行打开 ─── */
|
|
let detailId = null;
|
|
function openMemberDetail(id) {
|
|
const m = MEMBERS.find(x => x.id === id);
|
|
if (!m) return;
|
|
detailId = id;
|
|
const r = ROLE_META[m.role];
|
|
const pct = m.monthly > 0 ? m.used / m.monthly : 0;
|
|
|
|
document.getElementById('md-av').textContent = m.av;
|
|
document.getElementById('md-name').textContent = m.name;
|
|
document.getElementById('md-email').textContent = m.email;
|
|
const rolePill = document.getElementById('md-role');
|
|
rolePill.className = 'role-pill ' + r.cls;
|
|
document.getElementById('md-role-label').textContent = r.label;
|
|
document.getElementById('md-creator').style.display = m.creator ? '' : 'none';
|
|
document.getElementById('md-pending').style.display = m.pending ? '' : 'none';
|
|
|
|
// 角色权限速览(PRD §10.2 权限矩阵)
|
|
document.getElementById('md-perm-role').textContent = r.label;
|
|
const permList = document.getElementById('md-perm-list');
|
|
const perms = ROLE_PERMS[m.role] || [];
|
|
permList.innerHTML = perms.map(p => `
|
|
<div class="pi${p.ok ? '' : ' deny'}">
|
|
<span class="ck">${p.ok ? '✓' : '—'}</span>
|
|
<span class="lb">${p.txt}</span>
|
|
</div>
|
|
`).join('');
|
|
|
|
document.getElementById('md-daily').textContent = fmtMoney(m.daily);
|
|
document.getElementById('md-monthly').textContent = fmtMoney(m.monthly);
|
|
const usedEl = document.getElementById('md-used');
|
|
usedEl.textContent = fmtMoney(m.used);
|
|
usedEl.classList.toggle('warn', pct >= 0.85);
|
|
document.getElementById('md-pct').textContent = (pct * 100).toFixed(1) + '%';
|
|
const bar = document.getElementById('md-bar');
|
|
bar.style.width = Math.min(100, pct * 100).toFixed(1) + '%';
|
|
bar.className = usedClass(pct);
|
|
|
|
// 今日已用 / 剩余
|
|
const usedToday = m.usedToday || 0;
|
|
const leftToday = Math.max(0, m.daily - usedToday);
|
|
document.getElementById('md-used-today').textContent = fmtMoney(usedToday);
|
|
const leftTodayEl = document.getElementById('md-left-today');
|
|
leftTodayEl.textContent = fmtMoney(leftToday);
|
|
leftTodayEl.classList.toggle('warn', m.daily > 0 && leftToday / m.daily < 0.2);
|
|
|
|
// 最近活跃
|
|
document.getElementById('md-last-active').textContent = '· 最近活跃 ' + (m.lastActive || '—');
|
|
|
|
document.getElementById('md-projects-active').textContent = m.projectsActive ?? 0;
|
|
document.getElementById('md-projects-done').textContent = m.projectsDone ?? 0;
|
|
document.getElementById('md-assets').textContent = m.assetsUploaded ?? 0;
|
|
|
|
// ─── 消费明细:按阶段柱状条 ───
|
|
const stageList = document.getElementById('md-stage-list');
|
|
const bs = m.byStage || { video: 0, storyboard: 0, asset: 0, script: 0 };
|
|
const totalCost = bs.video + bs.storyboard + bs.asset + bs.script;
|
|
const STAGES = [
|
|
{ key: 'video', label: '视频片段(Seedance)', cls: 's-video' },
|
|
{ key: 'storyboard', label: '故事板(image-2)', cls: 's-storyboard' },
|
|
{ key: 'asset', label: '基础资产', cls: 's-asset' },
|
|
{ key: 'script', label: '脚本 LLM', cls: 's-script' },
|
|
];
|
|
if (totalCost === 0) {
|
|
stageList.innerHTML = '<div style="font-size:12px;color:var(--black-alpha-48);font-family:var(--font-mono);text-align:center;padding:8px 0;">// 本月暂无消费</div>';
|
|
} else {
|
|
stageList.innerHTML = STAGES.map(s => {
|
|
const v = bs[s.key] || 0;
|
|
const pct = totalCost > 0 ? (v / totalCost * 100) : 0;
|
|
return `
|
|
<div class="md-stage-row">
|
|
<span class="swatch ${s.cls}"></span>
|
|
<div class="line">
|
|
<span class="nm">${s.label}</span>
|
|
<span class="pct">${pct.toFixed(0)}%</span>
|
|
<span class="amt">${fmtMoney(v)}</span>
|
|
</div>
|
|
<div class="bar"><span class="${s.cls}" style="width: ${pct.toFixed(1)}%"></span></div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
}
|
|
document.getElementById('md-cost-total').textContent = fmtMoney(totalCost);
|
|
|
|
// ─── 流水明细表 ───
|
|
const txTable = document.getElementById('md-tx-table');
|
|
const txToggle = document.getElementById('md-tx-toggle');
|
|
const txs = m.transactions || [];
|
|
document.getElementById('md-tx-count').textContent = txs.length;
|
|
// 保留 head 行,清除旧的明细行后重新注入
|
|
txTable.querySelectorAll('.md-tx-row:not(.head)').forEach(el => el.remove());
|
|
if (txs.length === 0) {
|
|
const empty = document.createElement('div');
|
|
empty.className = 'md-tx-row';
|
|
empty.style.gridTemplateColumns = '1fr';
|
|
empty.style.color = 'var(--black-alpha-48)';
|
|
empty.style.fontFamily = 'var(--font-mono)';
|
|
empty.style.textAlign = 'center';
|
|
empty.style.padding = '14px';
|
|
empty.textContent = '// 暂无消费记录';
|
|
txTable.appendChild(empty);
|
|
} else {
|
|
txs.forEach(t => {
|
|
const row = document.createElement('div');
|
|
row.className = 'md-tx-row';
|
|
const amtCls = t.amount > 0 ? 'pos' : (t.amount === 0 ? 'zero' : '');
|
|
const amtStr = t.amount === 0 ? '¥0.00' : (t.amount > 0 ? '+' + fmtMoney(t.amount) : '-' + fmtMoney(Math.abs(t.amount)));
|
|
row.innerHTML = `
|
|
<span class="ts">${t.ts}</span>
|
|
<span class="proj" title="${t.proj}">${t.proj}</span>
|
|
<span class="type">${t.type}</span>
|
|
<span class="amt ${amtCls}">${amtStr}</span>
|
|
`;
|
|
txTable.appendChild(row);
|
|
});
|
|
}
|
|
// 重置展开状态
|
|
txTable.classList.remove('show');
|
|
txToggle.classList.remove('expanded');
|
|
const txLabel = txToggle.querySelector('.md-tx-label');
|
|
txLabel.textContent = '展开';
|
|
txToggle.onclick = () => {
|
|
const opening = !txTable.classList.contains('show');
|
|
txTable.classList.toggle('show', opening);
|
|
txToggle.classList.toggle('expanded', opening);
|
|
txLabel.textContent = opening ? '收起' : '展开';
|
|
};
|
|
txToggle.onkeydown = (e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); txToggle.click(); }
|
|
};
|
|
|
|
const list = document.getElementById('md-activity');
|
|
if (!m.activity || m.activity.length === 0) {
|
|
list.innerHTML = '<div style="font-size:12px;color:var(--black-alpha-48);font-family:var(--font-mono);text-align:center;padding:16px 0;">// 暂无活动记录</div>';
|
|
} else {
|
|
list.innerHTML = m.activity.map(a =>
|
|
'<div class="md-activity-item"><span class="ts">' + a.ts + '</span><span><span class="act">' + a.act + '</span> <span class="obj">' + a.obj + '</span></span></div>'
|
|
).join('');
|
|
}
|
|
|
|
document.getElementById('md-meta').textContent =
|
|
'// 加入团队 ' + (m.joinDate || '—') +
|
|
(m.inviter && m.inviter !== '—' ? ' · 由 ' + m.inviter + ' 邀请' : ' · 团队创建者');
|
|
|
|
// footer 操作权限分支:
|
|
// - 创建者(超管):隐藏编辑/移除(协议保护,PRD 没有明确路径降级团队创建者)
|
|
// - 待激活:编辑成员 → 重发邀请,移出团队 → 取消邀请
|
|
// - 普通成员/团管:标准 编辑成员 + 移出团队
|
|
const editBtn = document.getElementById('md-edit');
|
|
const removeBtn = document.getElementById('md-remove');
|
|
if (m.creator) {
|
|
editBtn.style.display = 'none';
|
|
removeBtn.style.display = 'none';
|
|
} else if (m.pending) {
|
|
editBtn.style.display = '';
|
|
removeBtn.style.display = '';
|
|
editBtn.textContent = '重发邀请';
|
|
removeBtn.textContent = '取消邀请';
|
|
} else {
|
|
editBtn.style.display = '';
|
|
removeBtn.style.display = '';
|
|
editBtn.textContent = '编辑成员';
|
|
removeBtn.textContent = '移出团队';
|
|
}
|
|
|
|
Shell.openModal('member-detail-bg');
|
|
}
|
|
document.getElementById('md-edit').addEventListener('click', () => {
|
|
if (!detailId) return;
|
|
const m = MEMBERS.find(x => x.id === detailId);
|
|
if (!m) return;
|
|
if (m.pending) {
|
|
Shell.toast('邀请邮件已重发', m.email);
|
|
return;
|
|
}
|
|
Shell.closeModal('member-detail-bg');
|
|
openEdit(detailId);
|
|
});
|
|
document.getElementById('md-remove').addEventListener('click', () => {
|
|
const m = MEMBERS.find(x => x.id === detailId);
|
|
if (!m) return;
|
|
const verb = m.pending ? '取消「' + m.name + '」的邀请' : '将「' + m.name + '」移出团队';
|
|
if (confirm('确定' + verb + '?')) {
|
|
const i = MEMBERS.findIndex(x => x.id === m.id);
|
|
MEMBERS.splice(i, 1);
|
|
Shell.closeModal('member-detail-bg');
|
|
Shell.toast(m.pending ? '邀请已取消' : '已移除', m.name);
|
|
renderMembers(document.getElementById('member-search').value);
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|