(function attachSidepanelAccountRecordsManager(globalScope) { function createAccountRecordsManager(context = {}) { const { state, dom, helpers, runtime, constants = {}, } = context; const displayTimeZone = constants.displayTimeZone || 'Asia/Shanghai'; const pageSize = Math.max(1, Math.floor(Number(constants.pageSize) || 10)); const FILTER_CONFIG = { all: { label: '总', className: '', matches: () => true, metaLabel: '全部', }, success: { label: '成', className: 'is-success', matches: (record) => record.finalStatus === 'success', metaLabel: '成功', }, failed: { label: '失', className: 'is-failed', matches: (record) => record.finalStatus === 'failed', metaLabel: '失败', }, stopped: { label: '停', className: 'is-stopped', matches: (record) => record.finalStatus === 'stopped', metaLabel: '停止', }, retry: { label: '重试', className: 'is-retry', matches: (record) => normalizeRetryCount(record.retryCount) > 0, metaLabel: '重试', }, }; let currentPage = 1; let activeFilter = 'all'; let selectionMode = false; let eventsBound = false; const selectedRecordIds = new Set(); function escapeHtml(value) { if (typeof helpers.escapeHtml === 'function') { return helpers.escapeHtml(String(value || '')); } return String(value || ''); } function normalizeTimestamp(value) { const timestamp = Date.parse(String(value || '')); return Number.isFinite(timestamp) ? timestamp : 0; } function normalizeRetryCount(value) { const count = Math.floor(Number(value) || 0); return count > 0 ? count : 0; } function buildRecordId(record = {}) { const rawRecordId = String(record.recordId || '').trim(); if (rawRecordId) { return rawRecordId.toLowerCase(); } const rawIdentifierType = String(record.accountIdentifierType || '').trim().toLowerCase(); const hasPhoneOnlyIdentifier = !record.email && ( record.phoneNumber || record.phone || record.number || (record.accountIdentifier && !/@/.test(String(record.accountIdentifier || ''))) ); const identifierType = rawIdentifierType === 'phone' || (!rawIdentifierType && hasPhoneOnlyIdentifier) ? 'phone' : 'email'; const identifier = String( record.accountIdentifier || (identifierType === 'phone' ? (record.phoneNumber || record.phone || record.number || '') : (record.email || '')) || '' ).trim(); if (!identifier) { return ''; } return identifierType === 'phone' ? `phone:${identifier.toLowerCase()}` : identifier.toLowerCase(); } function getRecordIdentifierType(record = {}) { const rawType = String(record.accountIdentifierType || '').trim().toLowerCase(); if (rawType === 'phone') { return 'phone'; } if (rawType === 'email') { return 'email'; } if (!record.email && (record.phoneNumber || record.phone || record.number)) { return 'phone'; } if (!record.email && record.accountIdentifier && !/@/.test(String(record.accountIdentifier || ''))) { return 'phone'; } return 'email'; } function getRecordEmail(record = {}) { const identifierType = getRecordIdentifierType(record); return String( record.email || (identifierType === 'email' ? record.accountIdentifier : '') || '' ).trim(); } function getRecordPhoneNumber(record = {}) { const identifierType = getRecordIdentifierType(record); return String( record.phoneNumber || record.phone || record.number || (identifierType === 'phone' ? record.accountIdentifier : '') || '' ).trim(); } function getRecordPrimaryIdentifier(record = {}) { const identifierType = getRecordIdentifierType(record); const email = getRecordEmail(record); const phoneNumber = getRecordPhoneNumber(record); return identifierType === 'phone' ? (phoneNumber || String(record.accountIdentifier || '').trim() || email) : (email || String(record.accountIdentifier || '').trim() || phoneNumber); } function getRecordSecondaryIdentifier(record = {}) { const identifierType = getRecordIdentifierType(record); const email = getRecordEmail(record); const phoneNumber = getRecordPhoneNumber(record); if (identifierType === 'phone' && email) { return `邮箱 ${email}`; } if (identifierType !== 'phone' && phoneNumber) { return `绑定手机号 ${phoneNumber}`; } return ''; } function getRecordTitle(record = {}) { const primaryIdentifier = getRecordPrimaryIdentifier(record) || '(空账号)'; const secondaryIdentifier = getRecordSecondaryIdentifier(record); return secondaryIdentifier ? `${primaryIdentifier} / ${secondaryIdentifier}` : primaryIdentifier; } function getAccountRunRecords(currentState = state.getLatestState()) { return (Array.isArray(currentState?.accountRunHistory) ? currentState.accountRunHistory : []) .filter((item) => item && typeof item === 'object') .slice() .sort((left, right) => normalizeTimestamp(right.finishedAt) - normalizeTimestamp(left.finishedAt)); } function summarizeAccountRunHistory(records = []) { return records.reduce((summary, record) => { const retryCount = normalizeRetryCount(record.retryCount); summary.total += 1; if (record.finalStatus === 'success') { summary.success += 1; } else if (record.finalStatus === 'failed') { summary.failed += 1; } else if (record.finalStatus === 'stopped') { summary.stopped += 1; } if (retryCount > 0) { summary.retryRecordCount += 1; } summary.retryTotal += retryCount; return summary; }, { total: 0, success: 0, failed: 0, stopped: 0, retryRecordCount: 0, retryTotal: 0, }); } function formatAccountRecordTime(value) { const date = new Date(value); if (Number.isNaN(date.getTime())) { return '--:--'; } const now = new Date(); const sameYear = date.getFullYear() === now.getFullYear(); const sameDay = date.toDateString() === now.toDateString(); if (sameDay) { return date.toLocaleTimeString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit', timeZone: displayTimeZone, }); } return date.toLocaleString('zh-CN', { hour12: false, month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', ...(sameYear ? {} : { year: '2-digit' }), timeZone: displayTimeZone, }).replace(/\//g, '-'); } function getStatusMeta(record = {}) { if (record.finalStatus === 'success') { return { kind: 'success', label: '成功' }; } if (record.finalStatus === 'stopped') { return { kind: 'stopped', label: '停止' }; } return { kind: 'failed', label: '失败' }; } function getRecordSummaryText(record = {}) { if (record.finalStatus === 'success') { return '流程完成'; } return String(record.failureLabel || '').trim() || '流程失败'; } function getFilterConfig(filterKey = activeFilter) { return FILTER_CONFIG[filterKey] || FILTER_CONFIG.all; } function getFilteredRecords(records = []) { const filterConfig = getFilterConfig(activeFilter); return records.filter((record) => filterConfig.matches(record)); } function pruneSelectedRecordIds(records = []) { const availableIds = new Set(records.map((record) => buildRecordId(record)).filter(Boolean)); for (const recordId of Array.from(selectedRecordIds)) { if (!availableIds.has(recordId)) { selectedRecordIds.delete(recordId); } } } function setNodeHidden(node, hidden) { if (node) { node.hidden = Boolean(hidden); } } function setNodeDisabled(node, disabled) { if (node) { node.disabled = Boolean(disabled); } } function toggleNodeClass(node, className, enabled) { if (!node || !className) { return; } if (node.classList && typeof node.classList.toggle === 'function') { node.classList.toggle(className, Boolean(enabled)); } } function setNodeText(node, value) { if (node) { node.textContent = String(value || ''); } } function setNodeAttr(node, name, value) { if (!node || !name) { return; } if (typeof node.setAttribute === 'function') { node.setAttribute(name, String(value)); return; } node[name] = value; } function getDatasetValue(node, attrName) { if (!node || !attrName) { return ''; } if (typeof node.getAttribute === 'function') { return String(node.getAttribute(attrName) || ''); } const dataKey = attrName .replace(/^data-/, '') .replace(/-([a-z])/g, (_, char) => char.toUpperCase()); return String(node.dataset?.[dataKey] || ''); } function findClosest(target, selector) { if (!target || typeof target.closest !== 'function') { return null; } try { return target.closest(selector); } catch { return null; } } function createStatChip(filterKey, value) { const filterConfig = getFilterConfig(filterKey); const classNames = [ 'account-records-stat', filterConfig.className, activeFilter === filterKey ? 'is-active' : '', ].filter(Boolean).join(' '); return ` `; } function updateHeader(allRecords, filteredRecords) { if (!dom.accountRecordsMeta) { return; } if (!allRecords.length) { dom.accountRecordsMeta.textContent = '暂无账号记录'; return; } const latestTime = formatAccountRecordTime(allRecords[0]?.finishedAt); let metaText = `共 ${allRecords.length} 条,最近更新于 ${latestTime}`; if (activeFilter !== 'all') { metaText = `共 ${allRecords.length} 条,当前筛选 ${getFilterConfig(activeFilter).metaLabel} ${filteredRecords.length} 条,最近更新于 ${latestTime}`; } if (selectionMode) { metaText += `,已选 ${selectedRecordIds.size} 条`; } dom.accountRecordsMeta.textContent = metaText; } function updateStats(allRecords) { if (!dom.accountRecordsStats) { return; } const summary = summarizeAccountRunHistory(allRecords); dom.accountRecordsStats.innerHTML = [ createStatChip('all', summary.total), createStatChip('success', summary.success), createStatChip('failed', summary.failed), createStatChip('stopped', summary.stopped), createStatChip('retry', summary.retryTotal), ].join(''); } function updateToolbarState(allRecords) { const totalRecords = allRecords.length; setNodeDisabled(dom.btnClearAccountRecords, totalRecords === 0); setNodeDisabled(dom.btnToggleAccountRecordsSelection, totalRecords === 0); setNodeHidden(dom.btnClearAccountRecords, selectionMode); toggleNodeClass(dom.btnToggleAccountRecordsSelection, 'is-active', selectionMode); setNodeAttr(dom.btnToggleAccountRecordsSelection, 'aria-pressed', selectionMode ? 'true' : 'false'); setNodeText(dom.btnToggleAccountRecordsSelection, selectionMode ? '取消多选' : '多选'); const selectedCount = selectedRecordIds.size; setNodeHidden(dom.btnDeleteSelectedAccountRecords, !selectionMode); setNodeDisabled(dom.btnDeleteSelectedAccountRecords, selectedCount === 0); setNodeText( dom.btnDeleteSelectedAccountRecords, selectedCount > 0 ? `删除选中(${selectedCount})` : '删除选中' ); } function updatePagination(totalRecords) { const totalPages = totalRecords > 0 ? Math.ceil(totalRecords / pageSize) : 0; if (totalPages === 0) { currentPage = 1; } else if (currentPage > totalPages) { currentPage = totalPages; } else if (currentPage < 1) { currentPage = 1; } setNodeText(dom.accountRecordsPageLabel, totalPages > 0 ? `${currentPage} / ${totalPages}` : '0 / 0'); setNodeDisabled(dom.btnAccountRecordsPrev, totalPages <= 1 || currentPage <= 1); setNodeDisabled(dom.btnAccountRecordsNext, totalPages <= 1 || currentPage >= totalPages); return totalPages; } function renderEmptyState(allRecords) { if (!dom.accountRecordsList) { return; } const message = allRecords.length ? `当前筛选“${getFilterConfig(activeFilter).metaLabel}”下暂无记录` : '暂无账号记录'; dom.accountRecordsList.innerHTML = `
${escapeHtml(message)}
`; } function renderRecordList(allRecords, filteredRecords) { if (!dom.accountRecordsList) { return; } const totalPages = updatePagination(filteredRecords.length); if (!filteredRecords.length) { renderEmptyState(allRecords); return; } const startIndex = (currentPage - 1) * pageSize; const visibleRecords = filteredRecords.slice(startIndex, startIndex + pageSize); dom.accountRecordsList.innerHTML = visibleRecords.map((record) => { const recordId = buildRecordId(record); const primaryIdentifier = getRecordPrimaryIdentifier(record) || '(空账号)'; const secondaryIdentifier = getRecordSecondaryIdentifier(record); const recordTitle = getRecordTitle(record); const statusMeta = getStatusMeta(record); const summaryText = getRecordSummaryText(record); const retryCount = normalizeRetryCount(record.retryCount); const isSelected = selectedRecordIds.has(recordId); const itemClassNames = [ 'account-record-item', `is-${statusMeta.kind}`, selectionMode ? 'is-selectable' : '', isSelected ? 'is-selected' : '', ].filter(Boolean).join(' '); const selectionMarkup = selectionMode ? ` ` : ''; return `
`; }).join(''); if (totalPages <= 1) { setNodeText(dom.accountRecordsPageLabel, '1 / 1'); } } function render(currentState = state.getLatestState()) { const allRecords = getAccountRunRecords(currentState); pruneSelectedRecordIds(allRecords); if (!allRecords.length) { selectionMode = false; } const filteredRecords = getFilteredRecords(allRecords); updateHeader(allRecords, filteredRecords); updateStats(allRecords); updateToolbarState(allRecords); renderRecordList(allRecords, filteredRecords); } function openPanel() { setNodeHidden(dom.accountRecordsOverlay, false); render(); } function closePanel() { setNodeHidden(dom.accountRecordsOverlay, true); } function resetSelection() { selectedRecordIds.clear(); } function setSelectionMode(nextValue) { const nextSelectionMode = Boolean(nextValue); if (!nextSelectionMode) { resetSelection(); } selectionMode = nextSelectionMode; currentPage = 1; render(); } function toggleSelectionMode() { setSelectionMode(!selectionMode); } function toggleRecordSelection(recordId, forceSelected = null) { const normalizedRecordId = String(recordId || '').trim().toLowerCase(); if (!selectionMode || !normalizedRecordId) { return; } const shouldSelect = forceSelected === null ? !selectedRecordIds.has(normalizedRecordId) : Boolean(forceSelected); if (shouldSelect) { selectedRecordIds.add(normalizedRecordId); } else { selectedRecordIds.delete(normalizedRecordId); } } async function clearRecords() { const records = getAccountRunRecords(); if (!records.length) { helpers.showToast?.('没有可清理的账号记录。', 'warn', 1800); return; } const confirmed = await helpers.openConfirmModal({ title: '清理账号记录', message: '确认清理当前全部账号记录吗?该操作会同时清空面板记录与本地同步快照。', confirmLabel: '确认清理', confirmVariant: 'btn-danger', }); if (!confirmed) { return; } const response = await runtime.sendMessage({ type: 'CLEAR_ACCOUNT_RUN_HISTORY', source: 'sidepanel', }); if (response?.error) { throw new Error(response.error); } activeFilter = 'all'; currentPage = 1; selectionMode = false; resetSelection(); state.syncLatestState({ accountRunHistory: [] }); helpers.showToast?.(`已清理 ${Math.max(0, Number(response?.clearedCount) || 0)} 条账号记录。`, 'success', 2200); } async function deleteSelectedRecords() { const recordIds = Array.from(selectedRecordIds).filter(Boolean); if (!recordIds.length) { helpers.showToast?.('请先勾选要删除的账号记录。', 'warn', 1800); return; } const confirmed = await helpers.openConfirmModal({ title: '删除选中记录', message: `确认删除选中的 ${recordIds.length} 条账号记录吗?该操作会同步更新本地 helper 快照。`, confirmLabel: '确认删除', confirmVariant: 'btn-danger', }); if (!confirmed) { return; } const response = await runtime.sendMessage({ type: 'DELETE_ACCOUNT_RUN_HISTORY_RECORDS', source: 'sidepanel', payload: { recordIds, }, }); if (response?.error) { throw new Error(response.error); } const existingRecords = getAccountRunRecords(); const selectedIds = new Set(recordIds); const nextRecords = existingRecords.filter((record) => !selectedIds.has(buildRecordId(record))); resetSelection(); state.syncLatestState({ accountRunHistory: nextRecords }); helpers.showToast?.(`已删除 ${Math.max(0, Number(response?.deletedCount) || 0)} 条账号记录。`, 'success', 2200); } function handleStatsClick(event) { const filterNode = findClosest(event?.target, '[data-account-record-filter]'); if (!filterNode) { return; } const nextFilter = getDatasetValue(filterNode, 'data-account-record-filter'); if (!FILTER_CONFIG[nextFilter]) { return; } activeFilter = activeFilter === nextFilter && nextFilter !== 'all' ? 'all' : nextFilter; currentPage = 1; render(); } function handleRecordListClick(event) { if (!selectionMode) { return; } const toggleNode = findClosest(event?.target, '[data-account-record-toggle]'); if (toggleNode) { const recordId = getDatasetValue(toggleNode, 'data-account-record-toggle'); const explicitChecked = typeof event?.target?.checked === 'boolean' ? event.target.checked : null; toggleRecordSelection(recordId, explicitChecked); render(); return; } const recordNode = findClosest(event?.target, '[data-account-record-id]'); if (!recordNode) { return; } toggleRecordSelection(getDatasetValue(recordNode, 'data-account-record-id')); render(); } function bindEvents() { if (eventsBound) { return; } eventsBound = true; dom.btnOpenAccountRecords?.addEventListener('click', () => { openPanel(); }); dom.btnCloseAccountRecords?.addEventListener('click', () => { closePanel(); }); dom.accountRecordsOverlay?.addEventListener('click', (event) => { if (event.target === dom.accountRecordsOverlay) { closePanel(); } }); dom.accountRecordsStats?.addEventListener('click', (event) => { handleStatsClick(event); }); dom.accountRecordsList?.addEventListener('click', (event) => { handleRecordListClick(event); }); dom.btnAccountRecordsPrev?.addEventListener('click', () => { if (currentPage <= 1) { return; } currentPage -= 1; render(); }); dom.btnAccountRecordsNext?.addEventListener('click', () => { currentPage += 1; render(); }); dom.btnToggleAccountRecordsSelection?.addEventListener('click', () => { toggleSelectionMode(); }); dom.btnDeleteSelectedAccountRecords?.addEventListener('click', async () => { try { await deleteSelectedRecords(); } catch (error) { helpers.showToast?.(`删除账号记录失败:${error.message}`, 'error'); } }); dom.btnClearAccountRecords?.addEventListener('click', async () => { try { await clearRecords(); } catch (error) { helpers.showToast?.(`清理账号记录失败:${error.message}`, 'error'); } }); } function reset() { currentPage = 1; activeFilter = 'all'; selectionMode = false; resetSelection(); closePanel(); render(); } return { bindEvents, clearRecords, closePanel, deleteSelectedRecords, openPanel, render, reset, setSelectionMode, summarizeAccountRunHistory, toggleSelectionMode, }; } globalScope.SidepanelAccountRecordsManager = { createAccountRecordsManager, }; })(typeof window !== 'undefined' ? window : globalThis);