feat: 商品列表支持每页20至500条 (#25)
This commit is contained in:
@@ -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
|
||||
<!-- gitea-wiki-mirror:end -->
|
||||
|
||||
# 架构与代码地图
|
||||
|
||||
## 本地列表分页条数(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 表结构或商品/视频/任务/上传状态。
|
||||
|
||||
|
||||
@@ -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
|
||||
<!-- gitea-wiki-mirror:end -->
|
||||
|
||||
# 业务规则与术语
|
||||
|
||||
## 本地商品列表每页条数(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 排除,避免重复处理已下载或正在下载的记录。它按下载状态筛选,不扫描文件存在;已下载但文件被人工删除的记录仍属已下载。
|
||||
|
||||
@@ -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
|
||||
<!-- gitea-wiki-mirror:end -->
|
||||
|
||||
# 本地开发与验证
|
||||
|
||||
## 每页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 状态,不以文件是否存在判定。
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user