From 1a2b6feaf6f4223b7e84d95697c2aa885a0a2d65 Mon Sep 17 00:00:00 2001 From: QiuSW <105186638@qq.com> Date: Tue, 29 Sep 2026 16:52:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=95=86=E5=93=81=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=AF=8F=E9=A1=B520=E8=87=B3500=E6=9D=A1=20(?= =?UTF-8?q?#25)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/02-architecture-and-code-map.md | 12 ++++-- docs/03-business-rules-and-glossary.md | 10 ++++- docs/04-local-development-and-verification.md | 10 ++++- frontend/src/views/ProductListView.vue | 9 ++++ internal/store/product.go | 2 +- internal/store/product_test.go | 43 +++++++++++++++++++ 6 files changed, 78 insertions(+), 8 deletions(-) diff --git a/docs/02-architecture-and-code-map.md b/docs/02-architecture-and-code-map.md index db76bd2..124c296 100644 --- a/docs/02-architecture-and-code-map.md +++ b/docs/02-architecture-and-code-map.md @@ -2,15 +2,21 @@ generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件) wiki_page: Architecture-and-Code-Map wiki_url: https://git.ilapage.cn/chengma/cmsp/wiki/Architecture-and-Code-Map.- -wiki_revision: fc8ac21bda0121896e608c5a61d828ad48d137e9 -synchronized_at: 2026-09-29T08:20:24Z +wiki_revision: c070f41cdae8b495e381dfd571500274697a0677 +synchronized_at: 2026-09-29T08:52:08Z # 架构与代码地图 +## 本地列表分页条数(2026-09-29) + +商品列表 n-pagination 启用 show-size-picker,提供每页20/50/100/200/500条;初始/重置仍20。changePageSize 更新 query.pageSize、清空旧勾选,调用 search 回到第一页,其他筛选条件保留。仅此次页大小切换明确清空勾选,普通搜索/翻页继续原选择行为。 + +store.ListProducts 接受1—500的 pageSize,空/非正数/大于500仍回退20;参数化 LIMIT/OFFSET、排序与同条件 COUNT 保持原样。这里是本地商品列表,与 erpgo 商品同步每页200、任务并发及上传流程无关。选择500增加当前页行和视频摘要读取,不自动勾选或启动任务。 + ## 商品列表下载状态筛选(2026-09-29) -`ProductListView.vue` 的初始/重置查询默认为在售中(NORMAL)、缺少视频(missing)和未下载(not_downloaded)。下载状态下拉复用现有 n-select,提供全部、未下载(含失败)、待下载、下载中、已下载、下载失败;用户点击搜索应用条件,沿用原页码重置/选择清理和筛选栏换行。 +`ProductListView.vue` 的初始/重置查询默认为在售中(NORMAL)、缺少视频(missing)和未下载(not_downloaded)。下载状态下拉复用现有 n-select,提供全部、未下载(含失败)、待下载、下载中、已下载、下载失败;用户点击搜索应用条件,沿用原页码重置和筛选栏换行;普通搜索/翻页保持原有勾选行为。 `store.ProductQuery.DownloadStatus` 的 JSON 名称为 `downloadStatus`,空值保持旧调用查全部下载状态。`buildWhere` 把 not_downloaded 映射到 `download_status IN (pending, failed)`,单状态等值查询,所有值参数化,与其他条件 AND 组合;COUNT 和分页列表使用同一 WHERE。not_downloaded 仅是查询条件,不写入数据库状态。筛选不调用外部接口、不更改 SQLite 表结构或商品/视频/任务/上传状态。 diff --git a/docs/03-business-rules-and-glossary.md b/docs/03-business-rules-and-glossary.md index aa34195..640a8bc 100644 --- a/docs/03-business-rules-and-glossary.md +++ b/docs/03-business-rules-and-glossary.md @@ -2,12 +2,18 @@ generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件) wiki_page: Business-Rules-and-Glossary wiki_url: https://git.ilapage.cn/chengma/cmsp/wiki/Business-Rules-and-Glossary.- -wiki_revision: c9e553508e0ca8989a91d482ffe2680c53e73a1a -synchronized_at: 2026-09-29T08:20:25Z +wiki_revision: 8a8fc50cb1c9163b6819cc834e6511c27ed12bf8 +synchronized_at: 2026-09-29T08:52:08Z # 业务规则与术语 +## 本地商品列表每页条数(2026-09-29) + +分页栏可选20、50、100、200、500条,默认及重置20。切换每页条数后回到第一页、保留店铺/诊断/下载/上传等筛选、清空旧勾选;列表总数仍为全部匹配记录数。500条是显示范围,不表示自动批量处理500条,批量操作仍需勾选。 + +后端本地查询允许1—500条,无效值回退20;空页仍返回匹配总数。分页只读取SQLite,不扫描或改写任务数据,不修改 erpgo 接口分页大小或外部同步规则。 + ## 缺视频且未下载的筛选语义(2026-09-29) 商品列表默认及重置显示在售中、货憨憨诊断缺少视频、且本地未下载的记录。“未下载(含失败)”指 SQLite download_status 为 pending 或 failed;running/done 排除,避免重复处理已下载或正在下载的记录。它按下载状态筛选,不扫描文件存在;已下载但文件被人工删除的记录仍属已下载。 diff --git a/docs/04-local-development-and-verification.md b/docs/04-local-development-and-verification.md index 0d0d554..5ea146e 100644 --- a/docs/04-local-development-and-verification.md +++ b/docs/04-local-development-and-verification.md @@ -2,12 +2,18 @@ generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件) wiki_page: Local-Development-and-Verification wiki_url: https://git.ilapage.cn/chengma/cmsp/wiki/Local-Development-and-Verification.- -wiki_revision: 855eae1f9be9d9d0ebdc77bc2ef09a7f9233d479 -synchronized_at: 2026-09-29T08:20:28Z +wiki_revision: 4219925d9609a8c4c6343744a08af090774fc96f +synchronized_at: 2026-09-29T08:52:09Z # 本地开发与验证 +## 每页500条边界验证(2026-09-29) + +internal/store/product_test.go 使用501条虚构匹配记录和1条不匹配记录,验证20/50/100/200/500五档有效、无效值回退20、500条末页1条、随后空页、分页不重叠与总数一致。前端使用已安装Naive UI分页控件的showSizePicker/pageSizes/onUpdate:pageSize契约;changePageSize 保留筛选、清空勾选并回到第一页。默认/重置为20。 + +执行 go test ./...、go vet ./...、前端构建及 wails build -o cmsp25.exe。构建及数据库测试不代替Windows实际下拉、键盘、Narrator、高对比度/显示缩放或每页500条的真机响应体验验收。 + ## 缺少视频与未下载组合筛选验证(2026-09-29) `internal/store/product_test.go` 的组合筛选测试使用虚构数据和临时 SQLite,覆盖 pending/failed 包含、running/done 排除、全部及单状态、视频诊断/店铺/在售/上传交集、过滤后 COUNT 与分页一致、旧空条件兼容、参数化查询以及查询前后状态不变。未下载只依赖 SQLite 状态,不以文件是否存在判定。 diff --git a/frontend/src/views/ProductListView.vue b/frontend/src/views/ProductListView.vue index 80e3862..17673f2 100644 --- a/frontend/src/views/ProductListView.vue +++ b/frontend/src/views/ProductListView.vue @@ -477,6 +477,12 @@ function reset() { search() } +function changePageSize(size) { + query.value.pageSize = size + checkedIds.value = [] + search() +} + // ---- 操作按钮 ---- async function run(action, fn) { running.value = action @@ -865,7 +871,10 @@ onUnmounted(() => { v-model:page="query.page" :item-count="total" :page-size="query.pageSize" + :page-sizes="[20, 50, 100, 200, 500]" + show-size-picker @update:page="search(false)" + @update:page-size="changePageSize" /> diff --git a/internal/store/product.go b/internal/store/product.go index f8f0770..5c3a0a3 100644 --- a/internal/store/product.go +++ b/internal/store/product.go @@ -190,7 +190,7 @@ func (s *Store) ListProducts(q ProductQuery) (ProductPage, error) { if q.Page < 1 { q.Page = 1 } - if q.PageSize < 1 || q.PageSize > 200 { + if q.PageSize < 1 || q.PageSize > 500 { q.PageSize = 20 } diff --git a/internal/store/product_test.go b/internal/store/product_test.go index 4e962e1..1e37652 100644 --- a/internal/store/product_test.go +++ b/internal/store/product_test.go @@ -2,11 +2,54 @@ package store import ( "database/sql" + "fmt" "path/filepath" "reflect" "testing" ) +func Test商品分页支持五档及500条边界(t *testing.T) { + s := newTestStore(t) + items := make([]Product, 501) + for i := range items { + items[i] = Product{ID: fmt.Sprintf("fictional-%04d", i), VideoDiagnosis: VideoDiagnosisMissing} + } + items = append(items, Product{ID: "fictional-excluded", VideoDiagnosis: VideoDiagnosisOK}) + if err := s.UpsertProducts(items, "fictional-time"); err != nil { + t.Fatal(err) + } + for _, size := range []int{20, 50, 100, 200, 500, 0, -1, 501} { + want := size + if want < 1 || want > 500 { + want = 20 + } + page, err := s.ListProducts(ProductQuery{VideoDiagnosis: VideoDiagnosisMissing, PageSize: size}) + if err != nil || page.Size != want || len(page.Items) != want || page.Total != 501 { + t.Fatalf("size=%d: actualSize=%d len=%d total=%d err=%v", size, page.Size, len(page.Items), page.Total, err) + } + } + query := ProductQuery{VideoDiagnosis: VideoDiagnosisMissing, PageSize: 500} + first, err := s.ListProducts(query) + if err != nil { + t.Fatal(err) + } + query.Page = 2 + last, err := s.ListProducts(query) + if err != nil || last.Total != 501 || last.Size != 500 || len(last.Items) != 1 { + t.Fatalf("last page inconsistent: %+v err=%v", last, err) + } + for _, item := range first.Items { + if item.ID == last.Items[0].ID { + t.Fatal("last page duplicates first page") + } + } + query.Page = 3 + empty, err := s.ListProducts(query) + if err != nil || empty.Total != 501 || empty.Size != 500 || len(empty.Items) != 0 { + t.Fatalf("empty page inconsistent: %+v err=%v", empty, err) + } +} + func Test重置残留运行中状态只影响运行中字段(t *testing.T) { s := newTestStore(t) items := []Product{