From fb2e4c4dab04790cba045895b17f2d410c7a947f Mon Sep 17 00:00:00 2001 From: chengma Date: Mon, 10 Aug 2026 16:33:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8C=BA=E5=88=86=E9=A1=BA=E8=BF=90?= =?UTF-8?q?=E5=AE=9D=E6=A0=87=E9=A2=98=E4=B8=8E=E5=9B=BE=E7=89=87=E4=BA=A4?= =?UTF-8?q?=E4=BA=92=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/static/css/app.css | 43 ++++++++++++++++++++++++ admin/static/js/app.js | 42 ++++++++++++++++++++++++ admin/syb_interaction_template_test.go | 45 ++++++++++++++++++++++++++ admin/templates/syb/list.html | 37 ++++++++++++++++++--- docs/admin/05-ui-specification.md | 9 ++++-- 5 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 admin/syb_interaction_template_test.go diff --git a/admin/static/css/app.css b/admin/static/css/app.css index 0fbd6dc..a9cc127 100644 --- a/admin/static/css/app.css +++ b/admin/static/css/app.css @@ -511,6 +511,49 @@ select { border-radius: 3px; vertical-align: middle; } +.table-title-action, +.thumb-action { + border: 0; + background: transparent; + color: inherit; +} +.table-title-action { + display: block; + max-width: 100%; + padding: 2px 0; + overflow: hidden; + color: #0969da; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; +} +.table-title-action:hover { text-decoration: underline; } +.thumb-action { + padding: 3px; + border-radius: 4px; + line-height: 0; +} +.thumb-action:hover { background: #e8edf3; } +.table-title-action:focus-visible, +.thumb-action:focus-visible { + outline: 2px solid #0969da; + outline-offset: 2px; +} +.image-preview-modal { max-width: min(1120px, calc(100vw - 32px)); } +.image-preview-body { + display: flex; + min-height: 160px; + align-items: center; + justify-content: center; +} +.image-preview-body img { + display: block; + max-width: 100%; + max-height: calc(100vh - 190px); + object-fit: contain; +} +.image-preview-body img[hidden] { display: none; } +.image-preview-status { margin: 0; } /* ── 错误页 ───────────────────────────── */ .error-box { diff --git a/admin/static/js/app.js b/admin/static/js/app.js index f263a59..46404ec 100644 --- a/admin/static/js/app.js +++ b/admin/static/js/app.js @@ -296,6 +296,8 @@ var base = modal.getAttribute("data-detail-url") || ""; function loadDetail(id) { + if (!modal.hidden && modal.getAttribute("data-loaded-detail-id") === id) return; + modal.setAttribute("data-loaded-detail-id", id); slot.innerHTML = ""; openModal(modal); @@ -336,6 +338,45 @@ }); } + /* 顺运宝缩略图使用上游图片 URL。这里只在弹窗里放大展示, + 不代理、不下载图片;加载失败时保留新窗口重试入口。 */ + function setupImagePreview() { + var modal = document.getElementById("image-preview-modal"); + if (!modal) return; + var image = modal.querySelector("[data-image-preview-image]"); + var title = modal.querySelector("[data-image-preview-title]"); + var status = modal.querySelector("[data-image-preview-status]"); + var link = modal.querySelector("[data-image-preview-link]"); + if (!image || !title || !status || !link) return; + + document.querySelectorAll("[data-image-preview-url]").forEach(function (button) { + button.addEventListener("click", function () { + var url = button.getAttribute("data-image-preview-url") || ""; + var productTitle = button.getAttribute("data-image-preview-title") || "商品"; + title.textContent = productTitle + " · 原图"; + link.href = url; + image.alt = productTitle + " 原图"; + image.hidden = true; + status.hidden = false; + status.classList.remove("missing"); + status.textContent = "正在加载原图…"; + openModal(modal); + + image.onload = function () { + status.hidden = true; + image.hidden = false; + }; + image.onerror = function () { + image.hidden = true; + status.hidden = false; + status.classList.add("missing"); + status.textContent = "原图加载失败,请在新窗口打开或稍后重试。"; + }; + image.src = url; + }); + }); + } + /* ── 顺运宝登录弹窗:验证码"换一张" ───────────── 图片本身、以及旁边的"换一张"按钮,点了都重新请求验证码接口。 `[必须]` 每次请求带一个新的时间戳查询参数,绕开浏览器缓存—— @@ -446,6 +487,7 @@ setupUploadFeedback(); setupModals(); setupRowDetail(); + setupImagePreview(); setupCaptchaRefresh(); setupSybSyncFeedback(); setupSybHistoryRefresh(); diff --git a/admin/syb_interaction_template_test.go b/admin/syb_interaction_template_test.go new file mode 100644 index 0000000..a98fa6b --- /dev/null +++ b/admin/syb_interaction_template_test.go @@ -0,0 +1,45 @@ +package main + +import ( + "bytes" + "html/template" + "strings" + "testing" + + "cmautobuy/admin/service" +) + +func TestSybListTemplate_区分标题图片与阶段动作(t *testing.T) { + tmpl, err := template.ParseFS(templateFS, "templates/*/*.html") + if err != nil { + t.Fatal(err) + } + rows := []service.SybOrderView{{ + SybID: "SYB-READY", OrderNo: "ORDER-READY", Title: "可采购商品", + ImageURL: "https://example.test/original.jpg", Stage: service.SybStagePurchaseReady, + StageText: "可创建采购任务", StageHelp: "规格映射有效", ActionText: "核对规格", + CanPurchase: true, DefaultMaxPrice: "39.90", + }} + var output bytes.Buffer + data := map[string]any{ + "Title": "顺运宝数据", "Active": "syb", "CSRFToken": "test-csrf", + "Rows": rows, "DetailURL": "/syb/detail", "StageOptions": service.SybStageOptions(), + } + if err := tmpl.ExecuteTemplate(&output, "syb/list", data); err != nil { + t.Fatalf("渲染顺运宝列表失败: %v", err) + } + body := output.String() + for _, want := range []string{ + `class="table-title-action" data-detail-open-id="SYB-READY"`, + `data-image-preview-url="https://example.test/original.jpg"`, + `aria-label="查看 可采购商品 的原图"`, + `id="image-preview-modal"`, + } { + if !strings.Contains(body, want) { + t.Errorf("顺运宝列表缺少 %q", want) + } + } + if strings.Contains(body, ` {{range .Rows}} - + {{.SybID}} {{.OrderNo}} - {{.Title}} + + + {{.ProductSpec}} {{.ShopeeGoodsID}} {{.Quantity}} {{.PriceText}} - {{if .ImageURL}}{{else}}—{{end}} + {{if .ImageURL}} + + {{else}}—{{end}} {{.StageText}}
{{.StageHelp}} @@ -201,9 +211,28 @@

- 每行只显示当前最需要完成的下一步;双击行或点击“下一步”按钮可以打开处理详情。 + 每行只显示当前最需要完成的下一步;点击商品标题进入处理详情,点击图片查看原图。

+ +