修复访客在地点详情页看不到「图片」按钮
全屏画廊的地址列表原先只从 #imgs-grid 反推,而那个缩略图墙整块包在
{{if .IsAdmin}} 里——访客页根本没有这个元素,于是 syncGallery() 数到 0 张,
把服务端已经渲染好的「图片 N」按钮 remove 掉(表现成刷新后按钮闪一下就没了)。
站长自己打开一切正常,所以只有按访客身份渲染才看得到。
首版 b327bc1 就有,不是新引入的。
- 新增 PlaceData.ImageURLs:随页面把地址列表发给浏览器,访客页自带数据源。
- cellURLs() 的判据改成「缩略图墙在不在」,而不是「墙里有几张」:
站长把图删光时墙还在、只是空了,按张数回退会把删掉的图又数回来。
- 新增回归 TestVisitorPlacePageCarriesImageURLs:按访客身份渲染详情页,
要求页面里有「图片」按钮、且把图片地址发给了浏览器(去掉数据源即失败)。
实测(390 访客,/places/2254 三张图):按钮回归、点开 1/3 → 2/3 → 回绕 3/3 →
关闭正常,第 2 张 1200×900 实际加载;320/390/430 横向溢出均为 0。
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -547,6 +547,9 @@ type PlaceData struct {
|
||||
RondNote string
|
||||
// Images 是站长给这个地点配的图片(按 position 排序)。
|
||||
Images []PlaceImage
|
||||
// ImageURLs 是 Images 的地址列表,随页面一起发给浏览器。
|
||||
// 访客页里没有缩略图墙(那一整块是站长专属的),画廊只能拿这份当数据源。
|
||||
ImageURLs []string
|
||||
// ImageBackend 是当前生效的图片存储(local / s3),后台那块面板展示用;
|
||||
// ImageReady 为假表示选了对象存储但凭据没配齐,此时不允许上传。
|
||||
ImageBackend string
|
||||
|
||||
Reference in New Issue
Block a user