feat: 添加账号运行历史功能,包括状态分类和摘要展示

This commit is contained in:
QLHazyCoder
2026-04-17 03:00:22 +08:00
parent 6652899758
commit 4a04a09eba
7 changed files with 530 additions and 9 deletions
+182
View File
@@ -1657,6 +1657,188 @@ header {
margin-bottom: 6px;
}
.account-run-history-strip {
margin-bottom: 8px;
padding: 10px 12px;
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);
}
.account-run-history-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
}
.account-run-history-copy {
min-width: 0;
display: flex;
flex-direction: column;
gap: 3px;
}
.account-run-history-title {
font-size: 12px;
font-weight: 700;
letter-spacing: -0.01em;
color: var(--text-primary);
}
.account-run-history-meta {
font-size: 11px;
color: var(--text-secondary);
line-height: 1.45;
}
.account-run-history-stats {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 6px;
flex-wrap: wrap;
flex-shrink: 0;
}
.account-run-history-stat {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
border-radius: 999px;
border: 1px solid var(--border-subtle);
background: color-mix(in srgb, var(--bg-base) 86%, transparent);
font-size: 11px;
font-weight: 700;
color: var(--text-secondary);
}
.account-run-history-stat strong {
font-family: 'JetBrains Mono', 'Consolas', monospace;
font-size: 10px;
}
.account-run-history-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 {
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-run-history-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.account-run-history-item {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 10px;
align-items: center;
padding: 8px 9px;
border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm);
background: color-mix(in srgb, var(--bg-base) 90%, transparent);
}
.account-run-history-item-main {
min-width: 0;
display: flex;
flex-direction: column;
gap: 3px;
}
.account-run-history-item-email {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--text-primary);
font-size: 12px;
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 {
display: flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
}
.account-run-history-item-status {
display: inline-flex;
align-items: center;
padding: 2px 7px;
border-radius: 999px;
font-size: 11px;
font-weight: 700;
background: var(--bg-elevated);
color: var(--text-secondary);
}
.account-run-history-item-time {
color: var(--text-muted);
font-size: 11px;
}
.account-run-history-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 {
background: var(--green-soft);
color: var(--green);
}
.account-run-history-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 {
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-run-history-item.is-stopped .account-run-history-item-status {
background: color-mix(in srgb, var(--cyan) 12%, var(--bg-base));
color: var(--cyan);
}
#log-area {
background: var(--bg-surface);
border: 1px solid var(--border);
+10
View File
@@ -534,6 +534,16 @@
<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>
<div id="account-run-history-list" class="account-run-history-list"></div>
</div>
<div id="log-area"></div>
</section>
+193
View File
@@ -11,6 +11,10 @@ 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 updateSection = document.getElementById('update-section');
const extensionUpdateStatus = document.getElementById('extension-update-status');
const extensionVersionMeta = document.getElementById('extension-version-meta');
@@ -720,6 +724,8 @@ function syncLatestState(nextState) {
...(nextState || {}),
stepStatuses: mergedStepStatuses,
};
renderAccountRunHistory(latestState);
}
function hasOwnStateValue(source, key) {
@@ -2465,6 +2471,193 @@ 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;