feat: portrait cards, photo storage, edit button for face gallery

This commit is contained in:
tian 2026-05-08 11:10:58 +08:00
parent f3ae1ba6f4
commit b52db0b434
3 changed files with 33 additions and 5 deletions

View File

@ -145,3 +145,14 @@ func (r *FaceGalleryRepo) DeletePerson(id int) error {
_, err = db.Exec(`DELETE FROM face_persons WHERE id = ?`, id)
return err
}
func (r *FaceGalleryRepo) RenamePerson(id int, name string) error {
db, err := r.open()
if err != nil {
return err
}
defer db.Close()
now := time.Now().Format(time.RFC3339)
_, err = db.Exec(`UPDATE face_persons SET name = ?, updated_at = ? WHERE id = ?`, name, now, id)
return err
}

View File

@ -670,6 +670,7 @@ func (u *UI) Routes() (chi.Router, error) {
r.Post("/face-gallery/build", u.actionFaceGalleryBuild)
r.Post("/face-gallery/add", u.actionFaceGalleryAdd)
r.Post("/face-gallery/delete", u.actionFaceGalleryDelete)
r.Post("/face-gallery/rename", u.actionFaceGalleryRename)
r.Get("/monitor", u.pageMonitor)
r.Get("/hls/*", u.proxyHLS)
r.Get("/api/monitor/channels", u.apiMonitorChannels)
@ -4080,7 +4081,9 @@ func (u *UI) actionFaceGalleryImport(w http.ResponseWriter, r *http.Request) {
if len(parts) < 2 { continue }
personName := strings.TrimSpace(parts[len(parts)-2])
if personName != "" {
repo.FindOrCreatePerson(personName)
pid, _ := repo.FindOrCreatePerson(personName)
// Store relative path for display
repo.AddPhoto(pid, personName+"/"+filepath.Base(fp))
}
}
}
@ -4133,3 +4136,16 @@ func (u *UI) serveFacePhoto(w http.ResponseWriter, r *http.Request) {
fullPath := filepath.Join("dataset", path)
http.ServeFile(w, r, fullPath)
}
func (u *UI) actionFaceGalleryRename(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.Atoi(r.FormValue("id"))
name := strings.TrimSpace(r.FormValue("name"))
if id <= 0 || name == "" {
http.Error(w, "invalid", http.StatusBadRequest)
return
}
if strings.TrimSpace(u.dbPath) != "" {
storage.NewFaceGalleryRepo(u.dbPath).RenamePerson(id, name)
}
http.Redirect(w, r, "/ui/face-gallery", http.StatusFound)
}

View File

@ -50,13 +50,13 @@
{{if .FaceGalleryPersons}}
<div style="display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px;margin-top:12px">
{{range .FaceGalleryPersons}}
<div class="card" style="padding:8px;position:relative">
<div class="photo-viewer" data-photos='[{{range $i,$p := .Photos}}{{if $i}},{{end}}"/face-photo/{{$p}}"{{end}}]' style="width:100%;height:180px;background:var(--surface-soft);border-radius:var(--radius);overflow:hidden;position:relative">
<div class="card" style="padding:8px">
<div class="photo-viewer" data-photos='[{{range $i,$p := .Photos}}{{if $i}},{{end}}"/face-photo/{{$p}}"{{end}}]' style="width:100%;height:220px;background:var(--surface-soft);border-radius:var(--radius);overflow:hidden;position:relative">
{{if .Photos}}
<img src="/face-photo/{{index .Photos 0}}" style="width:100%;height:100%;object-fit:cover" id="img-{{.ID}}" />
{{if gt .PhotoCount 1}}
<button class="btn ghost" style="position:absolute;left:4px;top:50%;transform:translateY(-50%);padding:2px 6px;font-size:10px;opacity:0.8" onclick="prevPhoto({{.ID}})">&lt;</button>
<button class="btn ghost" style="position:absolute;right:4px;top:50%;transform:translateY(-50%);padding:2px 6px;font-size:10px;opacity:0.8" onclick="nextPhoto({{.ID}})">&gt;</button>
<button class="btn ghost" style="position:absolute;left:2px;top:50%;transform:translateY(-50%);padding:2px 4px;font-size:10px;opacity:0.7" onclick="prevPhoto({{.ID}})"></button>
<button class="btn ghost" style="position:absolute;right:2px;top:50%;transform:translateY(-50%);padding:2px 4px;font-size:10px;opacity:0.7" onclick="nextPhoto({{.ID}})"></button>
{{end}}
{{else}}
<div class="muted" style="display:flex;align-items:center;justify-content:center;height:100%">无照片</div>
@ -67,6 +67,7 @@
<span style="font-weight:500;font-size:13px">{{.Name}}</span>
<div class="muted small">{{.PhotoCount}} 张照片</div>
</div>
t <form method="post" action="/ui/face-gallery/rename" style="display:inline;margin-right:4px"><input type="hidden" name="id" value="{{.ID}}"><input type="text" name="name" value="{{.Name}}" style="width:60px;font-size:11px;padding:2px 4px"><button class="btn ghost" type="submit" style="font-size:10px;padding:2px 4px">改名</button></form>
<form method="post" action="/ui/face-gallery/delete" style="display:inline" onsubmit="return confirm('确定删除 {{.Name}}')">
<input type="hidden" name="id" value="{{.ID}}">
<button class="btn ghost" type="submit" style="color:var(--danger-soft-text);font-size:11px;padding:2px 6px">删除</button>