feat: 看板最近访问查看更多跳转+访问埋点今日/昨日/7天/30天/来源筛选(visits接口补range/source参数与统计)

This commit is contained in:
Hermes Agent
2026-09-19 15:53:04 +08:00
parent b7d085c475
commit 49946c6d21
4 changed files with 91 additions and 10 deletions
+4 -1
View File
@@ -27,7 +27,7 @@
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { onMounted, ref, provide } from 'vue'
import { getToken, setToken, clearToken, apiPost } from './api'
import Dashboard from './views/Dashboard.vue'
import Users from './views/Users.vue'
@@ -40,6 +40,9 @@ import VpayOrders from './views/VpayOrders.vue'
const token = ref(getToken())
const view = ref('dashboard')
// 子页面跳转菜单(如看板→访问日志)
const goto = (key) => { view.value = key }
provide('goto', goto)
const username = ref(''), password = ref(''), busy = ref(false), error = ref('')
const menus = [
+15 -3
View File
@@ -35,7 +35,10 @@
<p v-if="!(d.litByDim || []).some(b => b.count)" class="empty">还没有点亮记录</p>
</div>
<div class="panel grow">
<h3>最近访问记录</h3>
<div class="rec-head">
<h3 style="margin: 0">最近访问记录</h3>
<el-button size="small" text type="primary" @click="goVisitLog">查看更多 ›</el-button>
</div>
<el-table :data="d.recentLogins || []" size="small" stripe>
<el-table-column prop="createdAt" label="时间" width="160">
<template #default="{ row }">{{ fmt(row.createdAt) }}</template>
@@ -67,9 +70,11 @@
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ref, onMounted, inject } from 'vue'
import { apiGet } from '../api'
const d = ref({})
const goto = inject('goto', () => {})
function goVisitLog() { goto('logs') }
const visit = ref({})
function pct(n) {
const max = Math.max(1, ...(d.value.litByDim || []).map(b => b.count))
@@ -107,4 +112,11 @@ onMounted(async () => {
.bar-fill { height: 100%; background: linear-gradient(90deg, #5b5ff8, #8b5cf6); border-radius: 4px; }
.bar-num { width: 28px; text-align: right; font-size: 12px; color: #6b7080; }
.empty { color: #b6bac9; font-size: 12px; text-align: center; padding: 12px 0; }
</style>
.rec-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
</style>
+39 -3
View File
@@ -2,9 +2,26 @@
<div class="page">
<el-tabs v-model="tab" @tab-change="load">
<el-tab-pane label="登录/访问日志" name="login" />
<el-tab-pane label="访问埋点" name="visit" />
<el-tab-pane label="访问埋点" name="visit">
<template #label>访问埋点</template>
</el-tab-pane>
<el-tab-pane label="管理操作日志" name="admin" />
</el-tabs>
<div v-if="tab === 'visit'" class="visit-filters">
<el-radio-group v-model="vRange" size="small" @change="vPage = 1; load()">
<el-radio-button value="today">今日 {{ vStats.today ?? '' }}</el-radio-button>
<el-radio-button value="yesterday">昨日 {{ vStats.yesterday ?? '' }}</el-radio-button>
<el-radio-button value="7d">近7天 {{ vStats.last7 ?? '' }}</el-radio-button>
<el-radio-button value="30d">近30天 {{ vStats.last30 ?? '' }}</el-radio-button>
<el-radio-button value="all">全部</el-radio-button>
</el-radio-group>
<el-select v-model="vSource" size="small" clearable placeholder="全部来源" style="width: 120px" @change="vPage = 1; load()">
<el-option value="H5" label="H5" />
<el-option value="小程序" label="小程序" />
<el-option value="直接访问" label="直接访问" />
</el-select>
<span style="font-size: 12px; color: #8a8fa3">共 {{ vTotal }} 条</span>
</div>
<el-table :data="list" stripe v-loading="loading" size="small">
<template v-if="tab === 'login'">
<el-table-column label="时间" width="160"><template #default="{ row }">{{ fmt(row.createdAt) }}</template></el-table-column>
@@ -51,15 +68,26 @@
import { ref, onMounted } from 'vue'
import { apiGet } from '../api'
const tab = ref('login'), list = ref([]), total = ref(0), page = ref(1), pageSize = 20, loading = ref(false)
// 访问埋点筛选
const vRange = ref('7d'), vSource = ref(''), vStats = ref({}), vTotal = ref(0)
function fmt(t) { return t ? new Date(t).toLocaleString('zh-CN', { hour12: false }) : '—' }
// adminlog 的时间字段是 timestamp,loginlog 是 createdAt
async function load() {
loading.value = true
try {
const ep = tab.value === 'login' ? 'login-logs' : tab.value === 'visit' ? 'visits' : 'logs'
const r = await apiGet(`/api/admin/${ep}?page=${page.value}&limit=${pageSize}`)
let qs = `page=${page.value}&limit=${pageSize}`
if (tab.value === 'visit') {
qs += `&range=${vRange.value}`
if (vSource.value) qs += `&source=${encodeURIComponent(vSource.value)}`
}
const r = await apiGet(`/api/admin/${ep}?${qs}`)
list.value = r.items || []
total.value = r.pagination?.total || 0
if (tab.value === 'visit') {
vStats.value = r.stats || {}
vTotal.value = r.total || 0
}
} finally { loading.value = false }
}
onMounted(load)
@@ -67,4 +95,12 @@ onMounted(load)
<style scoped>
.page { background: #fff; border-radius: 12px; padding: 16px; box-shadow: 0 1px 4px rgba(0,0,0,.06); }
</style>
.visit-filters {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 10px;
}
</style>