feat: 人员库弹框显示人员图片
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -620,6 +620,7 @@ class PersonManager:
|
||||
'visit_count': p['visit_count'],
|
||||
'first_seen': p['first_seen'], # 已经精确到秒
|
||||
'last_seen': p['last_seen'], # 已经精确到秒
|
||||
'face_path': p.get('face_path', ''), # 人脸图片路径
|
||||
}
|
||||
for p in self.persons.values()
|
||||
]
|
||||
|
||||
Binary file not shown.
+19
@@ -195,6 +195,25 @@ async def rename_person(person_id: str, name: str):
|
||||
raise HTTPException(status_code=404, detail="人员不存在")
|
||||
|
||||
|
||||
@app.get("/api/persons/{person_id}/face")
|
||||
async def get_person_face(person_id: str):
|
||||
"""获取人员人脸图片"""
|
||||
from person_manager import person_manager
|
||||
from pathlib import Path
|
||||
|
||||
if person_id in person_manager.persons:
|
||||
face_path = person_manager.persons[person_id].get('face_path', '')
|
||||
if face_path and Path(face_path).exists():
|
||||
return FileResponse(face_path)
|
||||
|
||||
# 检查 faces_dir 中的默认图片
|
||||
face_file = person_manager.faces_dir / f"{person_id}.jpg"
|
||||
if face_file.exists():
|
||||
return FileResponse(str(face_file))
|
||||
|
||||
raise HTTPException(status_code=404, detail="人员图片不存在")
|
||||
|
||||
|
||||
@app.get("/api/stats/daily")
|
||||
async def get_daily_stats(date: str = None):
|
||||
"""获取每日统计数据"""
|
||||
|
||||
+10
-1
@@ -630,7 +630,15 @@ function loadPersonsList() {
|
||||
var firstSeen = new Date(person.first_seen).toLocaleString();
|
||||
var lastSeen = new Date(person.last_seen).toLocaleString();
|
||||
|
||||
item.innerHTML = '<div class="person-info">' +
|
||||
// 构建人员图片URL
|
||||
var faceImgHtml = '<div class="person-face-placeholder">No Image</div>';
|
||||
if (person.face_path) {
|
||||
faceImgHtml = '<img class="person-face-img" src="/api/persons/' + person.person_id + '/face" alt="' + person.name + '" onerror="this.parentElement.innerHTML=\'<div class="person-face-placeholder">No Image</div>\'">';
|
||||
}
|
||||
|
||||
item.innerHTML = '<div class="person-face">' + faceImgHtml + '</div>' +
|
||||
'<div class="person-content">' +
|
||||
'<div class="person-info">' +
|
||||
'<span class="person-name">' + person.name + '</span>' +
|
||||
'<span class="person-id">' + person.person_id + '</span>' +
|
||||
'</div>' +
|
||||
@@ -642,6 +650,7 @@ function loadPersonsList() {
|
||||
'<div class="person-actions">' +
|
||||
'<button onclick="renamePerson(\'' + person.person_id + '\')" class="btn-small">Rename</button>' +
|
||||
'<button onclick="deletePerson(\'' + person.person_id + '\')" class="btn-small btn-danger">Delete</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
listDiv.appendChild(item);
|
||||
|
||||
@@ -751,6 +751,38 @@ button {
|
||||
margin-bottom: 8px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #2196F3;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.person-face {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #e0e0e0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.person-face-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.person-face-placeholder {
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.person-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.person-info {
|
||||
@@ -771,6 +803,7 @@ button {
|
||||
|
||||
.person-stats {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
color: #555;
|
||||
font-size: 12px;
|
||||
|
||||
Reference in New Issue
Block a user