# -*- coding: utf-8 -*- new_transfers = ''' async function renderTransfersPage() { els.pageTitle.textContent = '客户交接看板'; const page = document.createElement('div'); page.className = 'page'; page.innerHTML = `

交接漏斗

交接结果分布

近7日交接执行趋势

交接包列表

预览包与执行记录
`; els.content.appendChild(page); await loadTransfersDashboard(page); dashboardRefreshTimer = setInterval(() => { if (state.page === 'transfers') loadTransfersDashboard(page, true); }, 30000); page.querySelector('#btn-tf-refresh').addEventListener('click', () => loadTransfersDashboard(page)); page.querySelector('#btn-tf-rebuild').addEventListener('click', async () => { try { await api('POST', '/api/dashboard/rebuild'); toast('索引已重建'); loadTransfersDashboard(page); } catch (err) { toast(err.message || '重建失败', 'error'); } }); page.querySelector('#btn-new-transfer').addEventListener('click', () => openTransferModal(page)); page.querySelector('#tf-filter-status').addEventListener('change', () => loadTransfersTable(page)); page.querySelector('#tf-filter-user').addEventListener('input', () => loadTransfersTable(page)); } async function loadTransfersDashboard(page, silent = false) { if (!silent) page.querySelector('#tf-transfer-table-wrap').innerHTML = '
'; try { const [summaryRes, transfersRes] = await Promise.all([ api('GET', '/api/dashboard/summary'), api('GET', '/api/dashboard/transfers') ]); const summary = summaryRes.data || {}; const transfers = transfersRes.data || {}; renderTransfersKpi(page, summary); renderTransfersCharts(page, summary, transfers); state.transfers = { ...state.transfers, ...transfers }; loadTransfersTable(page); } catch (err) { if (!silent) toast(err.message || '加载看板失败', 'error'); } } function renderTransfersKpi(page, summary) { const wrap = page.querySelector('#tf-kpi-row'); const t = summary.transfers || {}; const kpi = [ { label: '交接预览包', value: formatNumber(t.previews) }, { label: '已执行交接', value: formatNumber(t.executions) }, { label: '交接成功率', value: (t.successRate || 0) + '%', sub: '成功记录 / 总记录' }, { label: '近7日交接', value: formatNumber(t.recent7d), sub: '最近7天执行数' } ]; wrap.innerHTML = renderKpiRow(kpi); } function renderTransfersCharts(page, summary, transfers) { const t = summary.transfers || {}; initEChart('tf-funnel-chart', { tooltip: { trigger: 'item', formatter: '{b}: {c}' }, series: [{ type: 'funnel', left: '10%', width: '80%', label: { show: true, position: 'inside' }, data: [ { name: '预览包', value: t.previews || 0, itemStyle: { color: '#fa8c16' } }, { name: '已执行', value: t.executions || 0, itemStyle: { color: '#1890ff' } }, { name: '成功', value: Math.round(((t.successRate || 0) / 100) * (t.executions || 0)), itemStyle: { color: '#52c41a' } } ] }] }); const executions = transfers.executions || []; const success = executions.reduce((sum, e) => sum + (e.success || 0), 0); const failed = executions.reduce((sum, e) => sum + (e.failed || 0), 0); initEChart('tf-result-chart', { tooltip: { trigger: 'item' }, legend: { bottom: 0 }, series: [{ type: 'pie', radius: ['40%', '65%'], center: ['50%', '45%'], data: [ { name: '成功', value: success, itemStyle: { color: '#52c41a' } }, { name: '失败', value: failed, itemStyle: { color: '#ff4d4f' } } ] }] }); const days = last7Days(); const counts = countByDay(executions, 'executedAt'); initEChart('tf-trend-chart', { tooltip: { trigger: 'axis' }, xAxis: { type: 'category', data: days.map(d => formatDate(d)) }, yAxis: { type: 'value' }, series: [{ name: '交接执行', type: 'bar', data: counts, itemStyle: { color: '#fa8c16' } }] }); } function loadTransfersTable(page) { const wrap = page.querySelector('#tf-transfer-table-wrap'); const transfers = state.transfers || {}; const statusFilter = page.querySelector('#tf-filter-status').value; const userFilter = page.querySelector('#tf-filter-user').value.trim().toLowerCase(); let items = [ ...(transfers.previews || []).map(p => ({ ...p, type: 'preview' })), ...(transfers.executions || []).map(e => ({ ...e, type: 'execution' })) ]; let filtered = items.filter(item => { if (statusFilter && item.status !== statusFilter) return false; if (userFilter) { const text = `${item.fromUserId || ''} ${item.toUserId || ''}`.toLowerCase(); if (!text.includes(userFilter)) return false; } return true; }); filtered.sort((a, b) => (b.createdAt || b.executedAt || '').localeCompare(a.createdAt || a.executedAt || '')); const pageSize = 20; const currentPage = state.transfers.tablePage || 1; const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize)); if (currentPage > totalPages) state.transfers.tablePage = totalPages; const start = (state.transfers.tablePage - 1) * pageSize; const pageItems = filtered.slice(start, start + pageSize); wrap.innerHTML = `
${pageItems.length ? pageItems.map(item => ` `).join('') : ''}
类型 原顾问 新顾问 记录数 状态 创建/执行时间 操作
${item.type === 'preview' ? '预览' : '执行'} ${escapeHtml(item.fromUserId || '-')} ${escapeHtml(item.toUserId || '-')} ${formatNumber(item.itemCount || item.total || 0)} ${item.status === 'EXECUTED' ? '已执行' : '草稿'} ${formatDateTime(item.createdAt || item.executedAt)} ${item.type === 'preview' ? `` : ``}
暂无交接记录
`; const pagination = page.querySelector('#tf-pagination'); pagination.innerHTML = renderPagination(state.transfers.tablePage, filtered.length, pageSize); pagination.querySelectorAll('button').forEach(btn => { btn.addEventListener('click', () => { state.transfers.tablePage = Number(btn.dataset.page); loadTransfersTable(page); }); }); } function openTransferModal(page) { const body = `
`; openModal('新建交接预览', body, ` `); document.getElementById('btn-confirm-new-transfer').addEventListener('click', async () => { const fromUserId = document.getElementById('new-tf-from').value.trim(); const toUserId = document.getElementById('new-tf-to').value.trim(); const externalUserIds = document.getElementById('new-tf-external').value.split(/\n|\r/).map(s => s.trim()).filter(Boolean); const roomIds = document.getElementById('new-tf-rooms').value.split(/[,,]/).map(s => s.trim()).filter(Boolean); if (!fromUserId || !toUserId) { toast('请输入原顾问和新顾问 userId', 'warning'); return; } if (!externalUserIds.length && !roomIds.length) { toast('请输入 externalUserIds 或 roomIds', 'warning'); return; } try { await api('POST', '/api/transfers/preview', { fromUserId, toUserId, externalUserIds, roomIds }); toast('预览已生成'); closeModal(); loadTransfersDashboard(page); } catch (err) { toast(err.message || '生成失败', 'error'); } }); } window.dashboardExecuteTransfer = (filePath) => { const body = `
`; openModal('执行交接', body, ` `); document.getElementById('btn-confirm-exec-transfer').addEventListener('click', () => { const previewFile = document.getElementById('exec-tf-file').value.trim() || undefined; const welcomeText = document.getElementById('exec-tf-welcome').value.trim(); const removeOldBroker = document.getElementById('exec-tf-remove').checked; closeModal(); startJob('execute-transfer', '/api/transfers/execute', { guid: getGuid(), previewFile, welcomeText, removeOldBroker }, () => loadTransfersDashboard(document.querySelector('#tf-dashboard')?.closest('.page'))); }); }; window.dashboardTransferDetail = async (filePath) => { if (!filePath) return; try { openModal('交接详情', `
${escapeHtml(JSON.stringify({ filePath }, null, 2))}
${renderFileLink(filePath, '下载文件')}
`, ``); } catch (err) { toast(err.message || '查询失败', 'error'); } }; ''' with open('d:/caidawork/openclaw-voc-skill/claude-code/claude-code-qiwe-assistant/mcp/src/dashboard/app.js', 'r', encoding='utf-8') as f: lines = f.readlines() start = 2156 end = 2347 new_lines = lines[:start] + [new_transfers + '\n'] + lines[end:] with open('d:/caidawork/openclaw-voc-skill/claude-code/claude-code-qiwe-assistant/mcp/src/dashboard/app.js', 'w', encoding='utf-8') as f: f.writelines(new_lines) print('renderTransfersPage replaced successfully')