diff --git a/internal/web/handlers_public.go b/internal/web/handlers_public.go index 36833b1..585dc81 100644 --- a/internal/web/handlers_public.go +++ b/internal/web/handlers_public.go @@ -544,6 +544,11 @@ func (s *Server) placePage(w http.ResponseWriter, r *http.Request) { // 图片:访客也要看得到,所以不分管理员 if imgs, err := s.placeImagesOf(r.Context(), p.Dataset.ID, place.SrcPK); err == nil { d.Images = imgs + // 地址列表单独给一份:访客页没有缩略图墙可读,画廊靠它初始化 + d.ImageURLs = make([]string, 0, len(imgs)) + for _, im := range imgs { + d.ImageURLs = append(d.ImageURLs, im.URL) + } } if p.IsAdmin() { if backend, err := s.imageBackend(r.Context()); err == nil { diff --git a/internal/web/place_image_test.go b/internal/web/place_image_test.go index 4195cc5..fdda16f 100644 --- a/internal/web/place_image_test.go +++ b/internal/web/place_image_test.go @@ -2,6 +2,7 @@ package web import ( "bytes" + "context" "encoding/json" "fmt" "io" @@ -12,6 +13,7 @@ import ( "os" "path" "path/filepath" + "strconv" "strings" "testing" "time" @@ -302,3 +304,50 @@ func TestPlaceImageS3Backend(t *testing.T) { t.Errorf("切到对象存储后不该再写本地磁盘,却发现 %d 个条目", len(entries)) } } + +// TestVisitorPlacePageCarriesImageURLs 盯住「访客在详情页也能看到图片入口」。 +// +// 画廊的地址以前只从 #imgs-grid 反推,而那个缩略图墙是站长专属的 DOM,访客页里 +// 根本没有——于是 syncGallery() 数到 0 张,把服务端已经渲染好的「图片 N」按钮 +// remove 掉(表现成刷新后按钮闪一下就没了)。站长自己打开一切正常,所以只有按访客 +// 身份把页面渲出来才验得到:**访客页必须自带地址列表,不能依赖那一块 DOM**。 +func TestVisitorPlacePageCarriesImageURLs(t *testing.T) { + f := newMovementFixture(t) + + var placeID int64 + var srcPK int + if err := f.pg.QueryRowContext(f.ctx, `SELECT id, src_pk FROM places + WHERE dataset_id=$1 ORDER BY id LIMIT 1`, f.dsID).Scan(&placeID, &srcPK); err != nil { + t.Fatalf("找不到地点: %v", err) + } + const key = "9/visitor-page-test.webp" + // 这里只验发下去的地址,不验能否取回字节,所以不用真的写文件 + if _, err := f.pg.ExecContext(f.ctx, `INSERT INTO place_images + (dataset_id, src_pk, storage, object_key, width, height, bytes, position) + VALUES ($1,$2,'local',$3,1200,900,1024,0) + ON CONFLICT (dataset_id, src_pk, object_key) DO NOTHING`, + f.dsID, srcPK, key); err != nil { + t.Fatalf("插入图片记录失败: %v", err) + } + t.Cleanup(func() { + f.pg.ExecContext(f.ctx, + `DELETE FROM place_images WHERE dataset_id=$1 AND object_key=$2`, f.dsID, key) + }) + + req := httptest.NewRequest("GET", "/places/x", nil) + req = req.WithContext(context.Background()) // 访客 = 上下文里没有登录态 + req.SetPathValue("id", strconv.FormatInt(placeID, 10)) + rec := httptest.NewRecorder() + f.srv.placePage(rec, req) + body := rec.Body.String() + if rec.Code != http.StatusOK { + t.Fatalf("访客访问详情页返回 %d:%s", rec.Code, firstLine(body)) + } + if !strings.Contains(body, `id="imgs-open"`) { + t.Error("访客页面里没有「图片」按钮") + } + want := f.srv.media.PublicURL("local", key) + if !strings.Contains(body, want) { + t.Errorf("访客页面没有把图片地址发给浏览器(缺 %s)", want) + } +} diff --git a/internal/web/templates/place.html b/internal/web/templates/place.html index 398a836..5c8ad55 100644 --- a/internal/web/templates/place.html +++ b/internal/web/templates/place.html @@ -241,20 +241,25 @@ var admin = document.getElementById('imgs-admin'); /* ---------- 全屏画廊 ---------- - * 内容是「缩略图墙的镜像」:传图/删图之后不用刷新页面也能看到最新的, - * 页头那个「图片 N」按钮也一样(服务端渲染时还没有图的话,这里现补一个)。 - * 所以下面统一由 syncGallery() 从 grid 反推,而不是各写一套。 + * 地址有两个来源:站长页有缩略图墙,传图/删图之后就地在墙上读(它才是实时真相); + * 访客页没有那一整块,只能读服务端随页面发下来的这份。页头那个「图片 N」按钮 + * 与画廊共用同一套数据,所以服务端渲染时还没有图的话,这里现补一个。 */ var stage = document.getElementById('imgs-stage'); var viewImg = document.getElementById('imgs-img'); var counter = document.getElementById('imgs-counter'); var prevBtn = document.getElementById('imgs-prev'); var nextBtn = document.getElementById('imgs-next'); + var serverURLs = {{if .ImageURLs}}{{json .ImageURLs}}{{else}}[]{{end}}; var urls = []; var idx = 0; + // 判据必须是「缩略图墙在不在」,而不是「墙里有几张」:站长把图删光时墙还在、只是空了, + // 这时若按「墙是空的」回退到服务端那份,删掉的图就会被数回来。 function cellURLs() { - return [].map.call(document.querySelectorAll('#imgs-grid .imgs-cell img'), function (i) { + var g = document.getElementById('imgs-grid'); + if (!g) return serverURLs; + return [].map.call(g.querySelectorAll('.imgs-cell img'), function (i) { return i.getAttribute('src'); }); } diff --git a/internal/web/view.go b/internal/web/view.go index a2b9d19..64de639 100644 --- a/internal/web/view.go +++ b/internal/web/view.go @@ -547,6 +547,9 @@ type PlaceData struct { RondNote string // Images 是站长给这个地点配的图片(按 position 排序)。 Images []PlaceImage + // ImageURLs 是 Images 的地址列表,随页面一起发给浏览器。 + // 访客页里没有缩略图墙(那一整块是站长专属的),画廊只能拿这份当数据源。 + ImageURLs []string // ImageBackend 是当前生效的图片存储(local / s3),后台那块面板展示用; // ImageReady 为假表示选了对象存储但凭据没配齐,此时不允许上传。 ImageBackend string