```json
{
"summary": "将策略元件工厂与成效统计系统整体配色从暗色金主题转换为蓝白亮色主题。核心改动:CSS变量全面替换(背景→#f4f7fb/#ffffff、文字→#1e293b/#475569、强调色→#2563eb蓝色)、卡片/导航/输入框/表格/模态框等组件亮色化、ECharts图表配色蓝色系适配、Badge和按钮蓝色重构。功能与交互完全保留。",
"widget": {
"type": "full-html",
"html": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>策略元件工厂与成效统计</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;70…\" rel=\"stylesheet\">\n <script src=\"https://cdn.tailwindcss.com\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js\"></script>\n <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.c…\">\n <style>\n :root {\n --bg-deep: #f4f7fb;\n --bg-primary: #f8fafc;\n --bg-card: #ffffff;\n --bg-card-hover: #f1f5f9;\n --bg-input: #f1f5f9;\n --border-subtle: #e2e8f0;\n --border-active: #bfdbfe;\n --accent: #2563eb;\n --accent-glow: rgba(37, 99, 235, 0.20);\n --accent-soft: rgba(37, 99, 235, 0.06);\n --text-primary: #1e293b;\n --text-secondary: #475569;\n --text-muted: #94a3b8;\n --blue-data: #3b82f6;\n --teal-data: #06b6d4;\n --rose-data: #f43f5e;\n --green-status: #10b981;\n --orange-status: #f59e0b;\n --red-status: #ef4444;\n --font-heading: 'Outfit', system-ui, sans-serif;\n --font-body: 'DM Sans', system-ui, sans-serif;\n --font-mono: 'JetBrains Mono', monospace;\n --radius-sm: 6px;\n --radius-md: 10px;\n --radius-lg: 14px;\n --radius-xl: 18px;\n --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);\n --transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1);\n --transition-smooth: 350ms cubic-bezier(0.22, 0.61, 0.36, 1);\n --shadow-card: 0 1px 3px rgba(0,0,0,0.04), 0 1px 2px rgba(0,0,0,0.03);\n --shadow-card-hover: 0 4px 20px rgba(0,0,0,0.06), 0 0 0 1px rgba(37,99,235,0.10);\n --shadow-modal: 0 20px 60px rgba(0,0,0,0.12);\n }\n\n * { box-sizing: border-box; }\n\n body {\n font-family: var(--font-body);\n background: var(--bg-deep);\n color: var(--text-primary);\n margin: 0;\n min-height: 100vh;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n overflow-x: hidden;\n }\n\n /* 浅色网格背景纹理 */\n body::before {\n content: '';\n position: fixed;\n inset: 0;\n pointer-events: none;\n z-index: 0;\n background:\n radial-gradient(ellipse 80% 60% at 20% 10%, rgba(37, 99, 235, 0.04) 0%, transparent 60%),\n radial-gradient(ellipse 60% 50% at 75% 70%, rgba(59, 130, 246, 0.03) 0%, transparent 60%),\n radial-gradient(ellipse 50% 40% at 50% 90%, rgba(6, 182, 212, 0.02) 0%, transparent 50%);\n }\n\n /* 自定义滚动条 */\n ::-webkit-scrollbar { width: 5px; height: 5px; }\n ::-webkit-scrollbar-track { background: transparent; }\n ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }\n ::-webkit-scrollbar-thumb:hover { background: #94a3b8; }\n\n /* 顶部导航底部分割线 */\n .topnav-glow {\n position: relative;\n }\n .topnav-glow::after {\n content: '';\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 1px;\n background: linear-gradient(90deg, transparent, rgba(37,99,235,0.2), var(--border-subtle), rgba(59,130,246,0.15), transparent);\n }\n\n /* 卡片悬停效果 */\n .card-elevate {\n transition: all var(--transition-base);\n box-shadow: var(--shadow-card);\n }\n .card-elevate:hover {\n background: var(--bg-card-hover);\n border-color: var(--border-active);\n box-shadow: var(--shadow-card-hover);\n }\n\n /* 输入框样式 */\n .input-dark {\n background: var(--bg-input);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-sm);\n color: var(--text-primary);\n padding: 9px 12px;\n font-family: var(--font-body);\n font-size: 13px;\n transition: all var(--transition-fast);\n width: 100%;\n outline: none;\n }\n .input-dark:focus {\n border-color: var(--accent);\n box-shadow: 0 0 0 3px var(--accent-soft);\n }\n .input-dark::placeholder { color: var(--text-muted); }\n\n select.input-dark {\n appearance: none;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%2394a3b8' viewBox='0 0 16 16'%3E%3Cpath d='M4.646 5.646a.5.5 0 0 1 .708 0L8 8.293l2.646-2.647a.5.5 0 0 1 .708.708l-3 3a.5.5 0 0 1-.708 0l-3-3a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 10px center;\n padding-right: 32px;\n cursor: pointer;\n }\n\n textarea.input-dark {\n resize: vertical;\n min-height: 70px;\n }\n\n /* 标签按钮动画 */\n .tab-btn {\n position: relative;\n transition: all var(--transition-base);\n }\n .tab-btn.active::after {\n content: '';\n position: absolute;\n bottom: -1px;\n left: 50%;\n transform: translateX(-50%);\n width: 60%;\n height: 2px;\n background: var(--accent);\n border-radius: 2px;\n box-shadow: 0 0 10px var(--accent-glow);\n }\n\n /* 按钮 */\n .btn-primary {\n background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);\n color: #ffffff;\n border: none;\n border-radius: var(--radius-sm);\n padding: 9px 18px;\n font-family: var(--font-body);\n font-weight: 600;\n font-size: 13px;\n cursor: pointer;\n transition: all var(--transition-fast);\n letter-spacing: 0.01em;\n }\n .btn-primary:hover {\n box-shadow: 0 0 20px rgba(37,99,235,0.3);\n transform: translateY(-1px);\n }\n .btn-primary:active { transform: translateY(0); }\n\n .btn-outline {\n background: transparent;\n border: 1px solid var(--border-subtle);\n color: var(--text-secondary);\n border-radius: var(--radius-sm);\n padding: 8px 16px;\n font-family: var(--font-body);\n font-weight: 500;\n font-size: 13px;\n cursor: pointer;\n transition: all var(--transition-fast);\n }\n .btn-outline:hover {\n border-color: var(--border-active);\n color: var(--text-primary);\n background: rgba(37,99,235,0.03);\n }\n\n .btn-ghost {\n background: transparent;\n border: 1px solid transparent;\n color: var(--text-secondary);\n border-radius: var(--radius-sm);\n padding: 7px 12px;\n font-family: var(--font-body);\n font-weight: 500;\n font-size: 12px;\n cursor: pointer;\n transition: all var(--transition-fast);\n }\n .btn-ghost:hover { color: var(--text-primary); background: rgba(0,0,0,0.03); }\n .btn-ghost.danger:hover { color: var(--red-status); background: rgba(239,68,68,0.06); }\n\n /* 表格 */\n .table-dark {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n }\n .table-dark th {\n text-align: left;\n padding: 10px 12px;\n font-weight: 600;\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--text-muted);\n border-bottom: 1px solid var(--border-subtle);\n white-space: nowrap;\n }\n .table-dark td {\n padding: 10px 12px;\n border-bottom: 1px solid #f1f5f9;\n color: var(--text-secondary);\n white-space: nowrap;\n }\n .table-dark tbody tr {\n transition: all var(--transition-fast);\n }\n .table-dark tbody tr:hover {\n background: rgba(37,99,235,0.02);\n }\n\n /* 复选框 */\n .checkbox-custom {\n width: 18px;\n height: 18px;\n border: 1.5px solid #cbd5e1;\n border-radius: 4px;\n background: #ffffff;\n cursor: pointer;\n appearance: none;\n transition: all var(--transition-fast);\n flex-shrink: 0;\n position: relative;\n }\n .checkbox-custom:checked {\n background: var(--accent);\n border-color: var(--accent);\n }\n .checkbox-custom:checked::after {\n content: '\\f00c';\n font-family: 'Font Awesome 6 Free';\n font-weight: 900;\n font-size: 10px;\n color: #ffffff;\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n }\n\n /* Badge */\n .badge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 10px;\n border-radius: 20px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.02em;\n font-family: var(--font-body);\n }\n .badge-blue { background: rgba(37,99,235,0.08); color: #2563eb; }\n .badge-teal { background: rgba(6,182,212,0.08); color: #0891b2; }\n .badge-gold { background: rgba(37,99,235,0.08); color: #2563eb; }\n .badge-green { background: rgba(16,185,129,0.08); color: #059669; }\n .badge-orange { background: rgba(245,158,11,0.08); color: #d97706; }\n .badge-red { background: rgba(239,68,68,0.08); color: #dc2626; }\n\n /* 展开面板 */\n .expand-row {\n display: grid;\n grid-template-rows: 0fr;\n transition: grid-template-rows var(--transition-smooth);\n }\n .expand-row.open {\n grid-template-rows: 1fr;\n }\n .expand-row > div { overflow: hidden; }\n\n /* 脉冲点 */\n .pulse-dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n display: inline-block;\n position: relative;\n }\n .pulse-dot.live {\n background: var(--green-status);\n box-shadow: 0 0 0 0 rgba(16,185,129,0.5);\n animation: pulse-ring 2s infinite;\n }\n @keyframes pulse-ring {\n 0% { box-shadow: 0 0 0 0 rgba(16,185,129,0.5); }\n 70% { box-shadow: 0 0 0 8px rgba(16,185,129,0); }\n 100% { box-shadow: 0 0 0 0 rgba(16,185,129,0); }\n }\n\n /* Toast */\n .toast {\n position: fixed;\n top: 24px;\n right: 24px;\n z-index: 9999;\n padding: 12px 20px;\n border-radius: var(--radius-md);\n font-size: 13px;\n font-weight: 500;\n transform: translateX(120%);\n transition: transform var(--transition-smooth);\n pointer-events: none;\n background: #ffffff;\n border: 1px solid var(--border-subtle);\n color: var(--text-primary);\n box-shadow: 0 12px 40px rgba(0,0,0,0.10);\n }\n .toast.show { transform: translateX(0); }\n .toast.success { border-color: rgba(16,185,129,0.4); border-left: 3px solid #10b981; }\n .toast.error { border-color: rgba(239,68,68,0.4); border-left: 3px solid #ef4444; }\n\n /* 模态 */\n .modal-overlay {\n position: fixed;\n inset: 0;\n background: rgba(15,23,42,0.45);\n z-index: 9000;\n display: flex;\n align-items: center;\n justify-content: center;\n backdrop-filter: blur(4px);\n opacity: 0;\n pointer-events: none;\n transition: opacity var(--transition-smooth);\n }\n .modal-overlay.show { opacity: 1; pointer-events: auto; }\n .modal-content {\n background: #ffffff;\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-xl);\n padding: 28px;\n max-width: 720px;\n width: 90%;\n max-height: 85vh;\n overflow-y: auto;\n box-shadow: var(--shadow-modal);\n }\n\n /* 标签页内容过渡 */\n .tab-content {\n display: none;\n animation: fadeSlideIn 0.35s ease-out;\n }\n .tab-content.active { display: block; }\n @keyframes fadeSlideIn {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n }\n\n /* 统计卡片数字 */\n .stat-number {\n font-family: var(--font-heading);\n font-weight: 700;\n letter-spacing: -0.02em;\n line-height: 1;\n }\n\n /* 文件上传区域 */\n .upload-zone {\n border: 2px dashed #cbd5e1;\n border-radius: var(--radius-md);\n padding: 24px;\n text-align: center;\n cursor: pointer;\n transition: all var(--transition-base);\n background: var(--bg-input);\n }\n .upload-zone:hover {\n border-color: var(--accent);\n background: rgba(37,99,235,0.02);\n }\n\n /* 迷你图表容器 */\n .mini-chart { width: 100%; height: 280px; }\n .chart-card { width: 100%; height: 320px; }\n\n @media (max-width: 1024px) {\n .lg\\:grid-cols-2 { grid-template-columns: 1fr; }\n .lg\\:col-span-2 { grid-column: span 1; }\n }\n </style>\n</head>\n<body class=\"relative z-10\">\n\n <!-- Toast -->\n <div id=\"toast\" class=\"toast\"></div>\n\n <!-- 详情模态框 -->\n <div id=\"detailModal\" class=\"modal-overlay\" onclick=\"closeModal(event)\">\n <div class=\"modal-content\" onclick=\"event.stopPropagation()\">\n <div class=\"flex items-center justify-between mb-5\">\n <h3 class=\"text-lg font-semibold text-slate-800\" style=\"font-family: var(--font-heading);\">事件详情</h3>\n <button onclick=\"document.getElementById('detailModal').classList.remove('show')\" class=\"btn-ghost text-lg text-slate-400 hover:text-slate-600\"><i class=\"fa-solid fa-xmark\"></i></button>\n </div>\n <div id=\"modalBody\" class=\"space-y-4 text-sm\"></div>\n </div>\n </div>\n\n <!-- ========== 顶部导航 ========== -->\n <nav class=\"sticky top-0 z-50 bg-white/90 backdrop-blur-xl border-b border-slate-200 topnav-glow\">\n <div class=\"max-w-[1440px] mx-auto px-6 flex items-center justify-between h-14\">\n <div class=\"flex items-center gap-8\">\n <div class=\"flex items-center gap-2.5\">\n <div class=\"w-8 h-8 rounded-lg flex items-center justify-center\" style=\"background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);\">\n <i class=\"fa-solid fa-microchip text-white text-sm\"></i>\n </div>\n <span class=\"font-semibold text-[15px] tracking-tight text-slate-800\" style=\"font-family: var(--font-heading);\">Strate<span class=\"text-[#2563eb]\">Forge</span></span>\n </div>\n <div class=\"flex items-center gap-1 bg-slate-100 rounded-lg p-0.5 border border-slate-200\">\n <button class=\"tab-btn active px-4 py-1.5 rounded-md text-[13px] font-medium transition-all text-slate-800\" data-tab=\"tab-factory\" onclick=\"switchTab('tab-factory', this)\" style=\"font-family: var(--font-body); background: #ffffff; box-shadow: 0 1px 2px rgba(0,0,0,0.04);\">\n <i class=\"fa-solid fa-cubes mr-1.5 text-[11px] text-[#2563eb]\"></i>元件工厂\n </button>\n <button class=\"tab-btn px-4 py-1.5 rounded-md text-[13px] font-medium transition-all text-slate-500\" data-tab=\"tab-statistics\" onclick=\"switchTab('tab-statistics', this)\" style=\"font-family: var(--font-body);\">\n <i class=\"fa-solid fa-chart-pie mr-1.5 text-[11px]\"></i>策略成效统计\n </button>\n <button class=\"tab-btn px-4 py-1.5 rounded-md text-[13px] font-medium transition-all text-slate-500\" data-tab=\"tab-traceback\" onclick=\"switchTab('tab-traceback', this)\" style=\"font-family: var(--font-body);\">\n <i class=\"fa-solid fa-clock-rotate-left mr-1.5 text-[11px]\"></i>策略回溯\n </button>\n </div>\n </div>\n <div class=\"flex items-center gap-3\">\n <span class=\"text-xs text-slate-400\"><i class=\"fa-solid fa-circle pulse-dot live mr-1.5\"></i>系统运行中</span>\n <div class=\"w-7 h-7 rounded-full flex items-center justify-center text-xs font-semibold text-white\" style=\"background: linear-gradient(135deg, #3b82f6, #06b6d4);\">A</div>\n </div>\n </div>\n </nav>\n\n <!-- ========== 主内容区 ========== -->\n <main class=\"max-w-[1440px] mx-auto px-6 py-5 relative z-10\">\n\n <!-- ==================== Tab 1: 元件工厂 ==================== -->\n <div id=\"tab-factory\" class=\"tab-content active\">\n <div class=\"grid grid-cols-1 lg:grid-cols-3 gap-5\">\n\n <!-- 左侧:元件类型选择 + 表单 -->\n <div class=\"lg:col-span-1 space-y-4\">\n <!-- 类型选择卡片 -->\n <div class=\"card-elevate rounded-xl p-4 border border-slate-200\" style=\"background: var(--bg-card);\">\n <h3 class=\"text-xs font-semibold uppercase tracking-widest text-slate-400 mb-3\" style=\"font-family: var(--font-heading);\">元件类型</h3>\n <div class=\"space-y-2\">\n <button class=\"comp-type-btn w-full text-left px-3 py-3 rounded-lg border transition-all flex items-center gap-3 active-type\" data-type=\"event\" onclick=\"selectCompType('event', this)\" style=\"border-color: #2563eb; background: rgba(37,99,235,0.04);\">\n <div class=\"w-9 h-9 rounded-lg flex items-center justify-center flex-shrink-0\" style=\"background: rgba(37,99,235,0.10);\"><i class=\"fa-solid fa-bolt text-[#2563eb]\"></i></div>\n <div>\n <div class=\"text-[13px] font-semibold text-slate-800\">事件元件</div>\n <div class=\"text-[11px] text-slate-400\">Kafka / ActiveMQ / RabbitMQ</div>\n </div>\n </button>\n <button class=\"comp-type-btn w-full text-left px-3 py-3 rounded-lg border transition-all flex items-center gap-3\" data-type=\"algorithm\" onclick=\"selectCompType('algorithm', this)\" style=\"border-color: var(--border-subtle); background: transparent;\">\n <div class=\"w-9 h-9 rounded-lg flex items-center justify-center flex-shrink-0\" style=\"background: rgba(59,130,246,0.08);\"><i class=\"fa-solid fa-brain text-[#3b82f6]\"></i></div>\n <div>\n <div class=\"text-[13px] font-semibold text-slate-800\">策略算法元件</div>\n <div class=\"text-[11px] text-slate-400\">支持算法包上传</div>\n </div>\n </button>\n <button class=\"comp-type-btn w-full text-left px-3 py-3 rounded-lg border transition-all flex items-center gap-3\" data-type=\"function\" onclick=\"selectCompType('function', this)\" style=\"border-color: var(--border-subtle); background: transparent;\">\n <div class=\"w-9 h-9 rounded-lg flex items-center justify-center flex-shrink-0\" style=\"background: rgba(6,182,212,0.08);\"><i class=\"fa-solid fa-code text-[#0891b2]\"></i></div>\n <div>\n <div class=\"text-[13px] font-semibold text-slate-800\">函数元件</div>\n <div class=\"text-[11px] text-slate-400\">轻量级函数逻辑</div>\n </div>\n </button>\n </div>\n </div>\n\n <!-- 新增元件表单 -->\n <div class=\"card-elevate rounded-xl p-4 border border-slate-200\" style=\"background: var(--bg-card);\">\n <h3 class=\"text-xs font-semibold uppercase tracking-widest text-slate-400 mb-3\" style=\"font-family: var(--font-heading);\">新增元件</h3>\n <form id=\"compForm\" class=\"space-y-3\" onsubmit=\"handleAddComponent(event)\">\n <!-- 通用字段 -->\n <div>\n <label class=\"text-[11px] font-medium text-slate-500 uppercase tracking-wide mb-1 block\">元件名称 <span class=\"text-red-500\">*</span></label>\n <input type=\"text\" class=\"input-dark\" id=\"compName\" placeholder=\"输入元件名称\" required>\n </div>\n <div class=\"grid grid-cols-2 gap-3\">\n <div>\n <label class=\"text-[11px] font-medium text-slate-500 uppercase tracking-wide mb-1 block\">元件标识 <span class=\"text-red-500\">*</span></label>\n <input type=\"text\" class=\"input-dark\" id=\"compId\" placeholder=\"如: event_001\" required>\n </div>\n <div>\n <label class=\"text-[11px] font-medium text-slate-500 uppercase tracking-wide mb-1 block\">元件版本 <span class=\"text-red-500\">*</span></label>\n <input type=\"text\" class=\"input-dark\" id=\"compVersion\" placeholder=\"如: 1.0.0\" value=\"1.0.0\" required>\n </div>\n </div>\n <div>\n <label class=\"text-[11px] font-medium text-slate-500 uppercase tracking-wide mb-1 block\">元件描述</label>\n <textarea class=\"input-dark\" id=\"compDesc\" placeholder=\"输入元件描述信息\" rows=\"2\"></textarea>\n </div>\n\n <!-- 事件元件专属字段 -->\n <div id=\"eventFields\">\n <div class=\"grid grid-cols-2 gap-3\">\n <div>\n <label class=\"text-[11px] font-medium text-slate-500 uppercase tracking-wide mb-1 block\">事件名称 <span class=\"text-red-500\">*</span></label>\n <input type=\"text\" class=\"input-dark\" id=\"eventName\" placeholder=\"事件名称\">\n </div>\n <div>\n <label class=\"text-[11px] font-medium text-slate-500 uppercase tracking-wide mb-1 block\">事件标识 <span class=\"text-red-500\">*</span></label>\n <input type=\"text\" class=\"input-dark\" id=\"eventId\" placeholder=\"事件标识\">\n </div>\n </div>\n <div class=\"grid grid-cols-2 gap-3 mt-3\">\n <div>\n <label class=\"text-[11px] font-medium text-slate-500 uppercase tracking-wide mb-1 block\">解析类型 <span class=\"text-red-500\">*</span></label>\n <select class=\"input-dark\" id=\"parseType\">\n <option value=\"\">请选择</option>\n <option value=\"json\">JSON</option>\n <option value=\"xml\">XML</option>\n <option value=\"avro\">Avro</option>\n <option value=\"protobuf\">Protobuf</option>\n <option value=\"csv\">CSV</option>\n </select>\n </div>\n <div>\n <label class=\"text-[11px] font-medium text-slate-500 uppercase tracking-wide mb-1 block\">数据源类型 <span class=\"text-red-500\">*</span></label>\n <select class=\"input-dark\" id=\"dataSourceType\">\n <option value=\"\">请选择</option>\n <option value=\"kafka\">Kafka</option>\n <option value=\"activeMQ\">ActiveMQ</option>\n <option value=\"rabbitMQ\">RabbitMQ</option>\n </select>\n </div>\n </div>\n </div>\n\n <!-- 算法元件专属字段 -->\n <div id=\"algorithmFields\" class=\"hidden\">\n <div class=\"upload-zone\" onclick=\"document.getElementById('algoFileInput').click()\">\n <i class=\"fa-solid fa-cloud-arrow-up text-2xl text-slate-300 mb-1 block\"></i>\n <span class=\"text-xs text-slate-500\">点击上传算法包(.jar / .zip)</span>\n <input type=\"file\" id=\"algoFileInput\" class=\"hidden\" accept=\".jar,.zip,.tar.gz\" onchange=\"handleAlgoFile(this)\">\n </div>\n <div id=\"algoFileInfo\" class=\"hidden mt-2 text-xs text-[#0891b2]\"><i class=\"fa-solid fa-file-zipper mr-1\"></i><span></span></div>\n <div class=\"grid grid-cols-2 gap-3 mt-3\">\n <div>\n <label class=\"text-[11px] font-medium text-slate-500 uppercase tracking-wide mb-1 block\">算法名称</label>\n <input type=\"text\" class=\"input-dark\" id=\"algoName\" placeholder=\"算法名称\">\n </div>\n <div>\n <label class=\"text-[11px] font-medium text-slate-500 uppercase tracking-wide mb-1 block\">算法标识</label>\n <input type=\"text\" class=\"input-dark\" id=\"algoId\" placeholder=\"算法标识\">\n </div>\n </div>\n <div class=\"mt-3\">\n <label class=\"text-[11px] font-medium text-slate-500 uppercase tracking-wide mb-1 block\">算法版本</label>\n <input type=\"text\" class=\"input-dark\" id=\"algoVersion\" placeholder=\"如: 2.1.0\">\n </div>\n </div>\n\n <!-- 函数元件无额外字段 -->\n <div id=\"functionFields\" class=\"hidden\">\n <div class=\"text-xs text-slate-400 py-2 px-3 rounded-md\" style=\"background: var(--bg-input);\">\n <i class=\"fa-solid fa-info-circle mr-1 text-[#3b82f6]\"></i>函数元件仅需填写通用信息即可创建\n </div>\n </div>\n\n <button type=\"submit\" class=\"btn-primary w-full mt-2\">\n <i class=\"fa-solid fa-plus mr-1.5\"></i>新增元件\n </button>\n </form>\n </div>\n </div>\n\n <!-- 右侧:元件列表 -->\n <div class=\"lg:col-span-2\">\n <div class=\"card-elevate rounded-xl border border-slate-200\" style=\"background: var(--bg-card);\">\n <div class=\"flex items-center justify-between px-4 py-3 border-b border-slate-100\">\n <div class=\"flex items-center gap-3\">\n <h3 class=\"text-xs font-semibold uppercase tracking-widest text-slate-400\" style=\"font-family: var(--font-heading);\">元件列表</h3>\n <span class=\"badge badge-blue\" id=\"compCount\">共 0 个</span>\n </div>\n <div class=\"flex items-center gap-2\">\n <button class=\"btn-outline text-xs\" onclick=\"batchAction('publish')\"><i class=\"fa-solid fa-arrow-up mr-1\"></i>上架</button>\n <button class=\"btn-outline text-xs\" onclick=\"batchAction('unpublish')\"><i class=\"fa-solid fa-arrow-down mr-1\"></i>下架</button>\n <button class=\"btn-ghost danger text-xs\" onclick=\"batchAction('delete')\"><i class=\"fa-solid fa-trash-can mr-1\"></i>删除</button>\n </div>\n </div>\n <div class=\"overflow-x-auto max-h-[500px] overflow-y-auto\">\n <table class=\"table-dark\">\n <thead class=\"sticky top-0 z-10\" style=\"background: #ffffff;\">\n <tr>\n <th style=\"width:36px;\"><input type=\"checkbox\" class=\"checkbox-custom\" id=\"selectAll\" onchange=\"toggleSelectAll(this)\"></th>\n <th>元件名称</th>\n <th>元件标识</th>\n <th>版本</th>\n <th>类型</th>\n <th>状态</th>\n <th>创建时间</th>\n <th style=\"width:80px;\">操作</th>\n </tr>\n </thead>\n <tbody id=\"compTableBody\">\n <!-- 动态渲染 -->\n </tbody>\n </table>\n </div>\n <div id=\"emptyState\" class=\"py-16 text-center\">\n <i class=\"fa-solid fa-cube text-4xl text-slate-200 mb-3 block\"></i>\n <p class=\"text-sm text-slate-400\">暂无元件,请在左侧创建</p>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- ==================== Tab 2: 策略成效统计 ==================== -->\n <div id=\"tab-statistics\" class=\"tab-content\">\n <!-- 统计卡片 -->\n <div class=\"grid grid-cols-2 lg:grid-cols-4 gap-4 mb-5\">\n <div class=\"card-elevate rounded-xl p-4 border border-slate-200\" style=\"background: var(--bg-card);\">\n <div class=\"flex items-center justify-between mb-2\">\n <span class=\"text-[11px] uppercase tracking-wider text-slate-400 font-semibold\">策略总数</span>\n <div class=\"w-8 h-8 rounded-lg flex items-center justify-center\" style=\"background: rgba(37,99,235,0.08);\"><i class=\"fa-solid fa-shield-halved text-[#2563eb] text-xs\"></i></div>\n </div>\n <div class=\"stat-number text-3xl text-slate-800\">248</div>\n <div class=\"text-[11px] text-emerald-500 mt-1\"><i class=\"fa-solid fa-arrow-up text-[10px]\"></i> 较上月 +12</div>\n </div>\n <div class=\"card-elevate rounded-xl p-4 border border-slate-200\" style=\"background: var(--bg-card);\">\n <div class=\"flex items-center justify-between mb-2\">\n <span class=\"text-[11px] uppercase tracking-wider text-slate-400 font-semibold\">策略分组</span>\n <div class=\"w-8 h-8 rounded-lg flex items-center justify-center\" style=\"background: rgba(59,130,246,0.08);\"><i class=\"fa-solid fa-layer-group text-[#3b82f6] text-xs\"></i></div>\n </div>\n <div class=\"stat-number text-3xl text-slate-800\">16</div>\n <div class=\"text-[11px] text-slate-500 mt-1\">覆盖 6 大业务域</div>\n </div>\n <div class=\"card-elevate rounded-xl p-4 border border-slate-200\" style=\"background: var(--bg-card);\">\n <div class=\"flex items-center justify-between mb-2\">\n <span class=\"text-[11px] uppercase tracking-wider text-slate-400 font-semibold\">启用中</span>\n <div class=\"w-8 h-8 rounded-lg flex items-center justify-center\" style=\"background: rgba(16,185,129,0.08);\"><i class=\"fa-solid fa-circle-check text-[#10b981] text-xs\"></i></div>\n </div>\n <div class=\"stat-number text-3xl\" style=\"color: #059669;\">189</div>\n <div class=\"text-[11px] text-slate-500 mt-1\">占比 76.2%</div>\n </div>\n <div class=\"card-elevate rounded-xl p-4 border border-slate-200\" style=\"background: var(--bg-card);\">\n <div class=\"flex items-center justify-between mb-2\">\n <span class=\"text-[11px] uppercase tracking-wider text-slate-400 font-semibold\">已停用</span>\n <div class=\"w-8 h-8 rounded-lg flex items-center justify-center\" style=\"background: rgba(239,68,68,0.08);\"><i class=\"fa-solid fa-circle-pause text-[#ef4444] text-xs\"></i></div>\n </div>\n <div class=\"stat-number text-3xl\" style=\"color: #dc2626;\">59</div>\n <div class=\"text-[11px] text-slate-500 mt-1\">占比 23.8%</div>\n </div>\n </div>\n\n <!-- 图表行 -->\n <div class=\"grid grid-cols-1 lg:grid-cols-2 gap-5 mb-5\">\n <div class=\"card-elevate rounded-xl p-4 border border-slate-200\" style=\"background: var(--bg-card);\">\n <h4 class=\"text-xs font-semibold uppercase tracking-widest text-slate-400 mb-3\" style=\"font-family: var(--font-heading);\">事件流转漏斗</h4>\n <div id=\"chartFunnel\" class=\"mini-chart\"></div>\n </div>\n <div class=\"card-elevate rounded-xl p-4 border border-slate-200\" style=\"background: var(--bg-card);\">\n <h4 class=\"text-xs font-semibold uppercase tracking-widest text-slate-400 mb-3\" style=\"font-family: var(--font-heading);\">过滤占比 & 输出占比</h4>\n <div id=\"chartPie\" class=\"mini-chart\"></div>\n </div>\n </div>\n\n <!-- 策略输出排名 -->\n <div class=\"grid grid-cols-1 lg:grid-cols-2 gap-5\">\n <div class=\"card-elevate rounded-xl p-4 border border-slate-200\" style=\"background: var(--bg-card);\">\n <h4 class=\"text-xs font-semibold uppercase tracking-widest text-slate-400 mb-3\" style=\"font-family: var(--font-heading);\">策略输出事件排名</h4>\n <div id=\"chartBar\" class=\"mini-chart\"></div>\n </div>\n <div class=\"card-elevate rounded-xl p-4 border border-slate-200\" style=\"background: var(--bg-card);\">\n <h4 class=\"text-xs font-semibold uppercase tracking-widest text-slate-400 mb-3\" style=\"font-family: var(--font-heading);\">智能事件成效明细</h4>\n <div class=\"overflow-x-auto max-h-[280px] overflow-y-auto\">\n <table class=\"table-dark text-xs\">\n <thead>\n <tr>\n <th>事件类型</th>\n <th>原始事件</th>\n <th>过滤事件</th>\n <th>输出事件</th>\n <th>过滤占比</th>\n <th>输出占比</th>\n </tr>\n </thead>\n <tbody>\n <tr><td><span class=\"badge badge-blue\">车辆识别</span></td><td>12,840</td><td>3,210</td><td>9,630</td><td>25.0%</td><td>75.0%</td></tr>\n <tr><td><span class=\"badge badge-teal\">人脸抓拍</span></td><td>8,920</td><td>1,784</td><td>7,136</td><td>20.0%</td><td>80.0%</td></tr>\n <tr><td><span class=\"badge badge-gold\">行为分析</span></td><td>6,450</td><td>2,580</td><td>3,870</td><td>40.0%</td><td>60.0%</td></tr>\n <tr><td><span class=\"badge badge-orange\">轨迹追踪</span></td><td>4,320</td><td>1,296</td><td>3,024</td><td>30.0%</td><td>70.0%</td></tr>\n <tr><td><span class=\"badge badge-green\">异常预警</span></td><td>3,100</td><td>465</td><td>2,635</td><td>15.0%</td><td>85.0%</td></tr>\n <tr><td><span class=\"badge badge-red\">安全告警</span></td><td>2,780</td><td>1,112</td><td>1,668</td><td>40.0%</td><td>60.0%</td></tr>\n </tbody>\n </table>\n </div>\n </div>\n </div>\n </div>\n\n <!-- ==================== Tab 3: 策略回溯 ==================== -->\n <div id=\"tab-traceback\" class=\"tab-content\">\n <!-- 检索栏 -->\n <div class=\"card-elevate rounded-xl p-4 border border-slate-200 mb-5\" style=\"background: var(--bg-card);\">\n <h3 class=\"text-xs font-semibold uppercase tracking-widest text-slate-400 mb-3\" style=\"font-family: var(--font-heading);\">检索条件</h3>\n <div class=\"grid grid-cols-2 lg:grid-cols-5 gap-3\">\n <div>\n <label class=\"text-[10px] font-medium text-slate-500 uppercase tracking-wide mb-1 block\">事件类型</label>\n <select class=\"input-dark text-xs\" id=\"searchEventType\">\n <option value=\"\">全部</option>\n <option>车辆识别</option>\n <option>人脸抓拍</option>\n <option>行为分析</option>\n <option>轨迹追踪</option>\n <option>异常预警</option>\n </select>\n </div>\n <div>\n <label class=\"text-[10px] font-medium text-slate-500 uppercase tracking-wide mb-1 block\">上报时间起</label>\n <input type=\"datetime-local\" class=\"input-dark text-xs\" id=\"searchTimeStart\">\n </div>\n <div>\n <label class=\"text-[10px] font-medium text-slate-500 uppercase tracking-wide mb-1 block\">上报时间止</label>\n <input type=\"datetime-local\" class=\"input-dark text-xs\" id=\"searchTimeEnd\">\n </div>\n <div>\n <label class=\"text-[10px] font-medium text-slate-500 uppercase tracking-wide mb-1 block\">事件点位</label>\n <input type=\"text\" class=\"input-dark text-xs\" id=\"searchLocation\" placeholder=\"点位编号或名称\">\n </div>\n <div>\n <label class=\"text-[10px] font-medium text-slate-500 uppercase tracking-wide mb-1 block\">策略识别结果</label>\n <select class=\"input-dark text-xs\" id=\"searchResult\">\n <option value=\"\">全部</option>\n <option>通过</option>\n <option>过滤</option>\n <option>告警</option>\n <option>待审核</option>\n </select>\n </div>\n </div>\n <div class=\"flex gap-2 mt-3\">\n <button class=\"btn-primary text-xs\" onclick=\"handleSearch()\"><i class=\"fa-solid fa-magnifying-glass mr-1\"></i>检索</button>\n <button class=\"btn-outline text-xs\" onclick=\"resetSearch()\"><i class=\"fa-solid fa-rotate mr-1\"></i>重置</button>\n </div>\n </div>\n\n <!-- 回溯数据表格 -->\n <div class=\"card-elevate rounded-xl border border-slate-200\" style=\"background: var(--bg-card);\">\n <div class=\"flex items-center justify-between px-4 py-3 border-b border-slate-100\">\n <h3 class=\"text-xs font-semibold uppercase tracking-widest text-slate-400\" style=\"font-family: var(--font-heading);\">\n 策略回溯数据 <span class=\"badge badge-blue ml-2\" id=\"traceCount\">共 1,286 条</span>\n </h3>\n <span class=\"text-[11px] text-slate-400\"><i class=\"fa-solid fa-database mr-1\"></i>最近7天</span>\n </div>\n <div class=\"overflow-x-auto max-h-[500px] overflow-y-auto\">\n <table class=\"table-dark\">\n <thead class=\"sticky top-0 z-10\" style=\"background: #ffffff;\">\n <tr>\n <th style=\"width:40px;\"></th>\n <th>事件ID</th>\n <th>事件类型</th>\n <th>上报时间</th>\n <th>事件点位</th>\n <th>策略名称</th>\n <th>过滤元件</th>\n <th>识别结果</th>\n <th style=\"width:80px;\">操作</th>\n </tr>\n </thead>\n <tbody id=\"traceTableBody\">\n <!-- 动态渲染 -->\n </tbody>\n </table>\n </div>\n </div>\n </div>\n\n </main>\n\n <script>\n // ============ 全局状态 ============\n const state = {\n currentTab: 'tab-factory',\n compType: 'event',\n components: [\n { id: 'evt_001', name: '车辆卡口事件', compId: 'event_vehicle', version: '1.2.0', desc: '车辆卡口抓拍数据接入', type: 'event', status: 'published', eventName: 'VehiclePass', eventId: 'evt_vp_01', parseType: 'json', dataSource: 'kafka', createdAt: '2025-12-18 14:30', algoName: '', algoId: '', algoVersion: '' },\n { id: 'evt_002', name: '人脸抓拍事件', compId: 'event_face', version: '2.0.1', desc: '人脸抓拍机数据流接入', type: 'event', status: 'published', eventName: 'FaceCapture', eventId: 'evt_fc_02', parseType: 'avro', dataSource: 'rabbitMQ', createdAt: '2025-12-20 09:15', algoName: '', algoId: '', algoVersion: '' },\n { id: 'alg_001', name: '车辆特征提取算法', compId: 'alg_vehicle_feat', version: '3.1.0', desc: '基于深度学习的车辆特征识别', type: 'algorithm', status: 'published', eventName: '', eventId: '', parseType: '', dataSource: '', createdAt: '2025-11-05 16:45', algoName: 'VehicleCNN', algoId: 'algo_vcnn', algoVersion: '3.1.0' },\n { id: 'alg_002', name: '人脸比对算法', compId: 'alg_face_match', version: '2.4.2', desc: '高精度人脸1:1比对', type: 'algorithm', status: 'unpublished', eventName: '', eventId: '', parseType: '', dataSource: '', createdAt: '2025-10-22 11:00', algoName: 'FaceMatchV2', algoId: 'algo_fmv2', algoVersion: '2.4.2' },\n { id: 'fn_001', name: '数据脱敏函数', compId: 'func_mask', version: '1.0.0', desc: '敏感字段自动脱敏处理', type: 'function', status: 'published', eventName: '', eventId: '', parseType: '', dataSource: '', createdAt: '2025-12-25 08:30', algoName: '', algoId: '', algoVersion: '' },\n { id: 'fn_002', name: '时间戳格式化', compId: 'func_ts_fmt', version: '0.9.5', desc: '统一时间戳格式转换', type: 'function', status: 'draft', eventName: '', eventId: '', parseType: '', dataSource: '', createdAt: '2026-01-02 14:20', algoName: '', algoId: '', algoVersion: '' },\n ],\n traceData: [\n { eventId: 'TRC-20260112-001', eventType: '车辆识别', reportTime: '2026-01-12 08:32:15', location: '点位A-东门卡口', strategyName: '车辆黑名单策略', filterComp: '车牌比对元件', result: '过滤', filterReason: '车牌号命中黑名单库:粤B****8', hasImage: true },\n { eventId: 'TRC-20260112-002', eventType: '人脸抓拍', reportTime: '2026-01-12 08:35:42', location: '点位B-大厅入口', strategyName: '人脸识别策略', filterComp: '人脸比对元件', result: '通过', filterReason: '', hasImage: true },\n { eventId: 'TRC-20260112-003', eventType: '行为分析', reportTime: '2026-01-12 09:01:08', location: '点位C-停车场', strategyName: '异常行为检测', filterComp: '区域入侵元件', result: '告警', filterReason: '检测到禁区入侵行为', hasImage: true },\n { eventId: 'TRC-20260112-004', eventType: '轨迹追踪', reportTime: '2026-01-12 09:14:33', location: '点位D-走廊通道', strategyName: '轨迹分析策略', filterComp: '路径异常元件', result: '过滤', filterReason: '轨迹偏离常规路径超过阈值', hasImage: true },\n { eventId: 'TRC-20260112-005', eventType: '异常预警', reportTime: '2026-01-12 09:28:50', location: '点位E-机房入口', strategyName: '温湿度预警策略', filterComp: '阈值判断元件', result: '通过', filterReason: '', hasImage: false },\n { eventId: 'TRC-20260112-006', eventType: '安全告警', reportTime: '2026-01-12 09:45:11', location: '点位F-围墙周界', strategyName: '周界防护策略', filterComp: '电子围栏元件', result: '告警', filterReason: '周界震动传感器触发', hasImage: true },\n { eventId: 'TRC-20260112-007', eventType: '车辆识别', reportTime: '2026-01-12 10:02:29', location: '点位G-西门卡口', strategyName: '车辆白名单策略', filterComp: '车牌比对元件', result: '通过', filterReason: '', hasImage: true },\n { eventId: 'TRC-20260112-008', eventType: '人脸抓拍', reportTime: '2026-01-12 10:11:05', location: '点位H-电梯口', strategyName: '人脸识别策略', filterComp: '活体检测元件', result: '过滤', filterReason: '活体检测未通过,疑似照片攻击', hasImage: true },\n ],\n };\n\n // ============ Tab 切换 ============\n function switchTab(tabId, btn) {\n document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));\n document.querySelectorAll('.tab-btn').forEach(el => {\n el.classList.remove('active');\n el.style.color = '#64748b';\n el.style.background = 'transparent';\n el.style.boxShadow = 'none';\n });\n document.getElementById(tabId).classList.add('active');\n btn.classList.add('active');\n btn.style.color = '#1e293b';\n btn.style.background = '#ffffff';\n btn.style.boxShadow = '0 1px 2px rgba(0,0,0,0.04)';\n state.currentTab = tabId;\n if (tabId === 'tab-statistics') { setTimeout(initCharts, 200); }\n }\n\n // ============ 元件类型切换 ============\n function selectCompType(type, btn) {\n state.compType = type;\n document.querySelectorAll('.comp-type-btn').forEach(el => {\n el.style.borderColor = '#e2e8f0';\n el.style.background = 'transparent';\n el.classList.remove('active-type');\n });\n btn.style.borderColor = '#2563eb';\n btn.style.background = 'rgba(37,99,235,0.04)';\n btn.classList.add('active-type');\n\n document.getElementById('eventFields').classList.toggle('hidden', type !== 'event');\n document.getElementById('algorithmFields').classList.toggle('hidden', type !== 'algorithm');\n document.getElementById('functionFields').classList.toggle('hidden', type !== 'function');\n\n document.getElementById('compForm').reset();\n document.getElementById('algoFileInfo').classList.add('hidden');\n document.getElementById('compVersion').value = '1.0.0';\n }\n\n // ============ 算法文件上传 ============\n function handleAlgoFile(input) {\n if (input.files.length > 0) {\n const info = document.getElementById('algoFileInfo');\n info.classList.remove('hidden');\n info.querySelector('span').textContent = input.files[0].name;\n }\n }\n\n // ============ 新增元件 ============\n function handleAddComponent(e) {\n e.preventDefault();\n const type = state.compType;\n const comp = {\n id: 'comp_' + Date.now(),\n name: document.getElementById('compName').value,\n compId: document.getElementById('compId').value,\n version: document.getElementById('compVersion').value,\n desc: document.getElementById('compDesc').value || '',\n type: type,\n status: 'draft',\n eventName: type === 'event' ? (document.getElementById('eventName').value || '') : '',\n eventId: type === 'event' ? (document.getElementById('eventId').value || '') : '',\n parseType: type === 'event' ? document.getElementById('parseType').value : '',\n dataSource: type === 'event' ? document.getElementById('dataSourceType').value : '',\n createdAt: new Date().toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }),\n algoName: type === 'algorithm' ? (document.getElementById('algoName').value || '') : '',\n algoId: type === 'algorithm' ? (document.getElementById('algoId').value || '') : '',\n algoVersion: type === 'algorithm' ? (document.getElementById('algoVersion').value || '') : '',\n };\n state.components.unshift(comp);\n renderCompTable();\n document.getElementById('compForm').reset();\n document.getElementById('compVersion').value = '1.0.0';\n document.getElementById('algoFileInfo').classList.add('hidden');\n showToast('元件新增成功', 'success');\n }\n\n // ============ 渲染元件表格 ============\n function renderCompTable() {\n const tbody = document.getElementById('compTableBody');\n const empty = document.getElementById('emptyState');\n const count = document.getElementById('compCount');\n count.textContent = '共 ' + state.components.length + ' 个';\n\n if (state.components.length === 0) {\n tbody.innerHTML = '';\n empty.style.display = 'block';\n return;\n }\n empty.style.display = 'none';\n\n const typeLabels = { event: '事件元件', algorithm: '策略算法', function: '函数元件' };\n const typeBadges = { event: 'badge-blue', algorithm: 'badge-teal', function: 'badge-gold' };\n const statusLabels = { published: '已上架', unpublished: '已下架', draft: '草稿' };\n const statusBadges = { published: 'badge-green', unpublished: 'badge-orange', draft: 'badge-red' };\n\n tbody.innerHTML = state.components.map(c => `\n <tr>\n <td><input type=\"checkbox\" class=\"checkbox-custom row-checkbox\" data-id=\"${c.id}\"></td>\n <td class=\"font-medium text-slate-800 text-[13px]\">${escapeHtml(c.name)}</td>\n <td><code style=\"font-family:var(--font-mono);font-size:11px;color:#2563eb;\">${escapeHtml(c.compId)}</code></td>\n <td><span class=\"text-[11px] text-slate-500\" style=\"font-family:var(--font-mono);\">v${escapeHtml(c.version)}</span></td>\n <td><span class=\"badge ${typeBadges[c.type] || 'badge-blue'}\">${typeLabels[c.type] || c.type}</span></td>\n <td><span class=\"badge ${statusBadges[c.status] || 'badge-red'}\">${statusLabels[c.status] || c.status}</span></td>\n <td class=\"text-[12px] text-slate-400\">${c.createdAt}</td>\n <td>\n <div class=\"flex gap-1\">\n <button class=\"btn-ghost text-[11px]\" title=\"上架\" onclick=\"singleAction('publish','${c.id}')\"><i class=\"fa-solid fa-arrow-up text-[10px]\"></i></button>\n <button class=\"btn-ghost text-[11px]\" title=\"下架\" onclick=\"singleAction('unpublish','${c.id}')\"><i class=\"fa-solid fa-arrow-down text-[10px]\"></i></button>\n <button class=\"btn-ghost danger text-[11px]\" title=\"删除\" onclick=\"singleAction('delete','${c.id}')\"><i class=\"fa-solid fa-trash-can text-[10px]\"></i></button>\n </div>\n </td>\n </tr>\n `).join('');\n }\n\n function escapeHtml(str) {\n const div = document.createElement('div');\n div.textContent = str;\n return div.innerHTML;\n }\n\n function toggleSelectAll(cb) {\n document.querySelectorAll('.row-checkbox').forEach(el => el.checked = cb.checked);\n }\n\n function getSelectedIds() {\n return Array.from(document.querySelectorAll('.row-checkbox:checked')).map(el => el.dataset.id);\n }\n\n function batchAction(action) {\n const ids = getSelectedIds();\n if (ids.length === 0) { showToast('请至少选择一个元件', 'error'); return; }\n const labels = { publish: '上架', unpublish: '下架', delete: '删除' };\n if (action === 'delete' && !confirm('确定要删除选中的 ' + ids.length + ' 个元件吗?此操作不可恢复。')) return;\n ids.forEach(id => {\n const comp = state.components.find(c => c.id === id);\n if (!comp) return;\n if (action === 'publish') comp.status = 'published';\n else if (action === 'unpublish') comp.status = 'unpublished';\n else if (action === 'delete') state.components = state.components.filter(c => c.id !== id);\n });\n renderCompTable();\n document.getElementById('selectAll').checked = false;\n showToast(`已${labels[action]} ${ids.length} 个元件`, 'success');\n }\n\n function singleAction(action, id) {\n const comp = state.components.find(c => c.id === id);\n if (!comp) return;\n if (action === 'delete') {\n if (!confirm('确定删除该元件吗?')) return;\n state.components = state.components.filter(c => c.id !== id);\n } else if (action === 'publish') {\n comp.status = 'published';\n } else if (action === 'unpublish') {\n comp.status = 'unpublished';\n }\n renderCompTable();\n const labels = { publish: '上架', unpublish: '下架', delete: '删除' };\n showToast(`元件已${labels[action]}`, 'success');\n }\n\n function showToast(msg, type) {\n const toast = document.getElementById('toast');\n toast.textContent = msg;\n toast.className = 'toast ' + type + ' show';\n clearTimeout(toast._timeout);\n toast._timeout = setTimeout(() => { toast.classList.remove('show'); }, 2200);\n }\n\n function openDetail(eventId) {\n const item = state.traceData.find(t => t.eventId === eventId);\n if (!item) return;\n const body = document.getElementById('modalBody');\n const resultBadge = item.result === '通过' ? 'badge-green' : item.result === '过滤' ? 'badge-orange' : 'badge-red';\n body.innerHTML = `\n <div class=\"grid grid-cols-2 gap-3\">\n <div><span class=\"text-[11px] text-slate-400 uppercase tracking-wide\">事件ID</span><p class=\"text-sm font-mono text-slate-800\">${item.eventId}</p></div>\n <div><span class=\"text-[11px] text-slate-400 uppercase tracking-wide\">事件类型</span><p class=\"text-sm\"><span class=\"badge badge-blue\">${item.eventType}</span></p></div>\n <div><span class=\"text-[11px] text-slate-400 uppercase tracking-wide\">上报时间</span><p class=\"text-sm text-slate-800\">${item.reportTime}</p></div>\n <div><span class=\"text-[11px] text-slate-400 uppercase tracking-wide\">事件点位</span><p class=\"text-sm text-slate-800\">${item.location}</p></div>\n <div><span class=\"text-[11px] text-slate-400 uppercase tracking-wide\">策略名称</span><p class=\"text-sm text-slate-800\">${item.strategyName}</p></div>\n <div><span class=\"text-[11px] text-slate-400 uppercase tracking-wide\">过滤元件</span><p class=\"text-sm text-slate-800\">${item.filterComp}</p></div>\n <div><span class=\"text-[11px] text-slate-400 uppercase tracking-wide\">识别结果</span><p><span class=\"badge ${resultBadge}\">${item.result}</span></p></div>\n <div><span class=\"text-[11px] text-slate-400 uppercase tracking-wide\">关联图片</span><p class=\"text-sm\">${item.hasImage ? '<span class=\"text-[#2563eb]\"><i class=\"fa-solid fa-image mr-1\"></i>可查看</span>' : '<span class=\"text-slate-400\">无</span>'}</p></div>\n </div>\n ${item.filterReason ? `<div class=\"mt-3 p-3 rounded-lg border border-amber-200\" style=\"background: rgba(245,158,11,0.04);\"><span class=\"text-[11px] text-amber-600 uppercase tracking-wide font-semibold\"><i class=\"fa-solid fa-filter-circle-xmark mr-1\"></i>过滤原因</span><p class=\"text-sm text-slate-700 mt-1\">${item.filterReason}</p></div>` : ''}\n ${item.hasImage ? `<div class=\"mt-3 p-4 rounded-lg border border-slate-200 text-center\" style=\"background: var(--bg-input);\"><i class=\"fa-solid fa-image text-4xl text-slate-200 mb-2 block\"></i><span class=\"text-xs text-slate-400\">事件关联图像预览区域</span></div>` : ''}\n `;\n document.getElementById('detailModal').classList.add('show');\n }\n\n function closeModal(e) {\n if (e.target === e.currentTarget) {\n document.getElementById('detailModal').classList.remove('show');\n }\n }\n\n function renderTraceTable(data) {\n const tbody = document.getElementById('traceTableBody');\n const resultBadges = { '通过': 'badge-green', '过滤': 'badge-orange', '告警': 'badge-red', '待审核': 'badge-blue' };\n tbody.innerHTML = (data || state.traceData).map((t, i) => `\n <tr>\n <td><button class=\"btn-ghost text-[11px]\" onclick=\"toggleExpand(this, 'expand-${i}')\"><i class=\"fa-solid fa-chevron-down transition-transform duration-200 text-[10px]\"></i></button></td>\n <td><code style=\"font-family:var(--font-mono);font-size:11px;color:#2563eb;\">${t.eventId}</code></td>\n <td><span class=\"badge badge-blue\">${t.eventType}</span></td>\n <td class=\"text-[12px]\">${t.reportTime}</td>\n <td class=\"text-[12px]\">${t.location}</td>\n <td class=\"text-[13px] text-slate-800\">${t.strategyName}</td>\n <td class=\"text-[12px]\">${t.filterComp}</td>\n <td><span class=\"badge ${resultBadges[t.result] || 'badge-blue'}\">${t.result}</span></td>\n <td><button class=\"btn-ghost text-[11px] text-[#2563eb]\" onclick=\"openDetail('${t.eventId}')\"><i class=\"fa-solid fa-eye mr-0.5\"></i>详情</button></td>\n </tr>\n <tr class=\"expand-row\" id=\"expand-${i}\">\n <td colspan=\"9\">\n <div class=\"px-4 py-3 text-xs\" style=\"background: var(--bg-input);\">\n <div class=\"flex flex-wrap gap-4\">\n <div><span class=\"text-slate-400\">策略输出数据:</span><span class=\"text-slate-800\">${t.result === '过滤' ? '已拦截,未输出' : '正常输出至下游'}</span></div>\n ${t.filterReason ? `<div><span class=\"text-slate-400\">过滤原因:</span><span class=\"text-amber-600\">${t.filterReason}</span></div>` : ''}\n <div><span class=\"text-slate-400\">关联图片:</span>${t.hasImage ? '<span class=\"text-[#2563eb] cursor-pointer hover:underline\">点击查看</span>' : '<span class=\"text-slate-400\">无</span>'}</div>\n </div>\n </div>\n </td>\n </tr>\n `).join('');\n document.getElementById('traceCount').textContent = '共 ' + (data || state.traceData).length + ' 条';\n }\n\n function toggleExpand(btn, rowId) {\n const row = document.getElementById(rowId);\n const icon = btn.querySelector('i');\n row.classList.toggle('open');\n icon.style.transform = row.classList.contains('open') ? 'rotate(180deg)' : 'rotate(0deg)';\n }\n\n function handleSearch() {\n const type = document.getElementById('searchEventType').value;\n const result = document.getElementById('searchResult').value;\n let filtered = state.traceData;\n if (type) filtered = filtered.filter(t => t.eventType === type);\n if (result) filtered = filtered.filter(t => t.result === result);\n renderTraceTable(filtered);\n showToast('检索完成,共 ' + filtered.length + ' 条结果', 'success');\n }\n\n function resetSearch() {\n document.getElementById('searchEventType').value = '';\n document.getElementById('searchTimeStart').value = '';\n document.getElementById('searchTimeEnd').value = '';\n document.getElementById('searchLocation').value = '';\n document.getElementById('searchResult').value = '';\n renderTraceTable(state.traceData);\n showToast('检索条件已重置', 'success');\n }\n\n // ============ ECharts 初始化 ============\n function initCharts() {\n // 漏斗图\n const funnelDom = document.getElementById('chartFunnel');\n if (funnelDom && !funnelDom._inited) {\n funnelDom._inited = true;\n const funnelChart = echarts.init(funnelDom);\n funnelChart.setOption({\n tooltip: { trigger: 'item', backgroundColor: '#ffffff', borderColor: '#e2e8f0', textStyle: { color: '#1e293b', fontSize: 12 } },\n series: [{\n type: 'funnel', left: '10%', right: '10%', top: 20, bottom: 20,\n minSize: '18%', maxSize: '100%', gap: 2,\n label: { show: true, position: 'inside', fontSize: 11, color: '#fff', fontFamily: 'DM Sans' },\n labelLine: { show: false },\n itemStyle: { borderWidth: 0 },\n data: [\n { value: 38510, name: '原始事件', itemStyle: { color: '#3b82f6' } },\n { value: 30808, name: '策略过滤后', itemStyle: { color: '#f59e0b' } },\n { value: 22963, name: '策略输出事件', itemStyle: { color: '#06b6d4' } },\n ]\n }]\n });\n window.addEventListener('resize', () => funnelChart.resize());\n }\n\n // 饼图\n const pieDom = document.getElementById('chartPie');\n if (pieDom && !pieDom._inited) {\n pieDom._inited = true;\n const pieChart = echarts.init(pieDom);\n pieChart.setOption({\n tooltip: { trigger: 'item', backgroundColor: '#ffffff', borderColor: '#e2e8f0', textStyle: { color: '#1e293b', fontSize: 12 } },\n legend: { bottom: 0, textStyle: { color: '#64748b', fontSize: 10 }, itemWidth: 10, itemHeight: 10 },\n series: [{\n type: 'pie', radius: ['48%', '72%'], center: ['50%', '48%'], avoidLabelOverlap: false,\n itemStyle: { borderColor: '#ffffff', borderWidth: 3 },\n label: { show: false },\n emphasis: { label: { show: true, fontSize: 14, fontWeight: 'bold', color: '#1e293b' } },\n data: [\n { value: 7702, name: '策略过滤事件', itemStyle: { color: '#f59e0b' } },\n { value: 22963, name: '策略输出事件', itemStyle: { color: '#06b6d4' } },\n { value: 7845, name: '其他事件', itemStyle: { color: '#94a3b8' } },\n ]\n }]\n });\n window.addEventListener('resize', () => pieChart.resize());\n }\n\n // 柱状图\n const barDom = document.getElementById('chartBar');\n if (barDom && !barDom._inited) {\n barDom._inited = true;\n const barChart = echarts.init(barDom);\n barChart.setOption({\n tooltip: { trigger: 'axis', backgroundColor: '#ffffff', borderColor: '#e2e8f0', textStyle: { color: '#1e293b', fontSize: 12 } },\n grid: { left: '3%', right: '10%', top: 15, bottom: 25, containLabel: true },\n xAxis: {\n type: 'value',\n axisLine: { show: false },\n axisTick: { show: false },\n splitLine: { lineStyle: { color: '#f1f5f9' } },\n axisLabel: { color: '#94a3b8', fontSize: 10 }\n },\n yAxis: {\n type: 'category',\n data: ['异常预警策略', '人脸识别策略', '车辆黑名单', '周界防护', '轨迹分析', '行为检测'],\n axisLine: { show: false },\n axisTick: { show: false },\n axisLabel: { color: '#64748b', fontSize: 11 },\n inverse: true,\n },\n series: [{\n type: 'bar',\n barWidth: 16,\n itemStyle: {\n borderRadius: [0, 6, 6, 0],\n color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [\n { offset: 0, color: '#2563eb' },\n { offset: 1, color: '#3b82f6' }\n ])\n },\n label: { show: true, position: 'right', color: '#64748b', fontSize: 10, fontFamily: 'JetBrains Mono' },\n data: [2635, 7136, 9630, 1668, 3024, 3870]\n }]\n });\n window.addEventListener('resize', () => barChart.resize());\n }\n }\n\n // ============ 初始化 ============\n document.addEventListener('DOMContentLoaded', () => {\n renderCompTable();\n renderTraceTable();\n });\n\n document.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') {\n document.getElementById('detailModal').classList.remove('show');\n }\n });\n </script>\n</body>\n</html>"
},
"next": [
"元件表单校验增强",
"策略回溯图片预览",
"算法包上传进度展示",
"策略分组树形筛选器",
"导出统计报表功能"
],
"breakpoint": "none"
}
```
推理
用户想把刚才生成的策略元件工厂与成效统计系统的颜色方案从暗色(金色强调色)换成蓝白色。让我重新设计,采用蓝白色调——亮色/浅色背景,蓝色作为强调色。
我需要:
1. 获取设计系统推荐,蓝白色调
2. 重新生成整个HTML,保持功能不变,但颜色方案改为蓝白色
让我先获取设计系统。设计系统返回的是偏暗色的方案,但用户想要蓝白色。我需要自己设计蓝白色调方案。让我重新设计:
- 背景:白色/浅灰白
- 主色调:蓝色系(如 #2563eb, #3b82f6, #1d4ed8)
- 辅助色:浅蓝、天蓝
- 卡片:白色带浅灰边框
- 文字:深色(#1e293b, #475569)
- 强调色:蓝色渐变
保持所有功能不变,只是将暗色主题改为蓝白亮色主题。字体方面,用户之前使用的是 Outfit + DM Sans,这是不错的搭配,可以保留。Inter也可以考虑。
我需要生成完整的HTML,改动主要是CSS变量和颜色相关的地方。这是一个比较大的改动,需要:
1. 背景从深色改为白色/浅色
2. 卡片从深色背景改为白色
3. 文字从浅色改为深色
4. 金色强调色改为蓝色
5. 导航从深色改为白色
6. 输入框从深色改为浅色
7. 表格从深色改为浅色
8. ECharts图表颜色调整
9. Badge颜色调整
让我重新生成完整HTML。核心改动:
- --bg-deep: #f0f4f8
- --bg-primary: #f8fafc
- --bg-card: #ffffff
- --bg-input: #f1f5f9
- --border-subtle: #e2e8f0
- --accent: #2563eb (蓝色)
- --text-primary: #1e293b
- --text-secondary: #475569
- --text-muted: #94a3b8
其他功能完全保持。我需要确保ECharts图表的颜色也相应调整。
不调用get_assets了,直接用设计改动。
这个任务比较简单直接——保持完全相同的HTML结构和功能,只改颜色变量和相关的颜色值。
让我完整重写。我需要非常仔细地替换所有颜色:
1. CSS变量定义
2. 所有内联style中的颜色值
3. ECharts配置中的颜色
4. Badge颜色
5. 按钮颜色
6. Toast颜色
7. 模态框颜色
8. 导航样式
这实际上是对整个HTML做一个颜色主题的全局替换。考虑到代码量很大,我需要系统地处理。
让我整理关键的颜色映射:
- 暗色背景 → 浅色背景
- #06080d → #f4f7fb
- #0b0e14 → #f8fafc
- #11141d → #ffffff
- #161a25 → #f8fafc
- #0d1018 → #f1f5f9
- #0d1016 → #f1f5f9
- #1a1e2a → #e2e8f0
- #1e2232 → #cbd5e1
- #2a2f42 → #94a3b8
- #2a3050 → #93c5fd
- #e8eaf0 → #1e293b
- #9ca0b0 → #475569
- #5e6378 → #94a3b8
- #d4a537 → #2563eb (金色→蓝色)
- rgba(212,165,55,...) → rgba(37,99,235,...)
- 等等
ECharts颜色也需要调整,使用蓝色系。
这是一个很大的修改,但我需要保持所有功能不变。让我直接输出完整的HTML。
我需要:
1. 获取设计系统推荐,蓝白色调
2. 重新生成整个HTML,保持功能不变,但颜色方案改为蓝白色
让我先获取设计系统。设计系统返回的是偏暗色的方案,但用户想要蓝白色。我需要自己设计蓝白色调方案。让我重新设计:
- 背景:白色/浅灰白
- 主色调:蓝色系(如 #2563eb, #3b82f6, #1d4ed8)
- 辅助色:浅蓝、天蓝
- 卡片:白色带浅灰边框
- 文字:深色(#1e293b, #475569)
- 强调色:蓝色渐变
保持所有功能不变,只是将暗色主题改为蓝白亮色主题。字体方面,用户之前使用的是 Outfit + DM Sans,这是不错的搭配,可以保留。Inter也可以考虑。
我需要生成完整的HTML,改动主要是CSS变量和颜色相关的地方。这是一个比较大的改动,需要:
1. 背景从深色改为白色/浅色
2. 卡片从深色背景改为白色
3. 文字从浅色改为深色
4. 金色强调色改为蓝色
5. 导航从深色改为白色
6. 输入框从深色改为浅色
7. 表格从深色改为浅色
8. ECharts图表颜色调整
9. Badge颜色调整
让我重新生成完整HTML。核心改动:
- --bg-deep: #f0f4f8
- --bg-primary: #f8fafc
- --bg-card: #ffffff
- --bg-input: #f1f5f9
- --border-subtle: #e2e8f0
- --accent: #2563eb (蓝色)
- --text-primary: #1e293b
- --text-secondary: #475569
- --text-muted: #94a3b8
其他功能完全保持。我需要确保ECharts图表的颜色也相应调整。
不调用get_assets了,直接用设计改动。
这个任务比较简单直接——保持完全相同的HTML结构和功能,只改颜色变量和相关的颜色值。
让我完整重写。我需要非常仔细地替换所有颜色:
1. CSS变量定义
2. 所有内联style中的颜色值
3. ECharts配置中的颜色
4. Badge颜色
5. 按钮颜色
6. Toast颜色
7. 模态框颜色
8. 导航样式
这实际上是对整个HTML做一个颜色主题的全局替换。考虑到代码量很大,我需要系统地处理。
让我整理关键的颜色映射:
- 暗色背景 → 浅色背景
- #06080d → #f4f7fb
- #0b0e14 → #f8fafc
- #11141d → #ffffff
- #161a25 → #f8fafc
- #0d1018 → #f1f5f9
- #0d1016 → #f1f5f9
- #1a1e2a → #e2e8f0
- #1e2232 → #cbd5e1
- #2a2f42 → #94a3b8
- #2a3050 → #93c5fd
- #e8eaf0 → #1e293b
- #9ca0b0 → #475569
- #5e6378 → #94a3b8
- #d4a537 → #2563eb (金色→蓝色)
- rgba(212,165,55,...) → rgba(37,99,235,...)
- 等等
ECharts颜色也需要调整,使用蓝色系。
这是一个很大的修改,但我需要保持所有功能不变。让我直接输出完整的HTML。
角色
assistant
content
total_tokens
43927
summary
将策略元件工厂与成效统计系统整体配色从暗色金主题转换为蓝白亮色主题。核心改动:CSS变量全面替换(背景→#f4f7fb/#ffffff、文字→#1e293b/#475569、强调色→#2563eb蓝色)、卡片/导航/输入框/表格/模态框等组件亮色化、ECharts图表配色蓝色系适配、Badge和按钮蓝色重构。功能与交互完全保留。
uiParsing
关闭