- 添加项目基础结构:Cargo.toml、.gitignore、设备UID和密钥文件 - 实现前端Vue3项目结构:路由、登录页面、设备管理页面 - 添加核心协议定义(crates/protocol):设备状态、资产、USB事件等 - 实现客户端监控模块:系统状态收集、资产收集 - 实现服务端基础API和插件系统 - 添加数据库迁移脚本:设备管理、资产跟踪、告警系统等 - 实现前端设备状态展示和基本交互 - 添加使用时长统计和水印功能插件
75 lines
3.8 KiB
Vue
75 lines
3.8 KiB
Vue
<template>
|
|
<div class="plugin-page">
|
|
<el-tabs v-model="activeTab">
|
|
<el-tab-pane label="每日使用统计" name="daily">
|
|
<div class="toolbar">
|
|
<el-input v-model="uidFilter" placeholder="终端UID" style="width:200px" clearable @input="fetchDaily" />
|
|
</div>
|
|
<el-table :data="dailyData" v-loading="loading" stripe size="small">
|
|
<el-table-column prop="device_uid" label="终端" width="160" show-overflow-tooltip />
|
|
<el-table-column prop="date" label="日期" width="120" />
|
|
<el-table-column label="活跃时间" width="120">
|
|
<template #default="{ row }">{{ formatMinutes(row.total_active_minutes) }}</template>
|
|
</el-table-column>
|
|
<el-table-column label="空闲时间" width="120">
|
|
<template #default="{ row }">{{ formatMinutes(row.total_idle_minutes) }}</template>
|
|
</el-table-column>
|
|
<el-table-column prop="first_active_at" label="首次活跃" width="170" />
|
|
<el-table-column prop="last_active_at" label="最后活跃" width="170" />
|
|
</el-table>
|
|
</el-tab-pane>
|
|
<el-tab-pane label="应用使用详情" name="apps">
|
|
<div class="toolbar"><el-input v-model="appUid" placeholder="终端UID" style="width:200px" clearable @input="fetchApps" /></div>
|
|
<el-table :data="appData" v-loading="appLoading" stripe size="small">
|
|
<el-table-column prop="app_name" label="应用名称" min-width="200" />
|
|
<el-table-column prop="date" label="日期" width="120" />
|
|
<el-table-column label="使用时长" width="120">
|
|
<template #default="{ row }">{{ formatMinutes(row.usage_minutes) }}</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</el-tab-pane>
|
|
<el-tab-pane label="使用排行" name="leaderboard">
|
|
<el-table :data="board" v-loading="boardLoading" stripe size="small">
|
|
<el-table-column type="index" label="#" width="60" />
|
|
<el-table-column prop="device_uid" label="终端" min-width="200" />
|
|
<el-table-column label="7天总时长" width="140">
|
|
<template #default="{ row }">{{ formatMinutes(row.total_minutes) }}</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</el-tab-pane>
|
|
</el-tabs>
|
|
</div>
|
|
</template>
|
|
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue'
|
|
const activeTab = ref('daily')
|
|
const auth = () => ({ headers: { Authorization: `Bearer ${localStorage.getItem('token')}` } })
|
|
const uidFilter = ref('')
|
|
const dailyData = ref<any[]>([])
|
|
const loading = ref(false)
|
|
const appUid = ref('')
|
|
const appData = ref<any[]>([])
|
|
const appLoading = ref(false)
|
|
const board = ref<any[]>([])
|
|
const boardLoading = ref(false)
|
|
|
|
function formatMinutes(m: number) { if(m>=60) return `${Math.floor(m/60)}h${m%60}m`; return `${m}m` }
|
|
|
|
async function fetchDaily() {
|
|
loading.value=true
|
|
try{const params=new URLSearchParams();if(uidFilter.value)params.set('device_uid',uidFilter.value)
|
|
const r=await fetch(`/api/plugins/usage-timer/daily?${params}`,auth()).then(r=>r.json());if(r.success)dailyData.value=r.data.daily||[]}finally{loading.value=false}
|
|
}
|
|
async function fetchApps() {
|
|
appLoading.value=true
|
|
try{const params=new URLSearchParams();if(appUid.value)params.set('device_uid',appUid.value)
|
|
const r=await fetch(`/api/plugins/usage-timer/app-usage?${params}`,auth()).then(r=>r.json());if(r.success)appData.value=r.data.app_usage||[]}finally{appLoading.value=false}
|
|
}
|
|
async function fetchBoard() {
|
|
boardLoading.value=true
|
|
try{const r=await fetch('/api/plugins/usage-timer/leaderboard',auth()).then(r=>r.json());if(r.success)board.value=r.data.leaderboard||[]}finally{boardLoading.value=false}
|
|
}
|
|
onMounted(()=>{fetchDaily();fetchApps();fetchBoard()})
|
|
</script>
|
|
<style scoped>.plugin-page{padding:20px}.toolbar{display:flex;gap:12px;margin-bottom:16px}</style>
|