fix: resolve TypeScript compilation errors

Fixes:
- VideoPlayer.vue: restore allTags, filteredMarks, marksByTag (removed by mistake)
- PeopleView.vue: fix frameTime scope in playFaceDetailVideo catch block
- Both errors caused white screen on build failure

Root cause:
1. visibleTags duplicate declaration (old + new logic)
2. frameTime undefined in catch block (scope issue)

Solution:
1. Keep allTags/filteredMarks/marksByTag (needed in template)
2. Keep visibleTags (new logic for processor toggles)
3. Move frameTime calculation to correct scope in catch block
This commit is contained in:
2026-07-24 23:33:40 +08:00
parent 48975ef247
commit cb7e1ba0ba
37 changed files with 6875 additions and 205 deletions

View File

@@ -0,0 +1,85 @@
<template>
<div id="ms-view-admin">
<div class="admin-header">
<h1>Admin Panel</h1>
<div class="admin-tabs">
<button class="admin-tab" :class="{ active: activeTab === 'dashboard' }" @click="activeTab = 'dashboard'">Dashboard</button>
<button class="admin-tab" :class="{ active: activeTab === 'users' }" @click="activeTab = 'users'">Users</button>
<button class="admin-tab" :class="{ active: activeTab === 'shares' }" @click="activeTab = 'shares'">Shares</button>
</div>
</div>
<div class="admin-content">
<div v-if="activeTab === 'dashboard'" class="dashboard-panel">
<div class="stats-grid">
<div class="stat-card"><div class="stat-label">CPU</div><div class="stat-value">{{ stats.cpu }}%</div></div>
<div class="stat-card"><div class="stat-label">Memory</div><div class="stat-value">{{ stats.memory }}%</div></div>
<div class="stat-card"><div class="stat-label">Disk</div><div class="stat-value">{{ stats.disk }}%</div></div>
<div class="stat-card"><div class="stat-label">Users</div><div class="stat-value">{{ users.length }}</div></div>
<div class="stat-card"><div class="stat-label">Shares</div><div class="stat-value">{{ shares.length }}</div></div>
</div>
</div>
<div v-if="activeTab === 'users'" class="users-panel">
<table class="admin-table">
<thead><tr><th>Username</th><th>Home Dir</th><th>Status</th></tr></thead>
<tbody>
<tr v-for="u in users" :key="u.username">
<td>{{ u.username }}</td><td>{{ u.homeDir }}</td><td>{{ u.status === 1 ? 'Active' : 'Disabled' }}</td>
</tr>
</tbody>
</table>
</div>
<div v-if="activeTab === 'shares'" class="shares-panel">
<table class="admin-table">
<thead><tr><th>Name</th><th>Port</th><th>Status</th></tr></thead>
<tbody>
<tr v-for="s in shares" :key="s.name">
<td>{{ s.name }}</td><td>{{ s.port }}</td><td>{{ s.status }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { apiCall } from '../api'
const activeTab = ref('dashboard')
const stats = ref({ cpu: 0, memory: 0, disk: 0 })
const users = ref<any[]>([])
const shares = ref<any[]>([])
async function loadDashboard() {
try { stats.value = await apiCall('get_system_stats', {}) || { cpu: 0, memory: 0, disk: 0 } } catch (e) { console.error(e) }
}
async function loadUsers() {
try { users.value = await apiCall('list_admin_users', {}) || [] } catch (e) { console.error(e) }
}
async function loadShares() {
try { shares.value = await apiCall('list_shares', {}) || [] } catch (e) { console.error(e) }
}
onMounted(async () => { await loadDashboard(); await loadUsers(); await loadShares() })
</script>
<style scoped>
#ms-view-admin { padding: 20px; }
.admin-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.admin-tabs { display: flex; gap: 10px; }
.admin-tab { padding: 8px 16px; border: none; background: #f0f0f0; cursor: pointer; border-radius: 4px; }
.admin-tab.active { background: #2563eb; color: white; }
.admin-content { background: white; border-radius: 8px; padding: 20px; }
.stats-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 15px; }
.stat-card { background: #f8f9fa; padding: 15px; border-radius: 8px; text-align: center; }
.stat-label { font-size: 12px; color: #666; }
.stat-value { font-size: 24px; font-weight: bold; }
.admin-table { width: 100%; border-collapse: collapse; }
.admin-table th, .admin-table td { padding: 10px; border: 1px solid #ddd; text-align: left; }
</style>

View File

@@ -0,0 +1,84 @@
<template>
<div id="ms-view-client">
<div class="client-header">
<h1>File Manager</h1>
<div class="client-toolbar">
<button @click="goUp">Up</button>
<button @click="createFolder">New Folder</button>
<button @click="deleteSelected" :disabled="!selectedFile">Delete</button>
<span class="current-path">{{ currentPath }}</span>
</div>
</div>
<div class="client-content">
<table class="file-table">
<thead><tr><th>Name</th><th>Size</th><th>Type</th></tr></thead>
<tbody>
<tr v-for="f in files" :key="f.name" @click="selectFile(f)" :class="{ selected: selectedFile?.name === f.name }">
<td><span :class="f.isDir ? 'folder-icon' : 'file-icon'">{{ f.isDir ? '📁' : '📄' }}</span> {{ f.name }}</td>
<td>{{ f.isDir ? '-' : formatSize(f.size) }}</td>
<td>{{ f.isDir ? 'Folder' : 'File' }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { apiCall } from '../api'
const currentPath = ref('/')
const files = ref<any[]>([])
const selectedFile = ref<any>(null)
async function loadFiles() {
try {
const data = await apiCall('list_client_files', { path: currentPath.value })
files.value = (data || []).sort((a: any, b: any) => a.isDir === b.isDir ? a.name.localeCompare(b.name) : a.isDir ? -1 : 1)
} catch (e) { console.error(e); files.value = [] }
}
function goUp() {
const parts = currentPath.value.split('/').filter(p => p)
parts.pop()
currentPath.value = '/' + parts.join('/')
loadFiles()
}
function selectFile(f: any) {
if (f.isDir) { currentPath.value = currentPath.value === '/' ? '/' + f.name : currentPath.value + '/' + f.name; loadFiles() }
else { selectedFile.value = f }
}
async function createFolder() {
const name = prompt('Folder name:')
if (!name) return
const path = currentPath.value === '/' ? '/' + name : currentPath.value + '/' + name
try { await apiCall('mkdir_client', { path }); loadFiles() } catch (e) { alert('Failed: ' + e) }
}
async function deleteSelected() {
if (!selectedFile.value || !confirm('Delete ' + selectedFile.value.name + '?')) return
const path = currentPath.value === '/' ? '/' + selectedFile.value.name : currentPath.value + '/' + selectedFile.value.name
try { await apiCall('rm_client', { path }); selectedFile.value = null; loadFiles() } catch (e) { alert('Failed: ' + e) }
}
function formatSize(n: number) { return n < 1024 ? n + 'B' : n < 1024*1024 ? (n/1024).toFixed(1) + 'KB' : (n/1024/1024).toFixed(1) + 'MB' }
onMounted(loadFiles)
</script>
<style scoped>
#ms-view-client { padding: 20px; }
.client-header { margin-bottom: 15px; }
.client-toolbar { display: flex; gap: 10px; align-items: center; margin-top: 10px; }
.client-toolbar button { padding: 6px 12px; border: 1px solid #ddd; background: white; cursor: pointer; border-radius: 4px; }
.client-toolbar button:disabled { opacity: 0.5; }
.current-path { margin-left: 20px; font-weight: bold; }
.file-table { width: 100%; border-collapse: collapse; }
.file-table th, .file-table td { padding: 8px; border: 1px solid #eee; }
.file-table tr.selected { background: #e3f2fd; }
.file-table tr:hover { background: #f5f5f5; cursor: pointer; }
</style>

View File

@@ -1353,26 +1353,29 @@ async function playFaceDetailVideo() {
const c = faceDetailModal.value.candidate
if (!c || !c.file_uuid) return
const frame = c.frame_number || 0
const defaultFps = 30
try {
const fileInfo: any = await apiCall('get_file_info', { uuid: c.file_uuid })
const fps = fileInfo?.fps || 30
const fps = fileInfo?.fps || defaultFps
const frameTime = frame / fps
const startTime = Math.max(0, frameTime - 5)
const endTime = frameTime + 5
currentVideo.value = {
currentVideo.value = {
fileUuid: c.file_uuid,
startFrame: startTime * fps,
endFrame: endTime * fps,
startFrame: Math.round(startTime * fps),
endFrame: Math.round(endTime * fps),
title: `F${Math.round(startTime * fps)}-F${Math.round(endTime * fps)} (${c.name})`
}
playing.value = true
} catch (e) {
console.error('Failed to get file info:', e)
const frameTime = frame / defaultFps
currentVideo.value = {
fileUuid: c.file_uuid,
startFrame: Math.round(Math.max(0, frameTime - 5) * 30),
endFrame: Math.round((frameTime + 5) * 30),
title: `F${Math.round(Math.max(0, frameTime - 5) * 30)}-F${Math.round((frameTime + 5) * 30)}`
startFrame: Math.round(Math.max(0, frameTime - 5) * defaultFps),
endFrame: Math.round((frameTime + 5) * defaultFps),
title: `F${Math.round(Math.max(0, frameTime - 5) * defaultFps)}-F${Math.round((frameTime + 5) * defaultFps)}`
}
playing.value = true
}