diff --git a/admin/handler/web/others.go b/admin/handler/web/others.go index da7b768..b0d0795 100644 --- a/admin/handler/web/others.go +++ b/admin/handler/web/others.go @@ -67,12 +67,13 @@ func (h *Handler) renderSybListWithLoginReason(c *gin.Context, keyword, pageRaw, return } + syncStatus := service.GetSybSyncStatus() status := msg if status == "" { status = sybStatusLine(result) } - if service.GetSybSyncStatus().Running { - status = "同步进行中,请稍后刷新页面查看结果 · " + status + if syncStatus.Running && (msg == "" || strings.Contains(msg, "同步已开始")) { + status = "同步正在进行,请耐心等待;可打开同步记录刷新查看结果" } values := url.Values{} @@ -152,8 +153,11 @@ func (h *Handler) renderSybListWithLoginReason(c *gin.Context, keyword, pageRaw, "SyncDateTo": dateTo, "RangeError": c.Query("range_error"), "RangeWarning": rangeWarning, + "SyncRunning": syncStatus.Running, "SyncHistory": history, "NeedSyncHistory": c.Query("history") == "1", + "HistoryRefreshURL": sybHistoryURL(history.Page, + keyword, dateFrom, dateTo), "HistoryPagination": sybHistoryPagination(history.Page, history.TotalPages, keyword, dateFrom, dateTo), "Pagination": service.NewPaginationView(result.Page, result.TotalPages, values.Encode()), @@ -161,15 +165,7 @@ func (h *Handler) renderSybListWithLoginReason(c *gin.Context, keyword, pageRaw, } func sybHistoryPagination(pageNum, totalPages int, keyword, dateFrom, dateTo string) service.PaginationView { - link := func(target int) string { - values := url.Values{"history": {"1"}, "history_page": {strconv.Itoa(target)}} - if keyword != "" { - values.Set("order_no", keyword) - } - values.Set("date_from", dateFrom) - values.Set("date_to", dateTo) - return "/syb?" + values.Encode() - } + link := func(target int) string { return sybHistoryURL(target, keyword, dateFrom, dateTo) } view := service.PaginationView{ Page: pageNum, TotalPages: totalPages, HasPrev: pageNum > 1, HasNext: pageNum < totalPages, FirstURL: link(1), LastURL: link(totalPages), @@ -183,6 +179,16 @@ func sybHistoryPagination(pageNum, totalPages int, keyword, dateFrom, dateTo str return view } +func sybHistoryURL(pageNum int, keyword, dateFrom, dateTo string) string { + values := url.Values{"history": {"1"}, "history_page": {strconv.Itoa(pageNum)}} + if keyword != "" { + values.Set("order_no", keyword) + } + values.Set("date_from", dateFrom) + values.Set("date_to", dateTo) + return "/syb?" + values.Encode() +} + // sybStatusLine 组装底部状态条的默认文案(没有 msg 覆盖时)。 // `[必须]` 显示筛选后的**全量**总数,不是本页行数,见工单 #43 定下的规则。 func sybStatusLine(result *service.SybListResult) string { @@ -238,7 +244,7 @@ func (h *Handler) SybSync(c *gin.Context) { return } if !started { - h.sybRedirect(c, "验证码自动识别成功,已自动登录;但已经有一个同步任务在跑,请稍后刷新页面查看结果") + h.sybRedirect(c, "同步正在进行,请耐心等待,请勿重复点击") return } h.sybRedirectAfterStart(c, "验证码自动识别成功,已自动登录,同步已开始,请稍后刷新页面查看结果") @@ -251,7 +257,7 @@ func (h *Handler) SybSync(c *gin.Context) { return } if !started { - h.sybRedirect(c, "已经有一个同步任务在跑,请稍后刷新页面查看结果,不要重复点击") + h.sybRedirect(c, "同步正在进行,请耐心等待,请勿重复点击") return } h.sybRedirectAfterStart(c, "同步已开始,请稍后刷新页面查看结果") @@ -363,7 +369,7 @@ func (h *Handler) SybLoginAndSync(c *gin.Context) { return } if !started { - h.sybRedirect(c, "登录成功,但已经有一个同步任务在跑,请稍后刷新页面查看结果") + h.sybRedirect(c, "同步正在进行,请耐心等待,请勿重复点击") return } h.sybRedirectAfterStart(c, "登录成功,同步已开始,请稍后刷新页面查看结果") diff --git a/admin/handler/web/others_test.go b/admin/handler/web/others_test.go index 585693f..69832ff 100644 --- a/admin/handler/web/others_test.go +++ b/admin/handler/web/others_test.go @@ -99,3 +99,19 @@ func TestSybHistoryPagination_保留筛选并重开弹窗(t *testing.T) { } } } + +func TestSybHistoryURL_刷新时保留当前页和筛选(t *testing.T) { + location, err := url.Parse(sybHistoryURL(3, "ORDER-1", "2026-08-01", "2026-08-09")) + if err != nil { + t.Fatal(err) + } + query := location.Query() + for name, want := range map[string]string{ + "history": "1", "history_page": "3", "order_no": "ORDER-1", + "date_from": "2026-08-01", "date_to": "2026-08-09", + } { + if got := query.Get(name); got != want { + t.Fatalf("刷新链接 %s=%q,期望 %q;URL=%s", name, got, want, location.String()) + } + } +} diff --git a/admin/main_test.go b/admin/main_test.go index 60028f6..49303a5 100644 --- a/admin/main_test.go +++ b/admin/main_test.go @@ -67,7 +67,8 @@ func TestMainPagesReturnOK(t *testing.T) { sybResponse := httptest.NewRecorder() router.ServeHTTP(sybResponse, sybRequest) for _, want := range []string{ - "同步记录", `name="date_from"`, `name="date_to"`, + "同步记录", "刷新记录", `data-history-refresh-url=`, + `name="date_from"`, `name="date_to"`, "按顺运宝货运单创建日期(UTC+8)同步", } { if !strings.Contains(sybResponse.Body.String(), want) { @@ -78,6 +79,21 @@ func TestMainPagesReturnOK(t *testing.T) { t.Error("顺运宝页面不应再显示旧的指定日期同步入口") } + if !service.TryStartSybSync() { + t.Fatal("准备同步中页面状态失败") + } + runningRequest := httptest.NewRequest(http.MethodGet, "/syb", nil) + addAuth(runningRequest) + runningResponse := httptest.NewRecorder() + router.ServeHTTP(runningResponse, runningRequest) + service.FinishSybSync(service.SyncReport{}) + if runningResponse.Code != http.StatusOK || + !strings.Contains(runningResponse.Body.String(), "同步中…") || + !strings.Contains(runningResponse.Body.String(), `disabled aria-busy="true"`) || + !strings.Contains(runningResponse.Body.String(), "同步正在进行,请耐心等待") { + t.Fatalf("同步运行时页面应禁用按钮并显示等待提示,响应:%s", runningResponse.Body.String()) + } + pdd, err := repository.EnsurePddProduct( db, "737116531267", "https://mobile.yangkeduo.com/goods.html?goods_id=737116531267") diff --git a/admin/static/css/app.css b/admin/static/css/app.css index 1443589..3052d85 100644 --- a/admin/static/css/app.css +++ b/admin/static/css/app.css @@ -288,7 +288,8 @@ tr.empty small { color: #aaa; } white-space: normal; word-break: break-word; } -.sync-history-foot > span:first-child { color: #555; } +.sync-history-page { color: #555; white-space: nowrap; } +.sync-history-foot { flex-wrap: wrap; } .sync-status { display: inline-block; min-width: 52px; diff --git a/admin/static/js/app.js b/admin/static/js/app.js index 679c7b5..82b9831 100644 --- a/admin/static/js/app.js +++ b/admin/static/js/app.js @@ -251,6 +251,34 @@ if (btn) btn.addEventListener("click", refresh); } + /* 顺运宝同步是后台长任务。表单通过浏览器校验并提交后立即锁住按钮, + 让操作员在页面跳转前就看到反馈;服务端互斥仍是最终保护。 */ + function setupSybSyncFeedback() { + var form = document.querySelector("[data-syb-sync-form]"); + if (!form) return; + var button = form.querySelector("[data-syb-sync-submit]"); + if (!button) return; + form.addEventListener("submit", function () { + button.disabled = true; + button.setAttribute("aria-busy", "true"); + button.textContent = "同步中…"; + }); + } + + /* 同步记录继续由服务端渲染。刷新按钮带着 history=1 和当前页码重新加载, + 返回后弹窗会自动打开;点击后先显示加载态,避免连续刷新。 */ + function setupSybHistoryRefresh() { + var button = document.querySelector("[data-history-refresh-url]"); + if (!button) return; + button.addEventListener("click", function () { + if (button.disabled) return; + button.disabled = true; + button.setAttribute("aria-busy", "true"); + button.textContent = "刷新中…"; + window.location.assign(button.getAttribute("data-history-refresh-url")); + }); + } + document.addEventListener("DOMContentLoaded", function () { document.querySelectorAll("table").forEach(setupCheckAll); setupConfirmDelete(); @@ -260,6 +288,8 @@ setupModals(); setupRowDetail(); setupCaptchaRefresh(); + setupSybSyncFeedback(); + setupSybHistoryRefresh(); syncButtons(); }); diff --git a/admin/templates/syb/list.html b/admin/templates/syb/list.html index 74f3eca..74efa85 100644 --- a/admin/templates/syb/list.html +++ b/admin/templates/syb/list.html @@ -2,7 +2,7 @@ {{template "header" .}}