fix(logs): 忽略/取消忽略操作写入操作日志

原 /api/ignore 与 /api/ignore/:pkgName 只写 ignored_packages 表不记日志,
导致操作日志页查不到忽略记录。logs 表新增 action 列区分操作类型
(clean/ignore/unignore,旧库运行时 ALTER TABLE 迁移),忽略与取消忽略
接口补写 addLog;日志页按操作类型展示徽标与详情,删除数量/释放空间对
非清理操作显示占位符。
This commit is contained in:
陈银军
2026-09-19 21:58:44 +08:00
parent d5074d0ff4
commit a0f0c27c44
5 changed files with 81 additions and 27 deletions
+12
View File
@@ -156,6 +156,12 @@ const api = {
mockState.ignored.add(pkgName)
const p = mockState.packages.find(x => x.name === pkgName)
if (p) { p.status = 'ignored'; p.ignoredAt = new Date().toISOString().slice(0, 10) }
mockState.logs.unshift({
id: Date.now(),
action: 'ignore',
pkgName,
created_at: new Date().toISOString().slice(0, 16).replace('T', ' '),
})
return { ok: true }
}
const r = await fetch('/api/ignore', {
@@ -175,6 +181,12 @@ const api = {
p.status = computeStatus(p, mockState.versionThreshold)
delete p.ignoredAt
}
mockState.logs.unshift({
id: Date.now(),
action: 'unignore',
pkgName,
created_at: new Date().toISOString().slice(0, 16).replace('T', ' '),
})
return { ok: true }
}
const r = await fetch(`/api/ignore/${encodeURIComponent(pkgName)}`, { method: 'DELETE' })
+4 -4
View File
@@ -143,11 +143,11 @@ function topMax(list) {
return Math.max(...list.map(p => p.visits), 1)
}
// 日志
// 日志(action: clean=清理 / ignore=忽略 / unignore=取消忽略)
const logs = [
{ id: 3, pkgName: '@antv/g2', deletedVersions: '1.0.0, 1.1.0, 1.2.0', deletedCount: 3, freedSpace: '36.5 MB', created_at: dateStr(0) + ' 09:12:04' },
{ id: 2, pkgName: 'lodash', deletedVersions: '4.0.0, 4.1.0, 4.2.0, 4.3.0', deletedCount: 4, freedSpace: '15.2 MB', created_at: dateStr(1) + ' 18:40:22' },
{ id: 1, pkgName: 'axios', deletedVersions: '0.19.0', deletedCount: 1, freedSpace: '1.1 MB', created_at: dateStr(2) + ' 11:05:47' },
{ id: 3, action: 'clean', pkgName: '@antv/g2', deletedVersions: '1.0.0, 1.1.0, 1.2.0', deletedCount: 3, freedSpace: '36.5 MB', created_at: dateStr(0) + ' 09:12:04' },
{ id: 2, action: 'ignore', pkgName: 'moment', created_at: dateStr(1) + ' 18:40:22' },
{ id: 1, action: 'clean', pkgName: 'axios', deletedVersions: '0.19.0', deletedCount: 1, freedSpace: '1.1 MB', created_at: dateStr(2) + ' 11:05:47' },
]
// 状态统计(与 overview 对齐)
+51 -19
View File
@@ -10,13 +10,20 @@ onMounted(() => refreshLogs())
function fmtTime(t) {
return (t || '').replace('T', ' ').slice(0, 19)
}
// 操作类型 → 徽标文案/class/emoji
function actionMeta(action) {
if (action === 'ignore') return { emo: '💤', text: '忽略', cls: 'ignore' }
if (action === 'unignore') return { emo: '↩️', text: '取消忽略', cls: 'unignore' }
return { emo: '🗑', text: '清理', cls: 'clean' }
}
</script>
<template>
<div class="page-inner">
<div class="page-title"><span class="emo">🕒</span>最近动态</div>
<div class="page-sub">
共 <b>{{ state.logs.length }}</b> 条清理记录 · 按时间倒序
共 <b>{{ state.logs.length }}</b> 条操作记录 · 按时间倒序
<span class="log-live"><i></i>LIVE</span>
</div>
@@ -26,8 +33,9 @@ function fmtTime(t) {
<tr>
<th>时间</th>
<th>包名</th>
<th style="width: 120px">删除数量</th>
<th style="width: 120px">释放空间</th>
<th style="width: 110px">操作类型</th>
<th style="width: 100px">删除数量</th>
<th style="width: 100px">释放空间</th>
<th style="width: 90px">操作</th>
</tr>
</thead>
@@ -35,13 +43,24 @@ function fmtTime(t) {
<tr v-for="log in state.logs" :key="log.id" @click="detail = log">
<td><span class="date-cell">{{ fmtTime(log.created_at) }}</span></td>
<td><span class="pkg-cell">{{ log.pkgName }}</span></td>
<td><span class="chip n">− {{ log.deletedCount }} 版</span></td>
<td><span class="chip s">🟢 {{ log.freedSpace }}</span></td>
<td>
<span class="act-badge" :class="actionMeta(log.action).cls">
{{ actionMeta(log.action).emo }} {{ actionMeta(log.action).text }}
</span>
</td>
<td>
<span v-if="log.action === 'clean'" class="chip n">− {{ log.deletedCount }} 版</span>
<span v-else class="dash">—</span>
</td>
<td>
<span v-if="log.action === 'clean'" class="chip s">🟢 {{ log.freedSpace }}</span>
<span v-else class="dash">—</span>
</td>
<td><button class="btn ghost" @click.stop="detail = log">详情</button></td>
</tr>
</tbody>
</table>
<div v-if="!state.logs.length" class="empty-tip">暂无清理记录,清理操作会自动记录在这里</div>
<div v-if="!state.logs.length" class="empty-tip">暂无操作记录,清理、忽略等操作会自动记录在这里</div>
</div>
</div>
@@ -53,7 +72,9 @@ function fmtTime(t) {
<Transition name="pop">
<div v-if="detail" class="detail-dialog">
<div class="dd-head">
<span class="dd-title">📋 清理详情</span>
<span class="dd-title">
{{ detail.action === 'ignore' ? '💤 忽略' : detail.action === 'unignore' ? '↩️ 取消忽略' : '📋 清理详情' }}
</span>
<div class="dd-close" @click="detail = null">✕</div>
</div>
<div class="dd-body">
@@ -65,19 +86,25 @@ function fmtTime(t) {
<span class="k">操作时间</span>
<span class="v">{{ fmtTime(detail.created_at) }}</span>
</div>
<div class="dd-row">
<span class="k">删除数量</span>
<span class="v red">− {{ detail.deletedCount }} 个版本</span>
</div>
<div class="dd-row">
<span class="k">释放空间</span>
<span class="v green">🟢 {{ detail.freedSpace }}</span>
</div>
<div class="dd-row">
<span class="k">删除版本</span>
<div class="dd-tags">
<span v-for="v in (detail.deletedVersions || '').split(',').map(s => s.trim()).filter(Boolean)" :key="v" class="dd-tag">{{ v }}</span>
<template v-if="detail.action === 'clean'">
<div class="dd-row">
<span class="k">删除数量</span>
<span class="v red">− {{ detail.deletedCount }} 个版本</span>
</div>
<div class="dd-row">
<span class="k">释放空间</span>
<span class="v green">🟢 {{ detail.freedSpace }}</span>
</div>
<div class="dd-row">
<span class="k">删除版本</span>
<div class="dd-tags">
<span v-for="v in (detail.deletedVersions || '').split(',').map(s => s.trim()).filter(Boolean)" :key="v" class="dd-tag">{{ v }}</span>
</div>
</div>
</template>
<div v-else class="dd-row">
<span class="k">说明</span>
<span class="v">{{ detail.action === 'ignore' ? '已将包加入忽略列表,不再提示超阈值警告' : '已取消忽略,恢复超阈值提醒' }}</span>
</div>
</div>
</div>
@@ -102,9 +129,14 @@ function fmtTime(t) {
}
.log-live i { width: 7px; height: 7px; border-radius: 50%; background: var(--green-deep); box-shadow: 0 0 8px var(--green-deep); animation: blink 1.6s infinite; }
.pkg-cell { font-weight: 600; word-break: break-all; }
.act-badge { font-family: var(--num); font-size: 11.5px; font-weight: 700; padding: 4px 10px; border-radius: 9px; display: inline-block; }
.act-badge.clean { color: var(--red); background: var(--red-soft); }
.act-badge.ignore { color: var(--ink-dim); background: var(--num-bg); }
.act-badge.unignore { color: var(--green-deep); background: var(--green-soft); }
.chip { font-family: var(--num); font-size: 12px; font-weight: 700; padding: 4px 12px; border-radius: 9px; display: inline-block; }
.chip.n { color: var(--red); background: var(--red-soft); }
.chip.s { color: var(--green-deep); background: var(--green-soft); }
.dash { color: var(--ink-faint); }
.empty-tip { padding: 50px 0; text-align: center; color: var(--ink-faint); font-size: 13px; }
.ov { position: fixed; inset: 0; background: rgba(60, 45, 30, .4); z-index: 140; }