feat: real photo delete from DB and disk, pass photo IDs to edit modal

This commit is contained in:
tian 2026-05-08 12:29:10 +08:00
parent 0751adb564
commit cb1ce4deaa
3 changed files with 32 additions and 4 deletions

View File

@ -3,6 +3,8 @@ package storage
import (
"database/sql"
"fmt"
"os"
"path/filepath"
"time"
)
@ -11,6 +13,7 @@ type PersonRecord struct {
Name string `json:"name"`
PhotoCount int `json:"photo_count"`
Photos []string `json:"photos,omitempty"`
PhotoIDs []int `json:"photo_ids,omitempty"`
CreatedAt string `json:"created_at"`
}
@ -71,14 +74,16 @@ func (r *FaceGalleryRepo) ListPersons() ([]PersonRecord, error) {
}
// Load photos for each person
for i := range out {
photoRows, err := db.Query(`SELECT photo_path FROM face_photos WHERE person_id = ? ORDER BY id`, out[i].ID)
photoRows, err := db.Query(`SELECT photo_path, id FROM face_photos WHERE person_id = ? ORDER BY id`, out[i].ID)
if err != nil {
continue
}
for photoRows.Next() {
var path string
if photoRows.Scan(&path) == nil {
var pid int
if photoRows.Scan(&path, &pid) == nil {
out[i].Photos = append(out[i].Photos, path)
out[i].PhotoIDs = append(out[i].PhotoIDs, pid)
}
}
photoRows.Close()
@ -156,3 +161,17 @@ func (r *FaceGalleryRepo) RenamePerson(id int, name string) error {
_, err = db.Exec(`UPDATE face_persons SET name = ?, updated_at = ? WHERE id = ?`, name, now, id)
return err
}
func (r *FaceGalleryRepo) DeletePhoto(photoID int) error {
db, err := r.open()
if err != nil { return err }
defer db.Close()
// Get path before deleting
var path string
db.QueryRow(`SELECT photo_path FROM face_photos WHERE id = ?`, photoID).Scan(&path)
_, err = db.Exec(`DELETE FROM face_photos WHERE id = ?`, photoID)
if err != nil { return err }
// Remove file from disk
if path != "" { os.Remove(filepath.Join("dataset", path)) }
return nil
}

View File

@ -672,6 +672,7 @@ func (u *UI) Routes() (chi.Router, error) {
r.Post("/face-gallery/delete", u.actionFaceGalleryDelete)
r.Post("/face-gallery/rename", u.actionFaceGalleryRename)
r.Post("/face-gallery/add-photo", u.actionFaceGalleryAddPhoto)
r.Post("/face-gallery/delete-photo", u.actionFaceGalleryDeletePhoto)
r.Get("/monitor", u.pageMonitor)
r.Get("/hls/*", u.proxyHLS)
r.Get("/api/monitor/channels", u.apiMonitorChannels)
@ -4175,3 +4176,10 @@ func (u *UI) actionFaceGalleryAddPhoto(w http.ResponseWriter, r *http.Request) {
repo.AddPhoto(id, personName+"/"+hdr.Filename)
http.Redirect(w, r, "/ui/face-gallery", http.StatusFound)
}
func (u *UI) actionFaceGalleryDeletePhoto(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.Atoi(r.FormValue("id"))
if id > 0 && strings.TrimSpace(u.dbPath) != "" {
storage.NewFaceGalleryRepo(u.dbPath).DeletePhoto(id)
}
http.Redirect(w, r, "/ui/face-gallery", http.StatusFound)
}

View File

@ -35,7 +35,7 @@
<div style="margin-top:6px;display:flex;justify-content:space-between;align-items:center">
<div><span style="font-weight:500;font-size:13px">{{.Name}}</span> <span class="muted small">{{.PhotoCount}}张</span></div>
<div class="actions compact" style="gap:2px">
<span class="btn-icon ghost edit-btn" title="编辑" data-id="{{.ID}}" data-name="{{.Name}}" data-photos='[{{range $i,$p := .Photos}}{{if $i}},{{end}}"/ui/face-photo/{{$p}}"{{end}}]'>&#9998;</span>
<span class="btn-icon ghost edit-btn" title="编辑" data-id="{{.ID}}" data-name="{{.Name}}" data-photos='[{{range $i,$p := .Photos}}{{if $i}},{{end}}{"p":"/ui/face-photo/{{$p}}","id":{{index $.PhotoIDs $i}}}{{end}}]'>&#9998;</span>
<form method="post" action="/ui/face-gallery/delete" style="display:inline" onsubmit="return confirm('确定删除 {{.Name}}')">
<input type="hidden" name="id" value="{{.ID}}">
<span class="btn-icon ghost" style="color:var(--danger-soft-text)" title="删除" onclick="this.closest('form').requestSubmit()">&#10005;</span>
@ -79,7 +79,8 @@ function showEdit(id,name,photos){
h+='<button class="btn" type="submit" style="margin-bottom:1px">保存</button></form>';
h+='<div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px">';
photos.forEach(function(p){
h+='<div style="position:relative;width:80px;height:100px"><img src="'+p+'" style="width:100%;height:100%;object-fit:cover;border-radius:4px"><span class="btn-icon ghost" style="position:absolute;top:2px;right:2px;font-size:10px;color:red;background:rgba(0,0,0,0.5)" onclick="this.parentElement.remove()">&times;</span></div>';
h+='<div style="position:relative;width:80px;height:100px"><img src="'+p.p+'" style="width:100%;height:100%;object-fit:cover;border-radius:4px">';
h+='<form method="post" action="/ui/face-gallery/delete-photo" style="position:absolute;top:2px;right:2px"><input type="hidden" name="id" value="'+p.id+'"><span class="btn-icon ghost" style="font-size:10px;color:red;background:rgba(0,0,0,0.5)" onclick="this.closest(\'form\').submit()">&times;</span></form></div>';
});
h+='</div>';
h+='<form method="post" action="/ui/face-gallery/add-photo" enctype="multipart/form-data" style="display:flex;gap:8px;align-items:flex-end">';