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:
@@ -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})
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user