fix: 完善顺运宝列表紧凑布局 (#216)
This commit is contained in:
+17
-3
@@ -336,7 +336,7 @@ func TestSybPage_紧凑筛选和可见横向滚动(t *testing.T) {
|
||||
`class="table-wrap syb-table-wrap"`,
|
||||
`class="syb-col-shop"`,
|
||||
`class="syb-col-spec"`,
|
||||
`colspan="12"`,
|
||||
`colspan="11"`,
|
||||
} {
|
||||
if !strings.Contains(pageText, want) {
|
||||
t.Errorf("顺运宝紧凑列表缺少 %q", want)
|
||||
@@ -345,6 +345,8 @@ func TestSybPage_紧凑筛选和可见横向滚动(t *testing.T) {
|
||||
for _, removed := range []string{
|
||||
`href="/shops">店铺管理(同步`,
|
||||
`<th>货运单 ID</th>`,
|
||||
`<th>处理阶段</th>`,
|
||||
`按顺运宝货运单创建日期(UTC+8)同步`,
|
||||
`每行只显示当前最需要完成的下一步`,
|
||||
} {
|
||||
if strings.Contains(pageText, removed) {
|
||||
@@ -359,10 +361,15 @@ func TestSybPage_紧凑筛选和可见横向滚动(t *testing.T) {
|
||||
`.syb-col-spec`,
|
||||
`max-width: 140px`,
|
||||
`.syb-table-wrap`,
|
||||
`max-height: calc(100vh - 270px)`,
|
||||
`flex: 1 1 auto`,
|
||||
`min-height: 0`,
|
||||
`max-height: none`,
|
||||
`overflow: auto`,
|
||||
`.syb-table-wrap thead th`,
|
||||
`position: sticky`,
|
||||
`body.syb-page-body`,
|
||||
`height: 100dvh`,
|
||||
`body.syb-page-body > .page`,
|
||||
} {
|
||||
if !strings.Contains(string(css), want) {
|
||||
t.Errorf("顺运宝紧凑列表样式缺少 %q", want)
|
||||
@@ -371,6 +378,9 @@ func TestSybPage_紧凑筛选和可见横向滚动(t *testing.T) {
|
||||
if !strings.Contains(string(header), `href="/shops"`) {
|
||||
t.Error("移除顺运宝工具条入口后,管理员主导航仍应保留店铺管理模块")
|
||||
}
|
||||
if !strings.Contains(string(header), `class="syb-page-body"`) {
|
||||
t.Error("顺运宝页面缺少专用视口布局类")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShops_单一名称和状态且保护删除(t *testing.T) {
|
||||
@@ -610,7 +620,6 @@ func TestMainPagesReturnOK(t *testing.T) {
|
||||
`data-history-fetch-url=`,
|
||||
`name="date_from"`, `name="date_to"`,
|
||||
`name="stage"`, "处理阶段", `name="shop"`, "店铺", "下一步", `data-detail-url=`,
|
||||
"按顺运宝货运单创建日期(UTC+8)同步",
|
||||
"创建真实采购任务", "所有当前账号可见的客户端均可指派",
|
||||
} {
|
||||
if !strings.Contains(sybResponse.Body.String(), want) {
|
||||
@@ -630,6 +639,11 @@ func TestMainPagesReturnOK(t *testing.T) {
|
||||
if strings.Contains(sybResponse.Body.String(), "指定日期同步") {
|
||||
t.Error("顺运宝页面不应再显示旧的指定日期同步入口")
|
||||
}
|
||||
for _, removed := range []string{"按顺运宝货运单创建日期(UTC+8)同步", `<th>处理阶段</th>`} {
|
||||
if strings.Contains(sybResponse.Body.String(), removed) {
|
||||
t.Errorf("顺运宝页面仍包含已移除内容 %q", removed)
|
||||
}
|
||||
}
|
||||
for _, removed := range []string{`name="execution_mode"`, "采购演练", "仅演练", "支持真实下单",
|
||||
"真实下单安全确认", `name="live_acknowledged"`, `name="live_confirmation"`, `data-confirm-submit=`} {
|
||||
if strings.Contains(sybResponse.Body.String(), removed) {
|
||||
|
||||
@@ -1133,11 +1133,11 @@ func sybStageFor(c repository.SybOrderContext) (stage, text, help, action string
|
||||
case c.HasSucceededPurchaseTask:
|
||||
return SybStagePurchaseCompleted, "采购完成", "采购任务已成功完成,请到采集采购页面查看 PDD 订单编号和下单时间。", "查看采购结果"
|
||||
case c.HasManualReviewPurchaseTask:
|
||||
return SybStagePurchaseReview, "采购待人工核对", "任务可能已经下单,必须先核对订单,禁止重新创建采购任务。", "查看采购任务"
|
||||
return SybStagePurchaseReview, "采购待人工核对", "任务可能已经下单,必须先核对订单,禁止重新创建采购任务。", "核对采购订单"
|
||||
case c.HasActiveTask:
|
||||
return SybStageTaskCreated, "已创建采购任务", "已有未结束的采购任务,请到采集采购页面查看。", "查看采购任务"
|
||||
case c.Order.SpecKey == "":
|
||||
return SybStageSpecMissing, "采购数据异常:顺运宝未提供规格", "缺少规格原文,不能匹配或创建采购任务。", "核对数据"
|
||||
return SybStageSpecMissing, "采购数据异常:顺运宝未提供规格", "缺少规格原文,不能匹配或创建采购任务。", "核对缺失规格"
|
||||
case c.PddGoodsID == "" || c.PddCollectStatus == "":
|
||||
return SybStagePddMissing, "未关联 PDD", "下一步关联采购商品。", "关联 PDD"
|
||||
case c.PddCollectStatus == string(model.CollectPending):
|
||||
@@ -1155,7 +1155,7 @@ func sybStageFor(c repository.SybOrderContext) (stage, text, help, action string
|
||||
case !mappingIsValid(c):
|
||||
return SybStageMappingPending, "规格待匹配", "PDD 数据已采集,下一步匹配采购规格。", "匹配规格"
|
||||
case c.Order.Quantity <= 0:
|
||||
return SybStagePurchaseBlocked, "采购数据异常", "顺运宝采购数量不是正数,不能创建采购任务。", "核对数据"
|
||||
return SybStagePurchaseBlocked, "采购数据异常", "顺运宝采购数量不是正数,不能创建采购任务。", "核对采购数量"
|
||||
default:
|
||||
return SybStagePurchaseReady, "可创建采购任务", "规格映射有效,可以创建采购任务。", "创建采购任务"
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ func TestSybStageFor_全部筛选阶段与逐行推导一致(t *testing.T) {
|
||||
action string
|
||||
context repository.SybOrderContext
|
||||
}{
|
||||
{SybStageSpecMissing, "核对数据", copyWith(func(c *repository.SybOrderContext) { c.Order.SpecKey = "" })},
|
||||
{SybStageSpecMissing, "核对缺失规格", copyWith(func(c *repository.SybOrderContext) { c.Order.SpecKey = "" })},
|
||||
{SybStagePddMissing, "关联 PDD", copyWith(func(c *repository.SybOrderContext) { c.PddGoodsID, c.PddCollectStatus = "", "" })},
|
||||
{SybStagePddPending, "创建采集任务", copyWith(func(c *repository.SybOrderContext) { c.PddCollectStatus = string(model.CollectPending) })},
|
||||
{SybStagePddCollecting, "查看采集状态", copyWith(func(c *repository.SybOrderContext) {
|
||||
@@ -129,11 +129,11 @@ func TestSybStageFor_全部筛选阶段与逐行推导一致(t *testing.T) {
|
||||
c.HasSucceededPurchaseTask, c.HasManualReviewPurchaseTask = true, true
|
||||
c.Order.SpecKey = ""
|
||||
})},
|
||||
{SybStagePurchaseReview, "查看采购任务", copyWith(func(c *repository.SybOrderContext) {
|
||||
{SybStagePurchaseReview, "核对采购订单", copyWith(func(c *repository.SybOrderContext) {
|
||||
c.HasManualReviewPurchaseTask = true
|
||||
c.MappingOptionKey = ""
|
||||
})},
|
||||
{SybStagePurchaseBlocked, "核对数据", copyWith(func(c *repository.SybOrderContext) { c.Order.Quantity = 0 })},
|
||||
{SybStagePurchaseBlocked, "核对采购数量", copyWith(func(c *repository.SybOrderContext) { c.Order.Quantity = 0 })},
|
||||
{SybStagePurchaseReady, "创建采购任务", ready},
|
||||
}
|
||||
if got := len(SybStageOptions()) - 1; got != len(cases) {
|
||||
|
||||
@@ -79,6 +79,23 @@ body {
|
||||
/* ── 页面主体 ─────────────────────────── */
|
||||
.page { padding: 16px; }
|
||||
|
||||
/* 顺运宝是高密度操作表:桌面端让主区域占满导航和固定状态栏之间的空间,
|
||||
工具条或条件提示高度变化时,表格自动取得剩余高度,不依赖固定像素扣减。 */
|
||||
body.syb-page-body {
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
body.syb-page-body > .nav { flex: 0 0 auto; }
|
||||
body.syb-page-body > .page {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ── 第一段:顶部工具条 ───────────────── */
|
||||
.toolbar {
|
||||
display: flex;
|
||||
@@ -256,7 +273,9 @@ tr.empty small { color: #aaa; }
|
||||
max-width: 140px;
|
||||
}
|
||||
.syb-table-wrap {
|
||||
max-height: calc(100vh - 270px);
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
max-height: none;
|
||||
overflow: auto;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
@@ -266,6 +285,13 @@ tr.empty small { color: #aaa; }
|
||||
z-index: 1;
|
||||
}
|
||||
@media (max-width: 760px) {
|
||||
body.syb-page-body {
|
||||
height: auto;
|
||||
min-height: 100dvh;
|
||||
display: block;
|
||||
overflow: auto;
|
||||
}
|
||||
body.syb-page-body > .page { display: block; }
|
||||
.syb-table-wrap { max-height: none; }
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ func TestSybListTemplate_区分标题图片与阶段动作(t *testing.T) {
|
||||
{
|
||||
SybID: "SYB-REVIEW", OrderNo: "ORDER-REVIEW", Title: "待核对商品",
|
||||
Stage: service.SybStagePurchaseReview, StageText: "采购待人工核对",
|
||||
StageHelp: "禁止重新采购", ActionText: "查看采购任务",
|
||||
StageHelp: "禁止重新采购", ActionText: "核对采购订单",
|
||||
},
|
||||
{
|
||||
SybID: "SYB-PDD", OrderNo: "ORDER-PDD", Title: "待关联商品",
|
||||
@@ -90,10 +90,13 @@ func TestSybListTemplate_区分标题图片与阶段动作(t *testing.T) {
|
||||
`href="/syb?date_from=2026-08-12`,
|
||||
`date_to=2026-08-13">清除</a>`,
|
||||
`class="table-wrap syb-table-wrap"`,
|
||||
`class="syb-page-body"`,
|
||||
`<th class="syb-col-shop">店铺</th>`,
|
||||
`<th class="syb-col-spec">规格</th>`,
|
||||
`value="SYB-READY" name="ids"`,
|
||||
`同一 PDD 商品关联多条货运单时只创建一条任务`,
|
||||
`>核对采购订单</a>`,
|
||||
`title="禁止重新采购"`,
|
||||
`title="当前阶段不能批量操作:下一步关联采购商品"`,
|
||||
} {
|
||||
if !strings.Contains(body, want) {
|
||||
@@ -105,10 +108,12 @@ func TestSybListTemplate_区分标题图片与阶段动作(t *testing.T) {
|
||||
}
|
||||
for _, removed := range []string{
|
||||
`<th>货运单 ID</th>`,
|
||||
`<th>处理阶段</th>`,
|
||||
`<td>SYB-READY</td>`,
|
||||
`>创建 PDD 采集任务</button>`,
|
||||
`<h2 id="syb-collect-modal-title">创建 PDD 采集任务</h2>`,
|
||||
`每行只显示当前最需要完成的下一步`,
|
||||
`按顺运宝货运单创建日期(UTC+8)同步`,
|
||||
} {
|
||||
if strings.Contains(body, removed) {
|
||||
t.Errorf("顺运宝列表仍包含已移除内容 %q", removed)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<title>{{.Title}} · 采集采购管理端</title>
|
||||
<link rel="stylesheet" href="/static/css/app.css">
|
||||
</head>
|
||||
<body>
|
||||
<body{{if eq .Active "syb"}} class="syb-page-body"{{end}}>
|
||||
|
||||
<nav class="nav" data-module-navigation data-ui-state-user="{{if .CurrentUser}}{{.CurrentUser.UserID}}{{end}}">
|
||||
<span class="nav-brand">采集采购管理端</span>
|
||||
|
||||
@@ -64,9 +64,6 @@
|
||||
|
||||
{{if .RangeError}}<p class="missing sync-range-message" role="alert">{{.RangeError}}</p>{{end}}
|
||||
{{if .RangeWarning}}<p class="hint sync-range-message">{{.RangeWarning}}</p>{{end}}
|
||||
<p class="hint sync-range-message">
|
||||
按顺运宝货运单创建日期(UTC+8)同步,开始日和结束日都包含,每次最多 31 天;当前在店铺管理中启用 {{.EnabledSyncShopCount}} 个 SYB 同步店铺。
|
||||
</p>
|
||||
|
||||
<div class="modal-backdrop" id="sync-history-modal" {{if not .NeedSyncHistory}}hidden{{end}}>
|
||||
<div class="modal modal-wide" role="dialog" aria-modal="true"
|
||||
@@ -109,7 +106,6 @@
|
||||
<th>数量</th>
|
||||
<th>价格</th>
|
||||
<th>图片</th>
|
||||
<th>处理阶段</th>
|
||||
<th>下一步</th>
|
||||
<th>更新时间</th>
|
||||
</tr>
|
||||
@@ -146,22 +142,23 @@
|
||||
</button>
|
||||
{{else}}—{{end}}
|
||||
</td>
|
||||
<td><span class="status-pill">{{.StageText}}</span><br><small>{{.StageHelp}}</small>{{if .PddAssociationText}}<br><small class="hint">{{.PddAssociationText}}</small>{{end}}</td>
|
||||
<td>
|
||||
{{if eq .Stage "purchase_ready"}}
|
||||
<button type="button" data-modal-open="purchase-modal" data-purchase-open
|
||||
data-purchase-id="{{.SybID}}">{{.ActionText}}</button>
|
||||
data-purchase-id="{{.SybID}}" title="{{.StageHelp}}">{{.ActionText}}</button>
|
||||
{{else if or (eq .Stage "task_created") (eq .Stage "purchase_completed") (eq .Stage "purchase_review")}}
|
||||
<a class="button-link" href="/tasks?type=purchase&q={{urlquery .OrderNo}}">{{.ActionText}}</a>
|
||||
<a class="button-link" href="/tasks?type=purchase&q={{urlquery .OrderNo}}"
|
||||
title="{{.StageHelp}}">{{.ActionText}}</a>
|
||||
{{else}}
|
||||
<button type="button" data-detail-open-id="{{.SybID}}">{{.ActionText}}</button>
|
||||
<button type="button" data-detail-open-id="{{.SybID}}"
|
||||
title="{{.StageHelp}}">{{.ActionText}}</button>
|
||||
{{end}}
|
||||
</td>
|
||||
<td>{{.UpdatedAt}}</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr class="empty">
|
||||
<td colspan="12">
|
||||
<td colspan="11">
|
||||
{{if .IsFiltered}}
|
||||
当前筛选条件下没有货运单明细。<br>
|
||||
<small>换个店铺、订单号或清空筛选条件再试。<a href="/syb">查看全部</a></small>
|
||||
|
||||
@@ -226,14 +226,16 @@ PDD 商品之所以单独一个模块,是因为它在数据上就是**独立
|
||||
| 数量 | |
|
||||
| 价格 | 蝦皮售价,**台币分**,见 §7 |
|
||||
| 图片 | 存 URL,表格里显示缩略图 |
|
||||
| 处理阶段 | 采购数据异常(顺运宝未提供规格)/ 未关联 PDD / 待采集 / 采集中 / 采集失败 / 规格待匹配 / 可采购 / 已创建任务 |
|
||||
| 下一步 | 每行只显示当前最需要完成的一个操作 |
|
||||
| 更新时间 | |
|
||||
|
||||
原始的完整货运单 JSON 存 `syb_data` 字段,**不作为表格列显示**,在详情里看。
|
||||
货运单明细 ID 继续作为勾选、详情和任务关联使用的内部标识,但不单独显示为列表列。
|
||||
桌面端列表在固定高度区域内滚动并固定表头,让横向滚动条在浏览长列表时可到达;
|
||||
窄窗口使用自然页面高度。清除筛选时保留当前同步日期范围。
|
||||
处理阶段仍按实时业务事实推导,并用于顶部筛选、批量选择门禁、下一步路由和详情说明,
|
||||
但不作为独立列表列显示。待人工核对、缺少规格和数量异常必须分别显示“核对采购订单”、
|
||||
“核对缺失规格”和“核对采购数量”,避免隐藏阶段列后产生歧义。
|
||||
桌面端列表使用剩余视口高度并固定表头,让横向滚动条在浏览长列表时可到达;工具条或
|
||||
条件提示高度变化时列表自动伸缩。窄窗口使用自然页面高度。清除筛选时保留当前同步日期范围。
|
||||
|
||||
**双击行或点击下一步打开处理弹窗:**
|
||||
|
||||
|
||||
@@ -481,8 +481,9 @@ Go 的 map 是无序的,不靠它定顺序的话,同一个商品每次刷新
|
||||
|
||||
日期始终显示在主工具条,按 UTC+8 解释且两端都包含。首次打开页面固定默认昨天
|
||||
到今天,不因覆盖游标位置自动扩大范围;需要补历史缺口时由采购员明确选择日期。
|
||||
所有账号都能看到当前启用 SYB 同步店铺数量;管理员通过主导航进入“店铺管理”,
|
||||
顺运宝工具条不再重复放入口。
|
||||
同步日期规则和当前启用店铺数量不再使用常驻说明占用列表空间;日期错误、同步警告、
|
||||
配置问题和登录提示仍按条件显示。管理员通过主导航进入“店铺管理”,顺运宝工具条
|
||||
不再重复放入口。
|
||||
管理页一行一个店铺,只展示和编辑一个店铺名称,并使用一个启停状态。该名称同时用于
|
||||
SYB 同步准入和蝦皮商品关联。启用店铺不显示删除入口,服务端还要拒绝删除有关联货运单
|
||||
或蝦皮商品的停用店铺;“未登记商品 N 个”链接进入蝦皮未关联数据视图。
|
||||
@@ -541,7 +542,7 @@ HTML 片段,只替换弹窗内部的表格、总数和分页。主货运单表
|
||||
### 6.2 表格列
|
||||
|
||||
☐ / 订单号 / 店铺 / 商品标题 / 规格 / 蝦皮商品ID / 数量 /
|
||||
价格(台币)/ 图片 / **处理阶段** / **下一步** / 更新时间
|
||||
价格(台币)/ 图片 / **下一步** / 更新时间
|
||||
|
||||
- 一行对应顺运宝一张货运单的**一个商品明细**,不是一张货运单——一张货运单
|
||||
可以有多个商品,各占一行。
|
||||
@@ -551,22 +552,26 @@ HTML 片段,只替换弹窗内部的表格、总数和分页。主货运单表
|
||||
省略显示并通过单元格 `title` 查看全文。
|
||||
- 图片显示小缩略图。点击缩略图在弹窗中按比例查看顺运宝原图,并提供新窗口打开入口;
|
||||
图片加载失败时显示可读提示。`[必须]` 只存 URL,不代理、缓存或把图片塞进数据库。
|
||||
- 处理阶段是按当前关联事实**算出来的**,不是存一份容易过期的状态字段。阶段至少区分:
|
||||
- 处理阶段不作为独立列表列显示,但顶部筛选、勾选门禁、下一步路由和详情说明继续使用。
|
||||
阶段按当前关联事实**算出来**,不是存一份容易过期的状态字段。阶段至少区分:
|
||||
采购数据异常(顺运宝未提供规格)、未关联 PDD、PDD 待采集、采集中、采集中(超时)、
|
||||
采集失败、规格待匹配、可采购、已创建任务、采购完成和采购待人工核对。
|
||||
- 成功采购任务显示“采购完成”和“查看采购结果”;`manual_review` 显示“采购待人工核对”
|
||||
和“查看采购任务”,并明确提示可能已经下单、必须先核对。两种状态都不允许勾选或重新采购。
|
||||
- 成功采购任务的下一步显示“查看采购结果”;`manual_review` 显示“核对采购订单”,
|
||||
并在详情中明确提示可能已经下单、必须先核对。两种状态都不允许勾选或重新采购。
|
||||
- `collecting` 已超过 15 分钟,或已找不到 `pending/assigned/claimed` 采集任务时,
|
||||
显示“PDD 采集中(超时)”和“重新创建采集任务”,不能继续伪装成正常进行中。
|
||||
- 每行显示一个有文字的“下一步”按钮。商品标题是服务端渲染处理弹窗的明确入口,
|
||||
- 每行显示一个有文字的“下一步”按钮。缺少规格、数量异常、待人工核对分别使用
|
||||
“核对缺失规格”“核对采购数量”“核对采购订单”,不能使用含义相同的笼统文案。
|
||||
按钮的 `title` 保留当前阶段原因。商品标题是服务端渲染处理弹窗的明确入口,
|
||||
支持鼠标和键盘;双击其他普通单元格不执行动作。图片入口只负责查看原图,不能打开处理详情。
|
||||
- “下一步”随实时阶段变化:未关联 PDD、待采集、采集中、采集失败、规格待匹配、
|
||||
可创建采购任务、已创建采购任务和异常阶段分别显示对应动作。除“创建采购任务”和
|
||||
“查看采购任务”外,其余动作进入当前明细的处理弹窗。
|
||||
- 规格原文为空时不显示匹配表单,明确提示“顺运宝未提供规格”,并禁止建立映射或创建采购任务。
|
||||
- 完整货运单 JSON 不作为列显示,落在 `syb_data` 里,供后续排查用。
|
||||
- 桌面端表格使用有最大高度的内部滚动区,表头固定;横向滚动条位于该区域底部,
|
||||
浏览长列表时不必先滚到整页末尾。窄窗口回到自然页面高度,避免可用区域过小。
|
||||
- 桌面端顺运宝页面使用纵向 Flex 布局:导航、工具条和条件提示占实际高度,表格取得
|
||||
到固定底部状态栏之间的全部剩余空间。表格内部纵横滚动并固定表头,横向滚动条位于
|
||||
该区域底部。窄窗口解除视口高度锁定,回到自然页面滚动。
|
||||
|
||||
复选框按批量动作能力启用,不再等同于“可采购”:PDD 待采集、PDD 采集失败、
|
||||
PDD 采集中(超时)行可用于
|
||||
|
||||
Reference in New Issue
Block a user