feat: 互动记录/数据看板自动刷新 + 日志时间格式修复

1. Interactions.vue: 每30秒自动刷新互动记录列表
2. Dashboard.vue: 每30秒自动刷新数据看板
3. Logs.vue: 时间格式修复(T→空格,去掉时区标识)
4. logs.py: created_at 改用 strftime 输出 +08:00 格式(而非 isoformat 的 +00:00)

页面保持浏览时自动获取最新数据,离开页面时自动清除定时器
This commit is contained in:
stefanfeng
2026-04-07 14:51:59 +08:00
parent 7d9f5a358b
commit b43ee777fc
4 changed files with 13 additions and 3 deletions

View File

@@ -37,7 +37,7 @@ async def get_login_logs(
items = [{ items = [{
"id": l.id, "user_id": l.user_id, "user_account": l.user_account, "id": l.id, "user_id": l.user_id, "user_account": l.user_account,
"action": l.action, "session_id": l.session_id, "action": l.action, "session_id": l.session_id,
"error_msg": l.error_msg, "created_at": l.created_at.isoformat() "error_msg": l.error_msg, "created_at": l.created_at.strftime("%Y-%m-%dT%H:%M:%S+08:00") if l.created_at else None
} for l in logs] } for l in logs]
return ApiResponse(data={"total": total, "page": page, "page_size": page_size, "items": items}) return ApiResponse(data={"total": total, "page": page, "page_size": page_size, "items": items})

View File

@@ -216,10 +216,13 @@ onMounted(async () => {
await loadMonthlyTrend() await loadMonthlyTrend()
window.addEventListener('resize', handleResize) window.addEventListener('resize', handleResize)
window.addEventListener('page-refresh', loadDashboard) window.addEventListener('page-refresh', loadDashboard)
// 每30秒自动刷新数据看板
_dashTimer = setInterval(loadDashboard, 30000)
}) })
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('resize', handleResize) window.removeEventListener('resize', handleResize)
window.removeEventListener('page-refresh', loadDashboard) window.removeEventListener('page-refresh', loadDashboard)
if (_dashTimer) clearInterval(_dashTimer)
dailyChartInst?.dispose() dailyChartInst?.dispose()
monthlyChartInst?.dispose() monthlyChartInst?.dispose()
}) })

View File

@@ -194,12 +194,19 @@ async function handleExport() {
const a = document.createElement('a'); a.href = url; a.download = 'interactions.xlsx'; a.click() const a = document.createElement('a'); a.href = url; a.download = 'interactions.xlsx'; a.click()
} }
let _autoRefreshTimer = null
onMounted(async () => { onMounted(async () => {
await loadArticleDomain() // 先等域名加载完 await loadArticleDomain() // 先等域名加载完
load() load()
window.addEventListener('page-refresh', load) window.addEventListener('page-refresh', load)
// 每30秒自动刷新互动记录
_autoRefreshTimer = setInterval(load, 30000)
})
onUnmounted(() => {
window.removeEventListener('page-refresh', load)
if (_autoRefreshTimer) clearInterval(_autoRefreshTimer)
}) })
onUnmounted(() => window.removeEventListener('page-refresh', load))
</script> </script>
<style scoped> <style scoped>
.filter-bar { display: flex; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; } .filter-bar { display: flex; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }

View File

@@ -31,7 +31,7 @@
</el-table-column> </el-table-column>
<el-table-column label="时间" width="160"> <el-table-column label="时间" width="160">
<template #default="{ row }"> <template #default="{ row }">
<span style="font-size:12px;color:var(--color-text-muted)">{{ row.created_at?.slice(0,16) }}</span> <span style="font-size:12px;color:var(--color-text-muted)">{{ row.created_at ? row.created_at.replace('T',' ').replace('+08:00','').replace('+00:00','').slice(0,16) : '--' }}</span>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>