重构更新账号日志功能,添加开发者模式
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
(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));
|
||||
|
||||
let currentPage = 1;
|
||||
|
||||
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 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) => {
|
||||
summary.total += 1;
|
||||
if (record.finalStatus === 'success') {
|
||||
summary.success += 1;
|
||||
} else if (record.finalStatus === 'failed') {
|
||||
summary.failed += 1;
|
||||
}
|
||||
summary.retryTotal += Math.max(0, Math.floor(Number(record.retryCount) || 0));
|
||||
return summary;
|
||||
}, {
|
||||
total: 0,
|
||||
success: 0,
|
||||
failed: 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 = {}) {
|
||||
return record.finalStatus === 'success'
|
||||
? { kind: 'success', label: '成功' }
|
||||
: { kind: 'failed', label: '失败' };
|
||||
}
|
||||
|
||||
function getRecordSummaryText(record = {}) {
|
||||
if (record.finalStatus === 'success') {
|
||||
return '流程完成';
|
||||
}
|
||||
|
||||
return String(record.failureLabel || '').trim() || '流程失败';
|
||||
}
|
||||
|
||||
function createStatChip(label, value, className = '') {
|
||||
return `<span class="account-records-stat${className ? ` ${className}` : ''}"><strong>${escapeHtml(String(value))}</strong>${escapeHtml(label)}</span>`;
|
||||
}
|
||||
|
||||
function updateHeader(records) {
|
||||
if (!dom.accountRecordsMeta || !dom.accountRecordsStats) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!records.length) {
|
||||
dom.accountRecordsMeta.textContent = '暂无邮箱记录';
|
||||
} else {
|
||||
dom.accountRecordsMeta.textContent = `共 ${records.length} 条,最近更新于 ${formatAccountRecordTime(records[0]?.finishedAt)}`;
|
||||
}
|
||||
|
||||
const summary = summarizeAccountRunHistory(records);
|
||||
dom.accountRecordsStats.innerHTML = [
|
||||
createStatChip('总', summary.total),
|
||||
createStatChip('成', summary.success, 'is-success'),
|
||||
createStatChip('失', summary.failed, 'is-failed'),
|
||||
createStatChip('重试', summary.retryTotal, 'is-retry'),
|
||||
].join('');
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (dom.accountRecordsPageLabel) {
|
||||
dom.accountRecordsPageLabel.textContent = totalPages > 0 ? `${currentPage} / ${totalPages}` : '0 / 0';
|
||||
}
|
||||
if (dom.btnAccountRecordsPrev) {
|
||||
dom.btnAccountRecordsPrev.disabled = totalPages <= 1 || currentPage <= 1;
|
||||
}
|
||||
if (dom.btnAccountRecordsNext) {
|
||||
dom.btnAccountRecordsNext.disabled = totalPages <= 1 || currentPage >= totalPages;
|
||||
}
|
||||
if (dom.btnClearAccountRecords) {
|
||||
dom.btnClearAccountRecords.disabled = totalRecords === 0;
|
||||
}
|
||||
|
||||
return totalPages;
|
||||
}
|
||||
|
||||
function renderRecordList(records = []) {
|
||||
if (!dom.accountRecordsList) {
|
||||
return;
|
||||
}
|
||||
|
||||
const totalPages = updatePagination(records.length);
|
||||
if (!records.length) {
|
||||
dom.accountRecordsList.innerHTML = '<div class="account-records-empty">暂无邮箱记录。</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const startIndex = (currentPage - 1) * pageSize;
|
||||
const visibleRecords = records.slice(startIndex, startIndex + pageSize);
|
||||
|
||||
dom.accountRecordsList.innerHTML = visibleRecords.map((record) => {
|
||||
const statusMeta = getStatusMeta(record);
|
||||
const summaryText = getRecordSummaryText(record);
|
||||
const retryCount = Math.max(0, Math.floor(Number(record.retryCount) || 0));
|
||||
return `
|
||||
<div class="account-record-item is-${escapeHtml(statusMeta.kind)}" title="${escapeHtml(String(record.email || ''))}">
|
||||
<div class="account-record-item-top">
|
||||
<div class="account-record-item-email mono">${escapeHtml(String(record.email || '').trim() || '(空邮箱)')}</div>
|
||||
<div class="account-record-item-side">
|
||||
<span class="account-record-item-status">${escapeHtml(statusMeta.label)}</span>
|
||||
<span class="account-record-item-time mono">${escapeHtml(formatAccountRecordTime(record.finishedAt))}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="account-record-item-bottom">
|
||||
<div class="account-record-item-summary">${escapeHtml(summaryText)}</div>
|
||||
<span class="account-record-item-retry mono">重试 ${escapeHtml(String(retryCount))}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
if (totalPages <= 1 && dom.accountRecordsPageLabel) {
|
||||
dom.accountRecordsPageLabel.textContent = '1 / 1';
|
||||
}
|
||||
}
|
||||
|
||||
function render(currentState = state.getLatestState()) {
|
||||
const records = getAccountRunRecords(currentState);
|
||||
updateHeader(records);
|
||||
renderRecordList(records);
|
||||
}
|
||||
|
||||
function openPanel() {
|
||||
if (dom.accountRecordsOverlay) {
|
||||
dom.accountRecordsOverlay.hidden = false;
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
function closePanel() {
|
||||
if (dom.accountRecordsOverlay) {
|
||||
dom.accountRecordsOverlay.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
currentPage = 1;
|
||||
state.syncLatestState({ accountRunHistory: [] });
|
||||
helpers.showToast?.(`已清理 ${Math.max(0, Number(response?.clearedCount) || 0)} 条邮箱记录`, 'success', 2200);
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
dom.btnOpenAccountRecords?.addEventListener('click', () => {
|
||||
openPanel();
|
||||
});
|
||||
dom.btnCloseAccountRecords?.addEventListener('click', () => {
|
||||
closePanel();
|
||||
});
|
||||
dom.accountRecordsOverlay?.addEventListener('click', (event) => {
|
||||
if (event.target === dom.accountRecordsOverlay) {
|
||||
closePanel();
|
||||
}
|
||||
});
|
||||
dom.btnAccountRecordsPrev?.addEventListener('click', () => {
|
||||
if (currentPage <= 1) {
|
||||
return;
|
||||
}
|
||||
currentPage -= 1;
|
||||
render();
|
||||
});
|
||||
dom.btnAccountRecordsNext?.addEventListener('click', () => {
|
||||
currentPage += 1;
|
||||
render();
|
||||
});
|
||||
dom.btnClearAccountRecords?.addEventListener('click', () => {
|
||||
clearRecords().catch((error) => {
|
||||
helpers.showToast?.(`清理邮箱记录失败:${error.message}`, 'error');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function reset() {
|
||||
currentPage = 1;
|
||||
closePanel();
|
||||
render();
|
||||
}
|
||||
|
||||
return {
|
||||
bindEvents,
|
||||
closePanel,
|
||||
openPanel,
|
||||
render,
|
||||
reset,
|
||||
summarizeAccountRunHistory,
|
||||
};
|
||||
}
|
||||
|
||||
globalScope.SidepanelAccountRecordsManager = {
|
||||
createAccountRecordsManager,
|
||||
};
|
||||
})(typeof window !== 'undefined' ? window : globalThis);
|
||||
+133
-61
@@ -1668,59 +1668,88 @@ header {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.account-run-history-strip {
|
||||
margin-bottom: 8px;
|
||||
padding: 10px 12px;
|
||||
.log-header-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
background:
|
||||
linear-gradient(180deg,
|
||||
color-mix(in srgb, var(--bg-base) 72%, var(--blue-soft)),
|
||||
color-mix(in srgb, var(--bg-surface) 92%, var(--bg-base))
|
||||
);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-sm);
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.account-run-history-head {
|
||||
.account-records-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1210;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 12px;
|
||||
background: rgba(15, 17, 23, 0.32);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.account-records-overlay[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.account-records-panel {
|
||||
width: min(100%, 420px);
|
||||
max-height: calc(100vh - 24px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 14px;
|
||||
background:
|
||||
linear-gradient(180deg,
|
||||
color-mix(in srgb, var(--bg-base) 82%, var(--blue-soft)),
|
||||
color-mix(in srgb, var(--bg-surface) 96%, var(--bg-base))
|
||||
);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
box-shadow: var(--shadow-md), 0 18px 36px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
.account-records-panel-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.account-run-history-copy {
|
||||
.account-records-panel-copy {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.account-run-history-title {
|
||||
font-size: 12px;
|
||||
.account-records-panel-title {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.account-run-history-meta {
|
||||
.account-records-panel-meta {
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.account-run-history-stats {
|
||||
.account-records-panel-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.account-run-history-stat {
|
||||
.account-records-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.account-records-stat {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
@@ -1733,55 +1762,80 @@ header {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.account-run-history-stat strong {
|
||||
.account-records-stat strong {
|
||||
font-family: 'JetBrains Mono', 'Consolas', monospace;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.account-run-history-stat.is-success {
|
||||
.account-records-stat.is-success {
|
||||
color: var(--green);
|
||||
background: var(--green-soft);
|
||||
border-color: color-mix(in srgb, var(--green) 16%, var(--border));
|
||||
}
|
||||
|
||||
.account-run-history-stat.is-failed {
|
||||
.account-records-stat.is-failed {
|
||||
color: var(--red);
|
||||
background: var(--red-soft);
|
||||
border-color: color-mix(in srgb, var(--red) 16%, var(--border));
|
||||
}
|
||||
|
||||
.account-run-history-stat.is-stopped {
|
||||
color: var(--cyan);
|
||||
background: color-mix(in srgb, var(--cyan) 12%, var(--bg-base));
|
||||
border-color: color-mix(in srgb, var(--cyan) 16%, var(--border));
|
||||
.account-records-stat.is-retry {
|
||||
color: var(--blue);
|
||||
background: var(--blue-soft);
|
||||
border-color: color-mix(in srgb, var(--blue) 16%, var(--border));
|
||||
}
|
||||
|
||||
.account-run-history-list {
|
||||
.account-records-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-height: 168px;
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.account-run-history-item {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 10px;
|
||||
.account-records-list::-webkit-scrollbar { width: 5px; }
|
||||
.account-records-list::-webkit-scrollbar-track { background: transparent; }
|
||||
.account-records-list::-webkit-scrollbar-thumb { background: var(--bg-elevated); border-radius: 4px; }
|
||||
.account-records-list::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
|
||||
|
||||
.account-records-empty {
|
||||
min-height: 168px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 9px;
|
||||
justify-content: center;
|
||||
padding: 20px 12px;
|
||||
border: 1px dashed var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: color-mix(in srgb, var(--bg-base) 92%, transparent);
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.account-record-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 10px 11px;
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-sm);
|
||||
background: color-mix(in srgb, var(--bg-base) 90%, transparent);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.account-run-history-item-main {
|
||||
min-width: 0;
|
||||
.account-record-item-top,
|
||||
.account-record-item-bottom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.account-run-history-item-email {
|
||||
.account-record-item-email {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -1790,24 +1844,14 @@ header {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.account-run-history-item-detail {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--text-secondary);
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.account-run-history-item-side {
|
||||
.account-record-item-side {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.account-run-history-item-status {
|
||||
.account-record-item-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 7px;
|
||||
@@ -1818,36 +1862,64 @@ header {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.account-run-history-item-time {
|
||||
.account-record-item-time {
|
||||
color: var(--text-muted);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.account-run-history-item.is-success {
|
||||
.account-record-item-summary {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--text-secondary);
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.account-record-item-retry {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 7px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--bg-base) 70%, var(--blue-soft));
|
||||
color: var(--blue);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.account-record-item.is-success {
|
||||
border-color: color-mix(in srgb, var(--green) 14%, var(--border-subtle));
|
||||
}
|
||||
|
||||
.account-run-history-item.is-success .account-run-history-item-status {
|
||||
.account-record-item.is-success .account-record-item-status {
|
||||
background: var(--green-soft);
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.account-run-history-item.is-failed {
|
||||
.account-record-item.is-failed {
|
||||
border-color: color-mix(in srgb, var(--red) 14%, var(--border-subtle));
|
||||
}
|
||||
|
||||
.account-run-history-item.is-failed .account-run-history-item-status {
|
||||
.account-record-item.is-failed .account-record-item-status {
|
||||
background: var(--red-soft);
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.account-run-history-item.is-stopped {
|
||||
border-color: color-mix(in srgb, var(--cyan) 14%, var(--border-subtle));
|
||||
.account-records-pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.account-run-history-item.is-stopped .account-run-history-item-status {
|
||||
background: color-mix(in srgb, var(--cyan) 12%, var(--bg-base));
|
||||
color: var(--cyan);
|
||||
.account-records-page-label {
|
||||
min-width: 56px;
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
#log-area {
|
||||
|
||||
+28
-12
@@ -289,7 +289,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-row">
|
||||
<span class="data-label">本地留档</span>
|
||||
<span class="data-label">本地同步</span>
|
||||
<div class="data-inline auto-delay-inline">
|
||||
<label class="toggle-switch" for="input-account-run-history-text-enabled">
|
||||
<input type="checkbox" id="input-account-run-history-text-enabled" />
|
||||
@@ -308,7 +308,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-row" id="row-account-run-history-helper-base-url" style="display:none;">
|
||||
<span class="data-label">留档服务</span>
|
||||
<span class="data-label">同步服务</span>
|
||||
<input type="text" id="input-account-run-history-helper-base-url" class="data-input mono" placeholder="http://127.0.0.1:17373" />
|
||||
</div>
|
||||
<div class="data-row">
|
||||
@@ -555,21 +555,36 @@
|
||||
<section id="log-section">
|
||||
<div class="log-header">
|
||||
<span class="section-label">日志</span>
|
||||
<button id="btn-clear-log" class="btn btn-ghost btn-xs" title="清空日志">清空</button>
|
||||
</div>
|
||||
<div id="account-run-history-strip" class="account-run-history-strip" hidden>
|
||||
<div class="account-run-history-head">
|
||||
<div class="account-run-history-copy">
|
||||
<span class="account-run-history-title">账号运行历史</span>
|
||||
<span id="account-run-history-meta" class="account-run-history-meta">最近运行记录</span>
|
||||
</div>
|
||||
<div id="account-run-history-stats" class="account-run-history-stats"></div>
|
||||
<div class="log-header-actions">
|
||||
<button id="btn-open-account-records" class="btn btn-ghost btn-xs" title="查看邮箱记录">记录</button>
|
||||
<button id="btn-clear-log" class="btn btn-ghost btn-xs" title="清空日志">清空</button>
|
||||
</div>
|
||||
<div id="account-run-history-list" class="account-run-history-list"></div>
|
||||
</div>
|
||||
<div id="log-area"></div>
|
||||
</section>
|
||||
|
||||
<div id="account-records-overlay" class="account-records-overlay" hidden>
|
||||
<div class="account-records-panel">
|
||||
<div class="account-records-panel-header">
|
||||
<div class="account-records-panel-copy">
|
||||
<span class="account-records-panel-title">邮箱记录</span>
|
||||
<span id="account-records-meta" class="account-records-panel-meta">暂无邮箱记录</span>
|
||||
</div>
|
||||
<div class="account-records-panel-actions">
|
||||
<button id="btn-clear-account-records" class="btn btn-ghost btn-xs" type="button">清理记录</button>
|
||||
<button id="btn-close-account-records" class="modal-close" type="button" aria-label="关闭">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="account-records-stats" class="account-records-stats"></div>
|
||||
<div id="account-records-list" class="account-records-list"></div>
|
||||
<div class="account-records-pagination">
|
||||
<button id="btn-account-records-prev" class="btn btn-outline btn-xs" type="button">上一页</button>
|
||||
<span id="account-records-page-label" class="account-records-page-label mono">0 / 0</span>
|
||||
<button id="btn-account-records-next" class="btn btn-outline btn-xs" type="button">下一页</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="auto-start-modal" class="modal-overlay" hidden>
|
||||
<div class="modal-card">
|
||||
<div class="modal-header">
|
||||
@@ -603,6 +618,7 @@
|
||||
<script src="hotmail-manager.js"></script>
|
||||
<script src="icloud-manager.js"></script>
|
||||
<script src="luckmail-manager.js"></script>
|
||||
<script src="account-records-manager.js"></script>
|
||||
<script src="sidepanel.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
+48
-193
@@ -11,10 +11,16 @@ const STATUS_ICONS = {
|
||||
};
|
||||
|
||||
const logArea = document.getElementById('log-area');
|
||||
const accountRunHistoryStrip = document.getElementById('account-run-history-strip');
|
||||
const accountRunHistoryMeta = document.getElementById('account-run-history-meta');
|
||||
const accountRunHistoryStats = document.getElementById('account-run-history-stats');
|
||||
const accountRunHistoryList = document.getElementById('account-run-history-list');
|
||||
const btnOpenAccountRecords = document.getElementById('btn-open-account-records');
|
||||
const accountRecordsOverlay = document.getElementById('account-records-overlay');
|
||||
const accountRecordsMeta = document.getElementById('account-records-meta');
|
||||
const accountRecordsStats = document.getElementById('account-records-stats');
|
||||
const accountRecordsList = document.getElementById('account-records-list');
|
||||
const accountRecordsPageLabel = document.getElementById('account-records-page-label');
|
||||
const btnAccountRecordsPrev = document.getElementById('btn-account-records-prev');
|
||||
const btnAccountRecordsNext = document.getElementById('btn-account-records-next');
|
||||
const btnCloseAccountRecords = document.getElementById('btn-close-account-records');
|
||||
const btnClearAccountRecords = document.getElementById('btn-clear-account-records');
|
||||
const updateSection = document.getElementById('update-section');
|
||||
const extensionUpdateStatus = document.getElementById('extension-update-status');
|
||||
const extensionVersionMeta = document.getElementById('extension-version-meta');
|
||||
@@ -874,7 +880,7 @@ function syncLatestState(nextState) {
|
||||
stepStatuses: mergedStepStatuses,
|
||||
};
|
||||
|
||||
renderAccountRunHistory(latestState);
|
||||
renderAccountRecords(latestState);
|
||||
}
|
||||
|
||||
function hasOwnStateValue(source, key) {
|
||||
@@ -1382,7 +1388,7 @@ function normalizeAccountRunHistoryHelperBaseUrlValue(value = '') {
|
||||
return DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL;
|
||||
}
|
||||
|
||||
if (parsed.pathname === '/append-account-log') {
|
||||
if (parsed.pathname === '/append-account-log' || parsed.pathname === '/sync-account-run-records') {
|
||||
parsed.pathname = '';
|
||||
parsed.search = '';
|
||||
parsed.hash = '';
|
||||
@@ -2697,193 +2703,6 @@ function appendLog(entry) {
|
||||
logArea.scrollTop = logArea.scrollHeight;
|
||||
}
|
||||
|
||||
function getAccountRunHistory(state = latestState) {
|
||||
return Array.isArray(state?.accountRunHistory)
|
||||
? state.accountRunHistory.filter((item) => item && typeof item === 'object')
|
||||
: [];
|
||||
}
|
||||
|
||||
function parseAccountRunStatus(status = '') {
|
||||
const normalized = String(status || '').trim().toLowerCase();
|
||||
const stepMatch = normalized.match(/^step(\d+)_(failed|stopped)$/);
|
||||
if (stepMatch) {
|
||||
return {
|
||||
kind: stepMatch[2] === 'failed' ? 'failed' : 'stopped',
|
||||
label: `步${stepMatch[1]}${stepMatch[2] === 'failed' ? '失败' : '停止'}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (normalized === 'success') {
|
||||
return { kind: 'success', label: '成功' };
|
||||
}
|
||||
if (normalized === 'failed') {
|
||||
return { kind: 'failed', label: '失败' };
|
||||
}
|
||||
if (normalized === 'stopped') {
|
||||
return { kind: 'stopped', label: '已停止' };
|
||||
}
|
||||
|
||||
return {
|
||||
kind: 'unknown',
|
||||
label: normalized || '记录',
|
||||
};
|
||||
}
|
||||
|
||||
function summarizeAccountRunHistory(records = []) {
|
||||
return records.reduce((summary, record) => {
|
||||
summary.total += 1;
|
||||
const { kind } = parseAccountRunStatus(record?.status);
|
||||
if (kind === 'success') {
|
||||
summary.success += 1;
|
||||
} else if (kind === 'failed') {
|
||||
summary.failed += 1;
|
||||
} else if (kind === 'stopped') {
|
||||
summary.stopped += 1;
|
||||
} else {
|
||||
summary.other += 1;
|
||||
}
|
||||
return summary;
|
||||
}, {
|
||||
total: 0,
|
||||
success: 0,
|
||||
failed: 0,
|
||||
stopped: 0,
|
||||
other: 0,
|
||||
});
|
||||
}
|
||||
|
||||
function formatAccountRunHistoryTime(recordedAt) {
|
||||
const date = new Date(recordedAt);
|
||||
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: DISPLAY_TIMEZONE,
|
||||
});
|
||||
}
|
||||
|
||||
return date.toLocaleString('zh-CN', {
|
||||
hour12: false,
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
...(sameYear ? {} : { year: '2-digit' }),
|
||||
timeZone: DISPLAY_TIMEZONE,
|
||||
}).replace(/\//g, '-');
|
||||
}
|
||||
|
||||
function buildAccountRunHistoryDetailText(record = {}) {
|
||||
const reason = String(record.reason || '').trim();
|
||||
if (reason) {
|
||||
return reason;
|
||||
}
|
||||
|
||||
const { kind } = parseAccountRunStatus(record.status);
|
||||
if (kind === 'success') {
|
||||
return '流程已完成并写入本地记录';
|
||||
}
|
||||
if (kind === 'stopped') {
|
||||
return '流程被手动停止,已保留当前账号快照';
|
||||
}
|
||||
if (kind === 'failed') {
|
||||
return '流程执行失败,已保留当前账号快照';
|
||||
}
|
||||
|
||||
return '账号运行记录已保存';
|
||||
}
|
||||
|
||||
function createAccountRunHistoryStat(label, value, className = '') {
|
||||
const item = document.createElement('span');
|
||||
item.className = `account-run-history-stat${className ? ` ${className}` : ''}`;
|
||||
item.innerHTML = `<strong>${escapeHtml(String(value))}</strong>${escapeHtml(label)}`;
|
||||
return item;
|
||||
}
|
||||
|
||||
function renderAccountRunHistory(state = latestState) {
|
||||
if (!accountRunHistoryStrip || !accountRunHistoryStats || !accountRunHistoryList || !accountRunHistoryMeta) {
|
||||
return;
|
||||
}
|
||||
|
||||
const records = getAccountRunHistory(state);
|
||||
if (!records.length) {
|
||||
accountRunHistoryStrip.hidden = true;
|
||||
accountRunHistoryStats.innerHTML = '';
|
||||
accountRunHistoryList.innerHTML = '';
|
||||
accountRunHistoryMeta.textContent = '最近运行记录';
|
||||
return;
|
||||
}
|
||||
|
||||
const summary = summarizeAccountRunHistory(records);
|
||||
const recentRecords = records.slice(-2).reverse();
|
||||
const latestRecord = records[records.length - 1] || null;
|
||||
|
||||
accountRunHistoryStrip.hidden = false;
|
||||
accountRunHistoryMeta.textContent = latestRecord
|
||||
? `共 ${summary.total} 条,最近更新于 ${formatAccountRunHistoryTime(latestRecord.recordedAt)}`
|
||||
: `共 ${summary.total} 条`;
|
||||
|
||||
accountRunHistoryStats.innerHTML = '';
|
||||
accountRunHistoryStats.appendChild(createAccountRunHistoryStat('总', summary.total));
|
||||
if (summary.success > 0) {
|
||||
accountRunHistoryStats.appendChild(createAccountRunHistoryStat('成', summary.success, 'is-success'));
|
||||
}
|
||||
if (summary.failed > 0) {
|
||||
accountRunHistoryStats.appendChild(createAccountRunHistoryStat('失', summary.failed, 'is-failed'));
|
||||
}
|
||||
if (summary.stopped > 0) {
|
||||
accountRunHistoryStats.appendChild(createAccountRunHistoryStat('停', summary.stopped, 'is-stopped'));
|
||||
}
|
||||
|
||||
accountRunHistoryList.innerHTML = '';
|
||||
recentRecords.forEach((record) => {
|
||||
const statusMeta = parseAccountRunStatus(record.status);
|
||||
const item = document.createElement('div');
|
||||
item.className = `account-run-history-item is-${statusMeta.kind}`;
|
||||
item.title = [
|
||||
record.email || '',
|
||||
statusMeta.label,
|
||||
record.reason || '',
|
||||
].filter(Boolean).join('\n');
|
||||
|
||||
const main = document.createElement('div');
|
||||
main.className = 'account-run-history-item-main';
|
||||
|
||||
const email = document.createElement('div');
|
||||
email.className = 'account-run-history-item-email mono';
|
||||
email.textContent = String(record.email || '').trim() || '(空邮箱)';
|
||||
|
||||
const detail = document.createElement('div');
|
||||
detail.className = 'account-run-history-item-detail';
|
||||
detail.textContent = buildAccountRunHistoryDetailText(record);
|
||||
|
||||
const side = document.createElement('div');
|
||||
side.className = 'account-run-history-item-side';
|
||||
|
||||
const status = document.createElement('span');
|
||||
status.className = 'account-run-history-item-status';
|
||||
status.textContent = statusMeta.label;
|
||||
|
||||
const time = document.createElement('span');
|
||||
time.className = 'account-run-history-item-time mono';
|
||||
time.textContent = formatAccountRunHistoryTime(record.recordedAt);
|
||||
|
||||
main.append(email, detail);
|
||||
side.append(status, time);
|
||||
item.append(main, side);
|
||||
accountRunHistoryList.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
@@ -3108,6 +2927,42 @@ const resetLuckmailManager = luckmailManager?.reset
|
||||
const bindLuckmailEvents = luckmailManager?.bindLuckmailEvents
|
||||
|| (() => { });
|
||||
bindLuckmailEvents();
|
||||
|
||||
const accountRecordsManager = window.SidepanelAccountRecordsManager?.createAccountRecordsManager({
|
||||
state: {
|
||||
getLatestState: () => latestState,
|
||||
syncLatestState,
|
||||
},
|
||||
dom: {
|
||||
accountRecordsList,
|
||||
accountRecordsMeta,
|
||||
accountRecordsOverlay,
|
||||
accountRecordsPageLabel,
|
||||
accountRecordsStats,
|
||||
btnAccountRecordsNext,
|
||||
btnAccountRecordsPrev,
|
||||
btnClearAccountRecords,
|
||||
btnCloseAccountRecords,
|
||||
btnOpenAccountRecords,
|
||||
},
|
||||
helpers: {
|
||||
escapeHtml,
|
||||
openConfirmModal,
|
||||
showToast,
|
||||
},
|
||||
runtime: {
|
||||
sendMessage: (message) => chrome.runtime.sendMessage(message),
|
||||
},
|
||||
constants: {
|
||||
displayTimeZone: DISPLAY_TIMEZONE,
|
||||
pageSize: 10,
|
||||
},
|
||||
});
|
||||
const renderAccountRecords = accountRecordsManager?.render
|
||||
|| (() => { });
|
||||
const bindAccountRecordEvents = accountRecordsManager?.bindEvents
|
||||
|| (() => { });
|
||||
bindAccountRecordEvents();
|
||||
renderStepsList();
|
||||
|
||||
async function exportSettingsFile() {
|
||||
|
||||
Reference in New Issue
Block a user