|
@@ -0,0 +1,931 @@
|
|
|
|
|
+<!DOCTYPE html>
|
|
|
|
|
+<html lang="zh-CN">
|
|
|
|
|
+<head>
|
|
|
|
|
+ <meta charset="UTF-8" />
|
|
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
|
|
|
+ <title>采集地区 · 人员绑定 Demo</title>
|
|
|
|
|
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
|
|
|
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
|
|
|
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
|
|
|
+ <style>
|
|
|
|
|
+ :root {
|
|
|
|
|
+ --primary: #10b981;
|
|
|
|
|
+ --primary-dark: #059669;
|
|
|
|
|
+ --primary-bg: #ecfdf5;
|
|
|
|
|
+ --text: #1e293b;
|
|
|
|
|
+ --text-muted: #64748b;
|
|
|
|
|
+ --border: #d9e8e2;
|
|
|
|
|
+ --bg-page: #f8fafc;
|
|
|
|
|
+ --surface: #ffffff;
|
|
|
|
|
+ --table-head: #f8fafc;
|
|
|
|
|
+ --row-alt: #fafafa;
|
|
|
|
|
+ --row-hover: #f1f5f9;
|
|
|
|
|
+ --danger: #dc2626;
|
|
|
|
|
+ --warning: #d97706;
|
|
|
|
|
+ --radius: 10px;
|
|
|
|
|
+ --shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ * { box-sizing: border-box; }
|
|
|
|
|
+ body {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+ font-family: Inter, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+ color: var(--text);
|
|
|
|
|
+ background: var(--bg-page);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .page {
|
|
|
|
|
+ max-width: 1180px;
|
|
|
|
|
+ margin: 0 auto;
|
|
|
|
|
+ padding: 24px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .demo-banner {
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+ padding: 12px 16px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ border: 1px solid #a7f3d0;
|
|
|
|
|
+ background: var(--primary-bg);
|
|
|
|
|
+ color: var(--primary-dark);
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .demo-banner strong { font-weight: 700; }
|
|
|
|
|
+
|
|
|
|
|
+ h1 {
|
|
|
|
|
+ margin: 0 0 6px;
|
|
|
|
|
+ font-size: 20px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ }
|
|
|
|
|
+ .page-desc {
|
|
|
|
|
+ margin: 0 0 20px;
|
|
|
|
|
+ color: var(--text-muted);
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .variant-tabs {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .variant-tab {
|
|
|
|
|
+ border: 1px solid var(--border);
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ padding: 8px 14px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: var(--text-muted);
|
|
|
|
|
+ }
|
|
|
|
|
+ .variant-tab.active {
|
|
|
|
|
+ background: var(--primary-bg);
|
|
|
|
|
+ border-color: #a7f3d0;
|
|
|
|
|
+ color: var(--primary-dark);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .panel {
|
|
|
|
|
+ background: var(--surface);
|
|
|
|
|
+ border: 1px solid var(--border);
|
|
|
|
|
+ border-radius: var(--radius);
|
|
|
|
|
+ box-shadow: var(--shadow);
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ }
|
|
|
|
|
+ .panel-top {
|
|
|
|
|
+ padding: 16px 20px 12px;
|
|
|
|
|
+ border-bottom: 1px solid var(--border);
|
|
|
|
|
+ }
|
|
|
|
|
+ .title-bar-heading {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .title-bar-heading::before {
|
|
|
|
|
+ content: "";
|
|
|
|
|
+ width: 3px;
|
|
|
|
|
+ height: 16px;
|
|
|
|
|
+ border-radius: 2px;
|
|
|
|
|
+ background: var(--primary);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .filter-block {
|
|
|
|
|
+ padding: 16px 20px 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .filter-grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
|
|
+ gap: 12px 16px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .field label {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ color: var(--text-muted);
|
|
|
|
|
+ margin-bottom: 4px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .field input, .field select {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 36px;
|
|
|
|
|
+ border: 1px solid var(--border);
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ padding: 0 10px;
|
|
|
|
|
+ font: inherit;
|
|
|
|
|
+ color: var(--text);
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ }
|
|
|
|
|
+ .filter-actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ margin-top: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .list-toolbar {
|
|
|
|
|
+ padding: 12px 20px;
|
|
|
|
|
+ border-top: 1px solid var(--border);
|
|
|
|
|
+ }
|
|
|
|
|
+ .list-toolbar-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ }
|
|
|
|
|
+ .sel-label { color: var(--text-muted); font-size: 13px; }
|
|
|
|
|
+ .sel-num { color: var(--primary-dark); font-weight: 700; }
|
|
|
|
|
+
|
|
|
|
|
+ .btn {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 6px;
|
|
|
|
|
+ height: 36px;
|
|
|
|
|
+ padding: 0 14px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ border: 1px solid var(--border);
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ color: var(--text);
|
|
|
|
|
+ font: inherit;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ }
|
|
|
|
|
+ .btn:hover { background: var(--row-hover); }
|
|
|
|
|
+ .btn:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
|
|
|
+ .btn-main {
|
|
|
|
|
+ background: var(--primary);
|
|
|
|
|
+ border-color: var(--primary);
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ }
|
|
|
|
|
+ .btn-main:hover { background: var(--primary-dark); }
|
|
|
|
|
+ .btn-ghost { background: #fff; }
|
|
|
|
|
+ .btn-danger { color: var(--danger); border-color: #fecaca; }
|
|
|
|
|
+ .btn-sm {
|
|
|
|
|
+ height: 28px;
|
|
|
|
|
+ padding: 0 10px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ }
|
|
|
|
|
+ .btn-op {
|
|
|
|
|
+ height: 28px;
|
|
|
|
|
+ padding: 0 10px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: var(--primary-dark);
|
|
|
|
|
+ border-color: #a7f3d0;
|
|
|
|
|
+ background: var(--primary-bg);
|
|
|
|
|
+ }
|
|
|
|
|
+ .btn-op.neutral {
|
|
|
|
|
+ color: var(--text);
|
|
|
|
|
+ border-color: var(--border);
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .list-body { border-top: 1px solid var(--border); }
|
|
|
|
|
+ table {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ border-collapse: collapse;
|
|
|
|
|
+ }
|
|
|
|
|
+ th, td {
|
|
|
|
|
+ padding: 10px 12px;
|
|
|
|
|
+ border-bottom: 1px solid var(--border);
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ vertical-align: middle;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ }
|
|
|
|
|
+ th {
|
|
|
|
|
+ background: var(--table-head);
|
|
|
|
|
+ color: var(--text-muted);
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+ tbody tr:nth-child(even) { background: var(--row-alt); }
|
|
|
|
|
+ tbody tr:hover { background: var(--row-hover); }
|
|
|
|
|
+ td.left { text-align: left; }
|
|
|
|
|
+ .muted { color: var(--text-muted); }
|
|
|
|
|
+ .stack-lines { display: flex; flex-direction: column; gap: 2px; align-items: flex-start; }
|
|
|
|
|
+ .ops { display: flex; justify-content: center; gap: 8px; flex-wrap: wrap; }
|
|
|
|
|
+
|
|
|
|
|
+ .tag {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ height: 22px;
|
|
|
|
|
+ padding: 0 8px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ }
|
|
|
|
|
+ .tag-success { background: var(--primary-bg); color: var(--primary-dark); }
|
|
|
|
|
+ .tag-warning { background: #fff7ed; color: var(--warning); }
|
|
|
|
|
+ .tag-info { background: #f1f5f9; color: var(--text-muted); }
|
|
|
|
|
+ .tag-muted { background: #f8fafc; color: var(--text-muted); border: 1px solid var(--border); }
|
|
|
|
|
+
|
|
|
|
|
+ .pager {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ padding: 12px 20px;
|
|
|
|
|
+ border-top: 1px solid var(--border);
|
|
|
|
|
+ color: var(--text-muted);
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* Modal */
|
|
|
|
|
+ .mask {
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ inset: 0;
|
|
|
|
|
+ background: rgba(15, 23, 42, 0.45);
|
|
|
|
|
+ display: none;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+ z-index: 50;
|
|
|
|
|
+ backdrop-filter: none;
|
|
|
|
|
+ }
|
|
|
|
|
+ .mask.open { display: flex; }
|
|
|
|
|
+ .dialog {
|
|
|
|
|
+ width: min(860px, 100%);
|
|
|
|
|
+ max-height: calc(100vh - 100px);
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ box-shadow: 0 20px 40px rgba(15, 23, 42, 0.18);
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ }
|
|
|
|
|
+ .dialog-hd {
|
|
|
|
|
+ padding: 16px 20px;
|
|
|
|
|
+ border-bottom: 1px solid var(--border);
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .dialog-title { font-size: 16px; font-weight: 700; }
|
|
|
|
|
+ .dialog-close {
|
|
|
|
|
+ width: 32px; height: 32px;
|
|
|
|
|
+ border: none; background: transparent;
|
|
|
|
|
+ border-radius: 8px; cursor: pointer;
|
|
|
|
|
+ color: var(--text-muted); font-size: 18px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .dialog-close:hover { background: var(--row-hover); }
|
|
|
|
|
+ .dialog-bd {
|
|
|
|
|
+ padding: 16px 20px;
|
|
|
|
|
+ overflow: auto;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ .dialog-ft {
|
|
|
|
|
+ padding: 12px 20px;
|
|
|
|
|
+ border-top: 1px solid var(--border);
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .dialog-meta {
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ color: var(--text-muted);
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .picker-row {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: 1fr auto;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ align-items: end;
|
|
|
|
|
+ }
|
|
|
|
|
+ .picker-row .field { margin: 0; }
|
|
|
|
|
+
|
|
|
|
|
+ .leader-toggle {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 6px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ user-select: none;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .leader-toggle input { accent-color: var(--primary); width: 16px; height: 16px; }
|
|
|
|
|
+
|
|
|
|
|
+ .note-box {
|
|
|
|
|
+ margin-top: 16px;
|
|
|
|
|
+ padding: 12px 14px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ background: #f8fafc;
|
|
|
|
|
+ border: 1px dashed var(--border);
|
|
|
|
|
+ color: var(--text-muted);
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .note-box code {
|
|
|
|
|
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border: 1px solid var(--border);
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ padding: 1px 4px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .approach-card {
|
|
|
|
|
+ display: none;
|
|
|
|
|
+ }
|
|
|
|
|
+ .approach-card.active { display: block; }
|
|
|
|
|
+
|
|
|
|
|
+ .split-layout {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: 1.2fr 1fr;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+ }
|
|
|
|
|
+ @media (max-width: 900px) {
|
|
|
|
|
+ .split-layout { grid-template-columns: 1fr; }
|
|
|
|
|
+ .filter-grid { grid-template-columns: 1fr; }
|
|
|
|
|
+ }
|
|
|
|
|
+ .mini-panel {
|
|
|
|
|
+ border: 1px solid var(--border);
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ }
|
|
|
|
|
+ .mini-panel-hd {
|
|
|
|
|
+ padding: 10px 12px;
|
|
|
|
|
+ background: var(--table-head);
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ border-bottom: 1px solid var(--border);
|
|
|
|
|
+ }
|
|
|
|
|
+ .chip-list {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ padding: 12px;
|
|
|
|
|
+ min-height: 64px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .chip {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 6px;
|
|
|
|
|
+ height: 28px;
|
|
|
|
|
+ padding: 0 8px 0 10px;
|
|
|
|
|
+ border-radius: 999px;
|
|
|
|
|
+ border: 1px solid var(--border);
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .chip.leader {
|
|
|
|
|
+ border-color: #a7f3d0;
|
|
|
|
|
+ background: var(--primary-bg);
|
|
|
|
|
+ color: var(--primary-dark);
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ }
|
|
|
|
|
+ .chip button {
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ background: transparent;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ color: var(--text-muted);
|
|
|
|
|
+ padding: 0 2px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ .chip button:hover { color: var(--danger); }
|
|
|
|
|
+
|
|
|
|
|
+ .toast {
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ top: 20px;
|
|
|
|
|
+ left: 50%;
|
|
|
|
|
+ transform: translateX(-50%);
|
|
|
|
|
+ background: #0f172a;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ padding: 10px 16px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ z-index: 100;
|
|
|
|
|
+ opacity: 0;
|
|
|
|
|
+ pointer-events: none;
|
|
|
|
|
+ transition: opacity 0.2s;
|
|
|
|
|
+ }
|
|
|
|
|
+ .toast.show { opacity: 1; }
|
|
|
|
|
+ </style>
|
|
|
|
|
+</head>
|
|
|
|
|
+<body>
|
|
|
|
|
+ <div class="page">
|
|
|
|
|
+ <div class="demo-banner">
|
|
|
|
|
+ <strong>交互 Demo(非最终实现)</strong>:展示「采集地区 ↔ 系统人员」绑定,以及将部分人员设为<strong>地区负责人</strong>。可切换两种交互方案对比。
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <h1>采集地区 · 人员绑定</h1>
|
|
|
|
|
+ <p class="page-desc">
|
|
|
|
|
+ 页面:细胞采集基础信息维护 → 采集地区及医院。本 Demo 在现有「地区医院配置」旁增加「地区人员配置」。
|
|
|
|
|
+ </p>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="variant-tabs">
|
|
|
|
|
+ <button class="variant-tab active" data-variant="a" type="button">方案 A:独立配置弹窗(推荐)</button>
|
|
|
|
|
+ <button class="variant-tab" data-variant="b" type="button">方案 B:主表直接展示 + 行内设置负责人</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- ========== Approach A ========== -->
|
|
|
|
|
+ <div class="approach-card active" id="approach-a">
|
|
|
|
|
+ <section class="panel">
|
|
|
|
|
+ <div class="panel-top">
|
|
|
|
|
+ <h2 class="title-bar-heading">采集地区及医院</h2>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="filter-block">
|
|
|
|
|
+ <div class="filter-grid">
|
|
|
|
|
+ <div class="field">
|
|
|
|
|
+ <label>关键词</label>
|
|
|
|
|
+ <input type="text" placeholder="地区名称" value="" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="field">
|
|
|
|
|
+ <label>负责人</label>
|
|
|
|
|
+ <input type="text" placeholder="可选,按负责人姓名筛" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="field">
|
|
|
|
|
+ <label>绑定人员</label>
|
|
|
|
|
+ <input type="text" placeholder="可选,按工作人员姓名筛" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="filter-actions">
|
|
|
|
|
+ <button class="btn btn-ghost" type="button">重置</button>
|
|
|
|
|
+ <button class="btn btn-main" type="button">搜索</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="list-toolbar">
|
|
|
|
|
+ <div class="list-toolbar-row">
|
|
|
|
|
+ <div class="sel-label">已选 <span class="sel-num">0</span> 项</div>
|
|
|
|
|
+ <button class="btn btn-main" type="button">+ 新增地区</button>
|
|
|
|
|
+ <button class="btn btn-ghost btn-danger" type="button" disabled>批量删除</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="list-body">
|
|
|
|
|
+ <table>
|
|
|
|
|
+ <thead>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th style="width:48px"><input type="checkbox" disabled /></th>
|
|
|
|
|
+ <th style="width:64px">序号</th>
|
|
|
|
|
+ <th>地区名称</th>
|
|
|
|
|
+ <th class="left">医院信息</th>
|
|
|
|
|
+ <th class="left">工作人员</th>
|
|
|
|
|
+ <th>负责人</th>
|
|
|
|
|
+ <th>创建时间</th>
|
|
|
|
|
+ <th style="min-width:280px">操作</th>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </thead>
|
|
|
|
|
+ <tbody id="region-tbody-a"></tbody>
|
|
|
|
|
+ </table>
|
|
|
|
|
+ <div class="pager">共 3 条 · 10 条/页 · 1</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="note-box">
|
|
|
|
|
+ 方案 A:与现有「地区医院配置」对称 —— 操作列增加「地区人员配置」弹窗;主列表用摘要列展示工作人员数 / 负责人 Tag。
|
|
|
|
|
+ 数据建议:关联表 <code>biz_collection_region_staff</code>(region_id + user_id + is_leader)。
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- ========== Approach B ========== -->
|
|
|
|
|
+ <div class="approach-card" id="approach-b">
|
|
|
|
|
+ <section class="panel">
|
|
|
|
|
+ <div class="panel-top">
|
|
|
|
|
+ <h2 class="title-bar-heading">采集地区及医院</h2>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="list-toolbar">
|
|
|
|
|
+ <div class="list-toolbar-row">
|
|
|
|
|
+ <div class="sel-label">已选 <span class="sel-num">0</span> 项</div>
|
|
|
|
|
+ <button class="btn btn-main" type="button">+ 新增地区</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="list-body">
|
|
|
|
|
+ <table>
|
|
|
|
|
+ <thead>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th>地区</th>
|
|
|
|
|
+ <th class="left">已绑定人员(点击星标设为负责人)</th>
|
|
|
|
|
+ <th style="min-width:160px">操作</th>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </thead>
|
|
|
|
|
+ <tbody id="region-tbody-b"></tbody>
|
|
|
|
|
+ </table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+ <div class="note-box">
|
|
|
|
|
+ 方案 B:主表行内直接展示人员 Chip,星标切换负责人;适合人员少、频繁调整的场景,但主表会变「胖」。
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Staff config dialog (Approach A) -->
|
|
|
|
|
+ <div class="mask" id="staff-mask" role="dialog" aria-modal="true">
|
|
|
|
|
+ <div class="dialog">
|
|
|
|
|
+ <div class="dialog-hd">
|
|
|
|
|
+ <div class="dialog-title">地区人员配置</div>
|
|
|
|
|
+ <button class="dialog-close" type="button" id="staff-close" aria-label="关闭">×</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="dialog-bd">
|
|
|
|
|
+ <div class="dialog-meta">当前地区:<strong id="staff-region-name">—</strong></div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="picker-row">
|
|
|
|
|
+ <div class="field">
|
|
|
|
|
+ <label>添加系统人员</label>
|
|
|
|
|
+ <select id="staff-picker">
|
|
|
|
|
+ <option value="">请选择用户(演示数据)</option>
|
|
|
|
|
+ </select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <button class="btn btn-main" type="button" id="staff-add">+ 添加</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="list-toolbar" style="padding:0 0 10px; border:none">
|
|
|
|
|
+ <div class="list-toolbar-row">
|
|
|
|
|
+ <div class="sel-label">已绑定 <span class="sel-num" id="staff-count">0</span> 人 · 负责人 <span class="sel-num" id="leader-count">0</span> 人</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="list-body" style="border:1px solid var(--border); border-radius:10px; overflow:hidden">
|
|
|
|
|
+ <table>
|
|
|
|
|
+ <thead>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th style="width:56px">序号</th>
|
|
|
|
|
+ <th class="left">姓名</th>
|
|
|
|
|
+ <th>工号</th>
|
|
|
|
|
+ <th>部门</th>
|
|
|
|
|
+ <th>角色</th>
|
|
|
|
|
+ <th>是否负责人</th>
|
|
|
|
|
+ <th style="min-width:100px">操作</th>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </thead>
|
|
|
|
|
+ <tbody id="staff-tbody"></tbody>
|
|
|
|
|
+ </table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="note-box" style="margin-top:12px">
|
|
|
|
|
+ Demo 约定:同一地区可绑定多名工作人员;负责人可为 <strong>0~多人</strong>(勾选「是否负责人」)。保存后写入关联表,不修改 <code>sys_user</code>。
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="dialog-ft">
|
|
|
|
|
+ <button class="btn btn-ghost" type="button" id="staff-cancel">取消</button>
|
|
|
|
|
+ <button class="btn btn-main" type="button" id="staff-save">✓ 确定</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Nested: quick view of approach B detail -->
|
|
|
|
|
+ <div class="mask" id="b-mask">
|
|
|
|
|
+ <div class="dialog" style="width:min(720px,100%)">
|
|
|
|
|
+ <div class="dialog-hd">
|
|
|
|
|
+ <div class="dialog-title">人员绑定(方案 B 弹窗备选)</div>
|
|
|
|
|
+ <button class="dialog-close" type="button" id="b-close">×</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="dialog-bd">
|
|
|
|
|
+ <div class="split-layout">
|
|
|
|
|
+ <div class="mini-panel">
|
|
|
|
|
+ <div class="mini-panel-hd">候选人员</div>
|
|
|
|
|
+ <div class="chip-list" id="b-candidates"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="mini-panel">
|
|
|
|
|
+ <div class="mini-panel-hd">已绑定(绿底 = 负责人)</div>
|
|
|
|
|
+ <div class="chip-list" id="b-bound"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="dialog-ft">
|
|
|
|
|
+ <button class="btn btn-ghost" type="button" id="b-cancel">取消</button>
|
|
|
|
|
+ <button class="btn btn-main" type="button" id="b-save">✓ 确定</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="toast" id="toast"></div>
|
|
|
|
|
+
|
|
|
|
|
+ <script>
|
|
|
|
|
+ const ALL_USERS = [
|
|
|
|
|
+ { id: 'u1', name: '张敏', userNo: 'SQ001', dept: '市场一部', role: '采集专员' },
|
|
|
|
|
+ { id: 'u2', name: '李强', userNo: 'SQ002', dept: '市场一部', role: '采集专员' },
|
|
|
|
|
+ { id: 'u3', name: '王芳', userNo: 'SQ003', dept: '市场二部', role: '销售' },
|
|
|
|
|
+ { id: 'u4', name: '陈静', userNo: 'SQ004', dept: '市场支持', role: '市场支持专员' },
|
|
|
|
|
+ { id: 'u5', name: '赵磊', userNo: 'SQ005', dept: '市场二部', role: '采集专员' },
|
|
|
|
|
+ { id: 'u6', name: '周婷', userNo: 'SQ006', dept: '市场一部', role: '销售' },
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ const regions = [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 'r1',
|
|
|
|
|
+ name: '合肥市区',
|
|
|
|
|
+ hospitals: ['安徽省立医院', '安医大一附院'],
|
|
|
|
|
+ createTime: '2026-03-12 10:20:00',
|
|
|
|
|
+ staff: [
|
|
|
|
|
+ { userId: 'u1', isLeader: true },
|
|
|
|
|
+ { userId: 'u2', isLeader: false },
|
|
|
|
|
+ { userId: 'u6', isLeader: false },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 'r2',
|
|
|
|
|
+ name: '芜湖市',
|
|
|
|
|
+ hospitals: ['皖南医学院弋矶山医院'],
|
|
|
|
|
+ createTime: '2026-04-02 14:08:00',
|
|
|
|
|
+ staff: [
|
|
|
|
|
+ { userId: 'u3', isLeader: true },
|
|
|
|
|
+ { userId: 'u5', isLeader: true },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 'r3',
|
|
|
|
|
+ name: '阜阳市',
|
|
|
|
|
+ hospitals: [],
|
|
|
|
|
+ createTime: '2026-05-18 09:41:00',
|
|
|
|
|
+ staff: [],
|
|
|
|
|
+ },
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ let editingRegionId = null;
|
|
|
|
|
+ let draftStaff = [];
|
|
|
|
|
+
|
|
|
|
|
+ function userById(id) {
|
|
|
|
|
+ return ALL_USERS.find((u) => u.id === id);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function toast(msg) {
|
|
|
|
|
+ const el = document.getElementById('toast');
|
|
|
|
|
+ el.textContent = msg;
|
|
|
|
|
+ el.classList.add('show');
|
|
|
|
|
+ setTimeout(() => el.classList.remove('show'), 1800);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function staffSummary(staff) {
|
|
|
|
|
+ const people = staff.map((s) => userById(s.userId)).filter(Boolean);
|
|
|
|
|
+ const leaders = staff.filter((s) => s.isLeader).map((s) => userById(s.userId)).filter(Boolean);
|
|
|
|
|
+ return { people, leaders };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function renderApproachA() {
|
|
|
|
|
+ const tbody = document.getElementById('region-tbody-a');
|
|
|
|
|
+ tbody.innerHTML = regions.map((r, idx) => {
|
|
|
|
|
+ const { people, leaders } = staffSummary(r.staff);
|
|
|
|
|
+ const hospitalHtml = r.hospitals.length
|
|
|
|
|
+ ? `<div class="stack-lines">${r.hospitals.map((h) => `<span>${h}</span>`).join('')}</div>`
|
|
|
|
|
+ : '<span class="muted">—</span>';
|
|
|
|
|
+ const staffHtml = people.length
|
|
|
|
|
+ ? `<div class="stack-lines">${people.map((p) => `<span>${p.name}<span class="muted">(${p.userNo})</span></span>`).join('')}</div>`
|
|
|
|
|
+ : '<span class="muted">未绑定</span>';
|
|
|
|
|
+ const leaderHtml = leaders.length
|
|
|
|
|
+ ? leaders.map((l) => `<span class="tag tag-success">${l.name}</span>`).join(' ')
|
|
|
|
|
+ : '<span class="tag tag-info">未设置</span>';
|
|
|
|
|
+ return `
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <td><input type="checkbox" /></td>
|
|
|
|
|
+ <td>${idx + 1}</td>
|
|
|
|
|
+ <td><strong>${r.name}</strong></td>
|
|
|
|
|
+ <td class="left">${hospitalHtml}</td>
|
|
|
|
|
+ <td class="left">${staffHtml}</td>
|
|
|
|
|
+ <td>${leaderHtml}</td>
|
|
|
|
|
+ <td>${r.createTime}</td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <div class="ops">
|
|
|
|
|
+ <button class="btn btn-op" type="button" data-open-hospital="${r.id}">地区医院配置</button>
|
|
|
|
|
+ <button class="btn btn-op" type="button" data-open-staff="${r.id}">地区人员配置</button>
|
|
|
|
|
+ <button class="btn btn-op neutral btn-sm" type="button">⋯</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ </tr>`;
|
|
|
|
|
+ }).join('');
|
|
|
|
|
+
|
|
|
|
|
+ tbody.querySelectorAll('[data-open-staff]').forEach((btn) => {
|
|
|
|
|
+ btn.addEventListener('click', () => openStaffDialog(btn.getAttribute('data-open-staff')));
|
|
|
|
|
+ });
|
|
|
|
|
+ tbody.querySelectorAll('[data-open-hospital]').forEach((btn) => {
|
|
|
|
|
+ btn.addEventListener('click', () => toast('医院配置沿用现有弹窗(本 Demo 不展开)'));
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function renderApproachB() {
|
|
|
|
|
+ const tbody = document.getElementById('region-tbody-b');
|
|
|
|
|
+ tbody.innerHTML = regions.map((r) => {
|
|
|
|
|
+ const chips = r.staff.length
|
|
|
|
|
+ ? r.staff.map((s) => {
|
|
|
|
|
+ const u = userById(s.userId);
|
|
|
|
|
+ if (!u) return '';
|
|
|
|
|
+ const star = s.isLeader ? '★' : '☆';
|
|
|
|
|
+ return `<span class="chip ${s.isLeader ? 'leader' : ''}">
|
|
|
|
|
+ <button type="button" title="切换负责人" data-toggle-leader="${r.id}:${s.userId}">${star}</button>
|
|
|
|
|
+ ${u.name}
|
|
|
|
|
+ <button type="button" title="移除" data-remove="${r.id}:${s.userId}">×</button>
|
|
|
|
|
+ </span>`;
|
|
|
|
|
+ }).join('')
|
|
|
|
|
+ : '<span class="muted">暂无人员,请点击「绑定人员」</span>';
|
|
|
|
|
+ return `
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <td><strong>${r.name}</strong></td>
|
|
|
|
|
+ <td class="left"><div class="chip-list" style="padding:0">${chips}</div></td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <div class="ops">
|
|
|
|
|
+ <button class="btn btn-op" type="button" data-open-b="${r.id}">绑定人员</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ </tr>`;
|
|
|
|
|
+ }).join('');
|
|
|
|
|
+
|
|
|
|
|
+ tbody.querySelectorAll('[data-toggle-leader]').forEach((btn) => {
|
|
|
|
|
+ btn.addEventListener('click', () => {
|
|
|
|
|
+ const [rid, uid] = btn.getAttribute('data-toggle-leader').split(':');
|
|
|
|
|
+ const region = regions.find((x) => x.id === rid);
|
|
|
|
|
+ const row = region.staff.find((s) => s.userId === uid);
|
|
|
|
|
+ row.isLeader = !row.isLeader;
|
|
|
|
|
+ renderApproachB();
|
|
|
|
|
+ renderApproachA();
|
|
|
|
|
+ toast(row.isLeader ? '已设为负责人' : '已取消负责人');
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ tbody.querySelectorAll('[data-remove]').forEach((btn) => {
|
|
|
|
|
+ btn.addEventListener('click', () => {
|
|
|
|
|
+ const [rid, uid] = btn.getAttribute('data-remove').split(':');
|
|
|
|
|
+ const region = regions.find((x) => x.id === rid);
|
|
|
|
|
+ region.staff = region.staff.filter((s) => s.userId !== uid);
|
|
|
|
|
+ renderApproachB();
|
|
|
|
|
+ renderApproachA();
|
|
|
|
|
+ toast('已移除人员');
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ tbody.querySelectorAll('[data-open-b]').forEach((btn) => {
|
|
|
|
|
+ btn.addEventListener('click', () => openBDialog(btn.getAttribute('data-open-b')));
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function fillPicker() {
|
|
|
|
|
+ const sel = document.getElementById('staff-picker');
|
|
|
|
|
+ const bound = new Set(draftStaff.map((s) => s.userId));
|
|
|
|
|
+ sel.innerHTML = '<option value="">请选择用户(演示数据)</option>' +
|
|
|
|
|
+ ALL_USERS.filter((u) => !bound.has(u.id))
|
|
|
|
|
+ .map((u) => `<option value="${u.id}">${u.name}(${u.userNo})· ${u.dept}</option>`)
|
|
|
|
|
+ .join('');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function renderStaffTable() {
|
|
|
|
|
+ const tbody = document.getElementById('staff-tbody');
|
|
|
|
|
+ document.getElementById('staff-count').textContent = String(draftStaff.length);
|
|
|
|
|
+ document.getElementById('leader-count').textContent = String(draftStaff.filter((s) => s.isLeader).length);
|
|
|
|
|
+
|
|
|
|
|
+ if (!draftStaff.length) {
|
|
|
|
|
+ tbody.innerHTML = `<tr><td colspan="7" class="muted">暂无绑定人员,请从上方下拉添加</td></tr>`;
|
|
|
|
|
+ fillPicker();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ tbody.innerHTML = draftStaff.map((s, idx) => {
|
|
|
|
|
+ const u = userById(s.userId);
|
|
|
|
|
+ return `
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <td>${idx + 1}</td>
|
|
|
|
|
+ <td class="left"><strong>${u.name}</strong></td>
|
|
|
|
|
+ <td>${u.userNo}</td>
|
|
|
|
|
+ <td>${u.dept}</td>
|
|
|
|
|
+ <td><span class="tag tag-muted">${u.role}</span></td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <label class="leader-toggle">
|
|
|
|
|
+ <input type="checkbox" data-leader="${s.userId}" ${s.isLeader ? 'checked' : ''} />
|
|
|
|
|
+ 设为负责人
|
|
|
|
|
+ </label>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <button class="btn btn-sm btn-danger" type="button" data-unbind="${s.userId}">移除</button>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ </tr>`;
|
|
|
|
|
+ }).join('');
|
|
|
|
|
+
|
|
|
|
|
+ tbody.querySelectorAll('[data-leader]').forEach((input) => {
|
|
|
|
|
+ input.addEventListener('change', () => {
|
|
|
|
|
+ const uid = input.getAttribute('data-leader');
|
|
|
|
|
+ const row = draftStaff.find((s) => s.userId === uid);
|
|
|
|
|
+ row.isLeader = input.checked;
|
|
|
|
|
+ document.getElementById('leader-count').textContent = String(draftStaff.filter((s) => s.isLeader).length);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ tbody.querySelectorAll('[data-unbind]').forEach((btn) => {
|
|
|
|
|
+ btn.addEventListener('click', () => {
|
|
|
|
|
+ const uid = btn.getAttribute('data-unbind');
|
|
|
|
|
+ draftStaff = draftStaff.filter((s) => s.userId !== uid);
|
|
|
|
|
+ renderStaffTable();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ fillPicker();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function openStaffDialog(regionId) {
|
|
|
|
|
+ const region = regions.find((r) => r.id === regionId);
|
|
|
|
|
+ editingRegionId = regionId;
|
|
|
|
|
+ draftStaff = region.staff.map((s) => ({ ...s }));
|
|
|
|
|
+ document.getElementById('staff-region-name').textContent = region.name;
|
|
|
|
|
+ renderStaffTable();
|
|
|
|
|
+ document.getElementById('staff-mask').classList.add('open');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function closeStaffDialog() {
|
|
|
|
|
+ document.getElementById('staff-mask').classList.remove('open');
|
|
|
|
|
+ editingRegionId = null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function openBDialog(regionId) {
|
|
|
|
|
+ editingRegionId = regionId;
|
|
|
|
|
+ draftStaff = regions.find((r) => r.id === regionId).staff.map((s) => ({ ...s }));
|
|
|
|
|
+ const boundIds = new Set(draftStaff.map((s) => s.userId));
|
|
|
|
|
+ document.getElementById('b-candidates').innerHTML = ALL_USERS.filter((u) => !boundIds.has(u.id))
|
|
|
|
|
+ .map((u) => `<button class="btn btn-sm" type="button" data-add="${u.id}">+ ${u.name}</button>`)
|
|
|
|
|
+ .join('') || '<span class="muted">无可选人员</span>';
|
|
|
|
|
+ document.getElementById('b-bound').innerHTML = draftStaff.map((s) => {
|
|
|
|
|
+ const u = userById(s.userId);
|
|
|
|
|
+ return `<span class="chip ${s.isLeader ? 'leader' : ''}">
|
|
|
|
|
+ <button type="button" data-star="${s.userId}">${s.isLeader ? '★' : '☆'}</button>
|
|
|
|
|
+ ${u.name}
|
|
|
|
|
+ <button type="button" data-rm="${s.userId}">×</button>
|
|
|
|
|
+ </span>`;
|
|
|
|
|
+ }).join('') || '<span class="muted">尚未绑定</span>';
|
|
|
|
|
+
|
|
|
|
|
+ document.getElementById('b-candidates').querySelectorAll('[data-add]').forEach((btn) => {
|
|
|
|
|
+ btn.addEventListener('click', () => {
|
|
|
|
|
+ draftStaff.push({ userId: btn.getAttribute('data-add'), isLeader: false });
|
|
|
|
|
+ openBDialog(regionId);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ document.getElementById('b-bound').querySelectorAll('[data-star]').forEach((btn) => {
|
|
|
|
|
+ btn.addEventListener('click', () => {
|
|
|
|
|
+ const row = draftStaff.find((s) => s.userId === btn.getAttribute('data-star'));
|
|
|
|
|
+ row.isLeader = !row.isLeader;
|
|
|
|
|
+ openBDialog(regionId);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ document.getElementById('b-bound').querySelectorAll('[data-rm]').forEach((btn) => {
|
|
|
|
|
+ btn.addEventListener('click', () => {
|
|
|
|
|
+ draftStaff = draftStaff.filter((s) => s.userId !== btn.getAttribute('data-rm'));
|
|
|
|
|
+ openBDialog(regionId);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ document.getElementById('b-mask').classList.add('open');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Events
|
|
|
|
|
+ document.querySelectorAll('.variant-tab').forEach((tab) => {
|
|
|
|
|
+ tab.addEventListener('click', () => {
|
|
|
|
|
+ document.querySelectorAll('.variant-tab').forEach((t) => t.classList.remove('active'));
|
|
|
|
|
+ tab.classList.add('active');
|
|
|
|
|
+ const v = tab.getAttribute('data-variant');
|
|
|
|
|
+ document.getElementById('approach-a').classList.toggle('active', v === 'a');
|
|
|
|
|
+ document.getElementById('approach-b').classList.toggle('active', v === 'b');
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ document.getElementById('staff-add').addEventListener('click', () => {
|
|
|
|
|
+ const uid = document.getElementById('staff-picker').value;
|
|
|
|
|
+ if (!uid) { toast('请先选择人员'); return; }
|
|
|
|
|
+ draftStaff.push({ userId: uid, isLeader: false });
|
|
|
|
|
+ renderStaffTable();
|
|
|
|
|
+ });
|
|
|
|
|
+ document.getElementById('staff-save').addEventListener('click', () => {
|
|
|
|
|
+ const region = regions.find((r) => r.id === editingRegionId);
|
|
|
|
|
+ region.staff = draftStaff.map((s) => ({ ...s }));
|
|
|
|
|
+ closeStaffDialog();
|
|
|
|
|
+ renderApproachA();
|
|
|
|
|
+ renderApproachB();
|
|
|
|
|
+ toast('人员配置已保存(Demo)');
|
|
|
|
|
+ });
|
|
|
|
|
+ document.getElementById('staff-cancel').addEventListener('click', closeStaffDialog);
|
|
|
|
|
+ document.getElementById('staff-close').addEventListener('click', closeStaffDialog);
|
|
|
|
|
+ document.getElementById('staff-mask').addEventListener('click', (e) => {
|
|
|
|
|
+ if (e.target.id === 'staff-mask') closeStaffDialog();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ document.getElementById('b-save').addEventListener('click', () => {
|
|
|
|
|
+ const region = regions.find((r) => r.id === editingRegionId);
|
|
|
|
|
+ region.staff = draftStaff.map((s) => ({ ...s }));
|
|
|
|
|
+ document.getElementById('b-mask').classList.remove('open');
|
|
|
|
|
+ renderApproachA();
|
|
|
|
|
+ renderApproachB();
|
|
|
|
|
+ toast('人员配置已保存(Demo)');
|
|
|
|
|
+ });
|
|
|
|
|
+ document.getElementById('b-cancel').addEventListener('click', () => document.getElementById('b-mask').classList.remove('open'));
|
|
|
|
|
+ document.getElementById('b-close').addEventListener('click', () => document.getElementById('b-mask').classList.remove('open'));
|
|
|
|
|
+
|
|
|
|
|
+ renderApproachA();
|
|
|
|
|
+ renderApproachB();
|
|
|
|
|
+ </script>
|
|
|
|
|
+</body>
|
|
|
|
|
+</html>
|