diff --git a/server/app/goauto/sybimport/handler.go b/server/app/goauto/sybimport/handler.go
index 7d0b823..2a8f417 100644
--- a/server/app/goauto/sybimport/handler.go
+++ b/server/app/goauto/sybimport/handler.go
@@ -47,7 +47,7 @@ func (handler Handler) List(c *gin.Context) {
return
}
response, err := service.List(c.Request.Context(), ListRequest{
- Page: page, PageSize: pageSize, ShopName: c.Query("shopName"), OrderCodes: []string{c.Query("orderCodes")}, ParseStatus: strings.TrimSpace(c.Query("parseStatus")), ProcessStage: strings.TrimSpace(c.Query("processStage")),
+ Page: page, PageSize: pageSize, ShopName: c.Query("shopName"), OrderCodes: []string{c.Query("orderCodes")}, ParseStatus: strings.TrimSpace(c.Query("parseStatus")), ProcessStage: strings.TrimSpace(c.Query("processStage")), CreatedFrom: strings.TrimSpace(c.Query("createdFrom")), CreatedTo: strings.TrimSpace(c.Query("createdTo")),
})
if err != nil {
writeError(c, err)
diff --git a/server/app/goauto/sybimport/service.go b/server/app/goauto/sybimport/service.go
index 480158c..3fe2e00 100644
--- a/server/app/goauto/sybimport/service.go
+++ b/server/app/goauto/sybimport/service.go
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"strings"
+ "time"
"go-admin/app/goauto/models"
"go-admin/app/goauto/purchase"
@@ -44,6 +45,8 @@ type ListRequest struct {
OrderCodes []string
ParseStatus string
ProcessStage string
+ CreatedFrom string
+ CreatedTo string
}
type ListResponse struct {
@@ -69,6 +72,16 @@ func (service *Service) List(ctx context.Context, request ListRequest) (ListResp
request.PageSize = 500
}
query := service.DB.WithContext(ctx).Model(&models.SYBProduct{})
+ createdFrom, createdTo, err := createdAtRange(request.CreatedFrom, request.CreatedTo)
+ if err != nil {
+ return ListResponse{}, err
+ }
+ if createdFrom != nil {
+ query = query.Where("created_at >= ?", *createdFrom)
+ }
+ if createdTo != nil {
+ query = query.Where("created_at < ?", *createdTo)
+ }
if request.ShopName = strings.TrimSpace(request.ShopName); request.ShopName != "" {
if len([]rune(request.ShopName)) > 255 {
return ListResponse{}, invalidRequest("店铺名称不能超过 255 个字符")
@@ -142,6 +155,43 @@ func (service *Service) List(ctx context.Context, request ListRequest) (ListResp
return ListResponse{Items: items, Total: total, Page: request.Page, PageSize: request.PageSize}, nil
}
+// createdAtRange turns inclusive YYYY-MM-DD bounds into a half-open time
+// range. The bounds are interpreted in the server's local timezone, matching
+// the timestamps written by GORM for this service.
+func createdAtRange(from, to string) (*time.Time, *time.Time, error) {
+ from = strings.TrimSpace(from)
+ to = strings.TrimSpace(to)
+ if from == "" && to == "" {
+ return nil, nil, nil
+ }
+ parse := func(value, label string) (*time.Time, error) {
+ if value == "" {
+ return nil, nil
+ }
+ parsed, err := time.ParseInLocation("2006-01-02", value, time.Local)
+ if err != nil {
+ return nil, invalidRequest(label + " 必须是 YYYY-MM-DD")
+ }
+ return &parsed, nil
+ }
+ start, err := parse(from, "createdFrom")
+ if err != nil {
+ return nil, nil, err
+ }
+ endDay, err := parse(to, "createdTo")
+ if err != nil {
+ return nil, nil, err
+ }
+ if start != nil && endDay != nil && start.After(*endDay) {
+ return nil, nil, invalidRequest("createdFrom 不能晚于 createdTo")
+ }
+ if endDay != nil {
+ end := endDay.AddDate(0, 0, 1)
+ endDay = &end
+ }
+ return start, endDay, nil
+}
+
func normalizeOrderCodes(raw []string) ([]string, error) {
seen := make(map[string]bool, len(raw))
result := make([]string, 0, len(raw))
diff --git a/server/app/goauto/sybimport/service_test.go b/server/app/goauto/sybimport/service_test.go
index ca7c6e9..e104afc 100644
--- a/server/app/goauto/sybimport/service_test.go
+++ b/server/app/goauto/sybimport/service_test.go
@@ -7,6 +7,7 @@ import (
"fmt"
"strings"
"testing"
+ "time"
"go-admin/app/goauto/models"
"go-admin/app/goauto/sybimport"
@@ -123,6 +124,46 @@ func TestServiceListCapsPageSizeAt500(t *testing.T) {
}
}
+func TestServiceListFiltersByCreatedDateInclusive(t *testing.T) {
+ db := openTestDB(t)
+ order := realOrder()
+ for i, day := range []string{"2026-09-01", "2026-09-02", "2026-09-03"} {
+ rowOrder := order
+ rowOrder.Code = fmt.Sprintf("CREATED-%d", i)
+ rowOrder.StockID += uint64(i)
+ detail := realDetailA()
+ detail.ID += uint64(i)
+ result, err := sybimport.ApplyDetail(context.Background(), db, rowOrder, detail)
+ if err != nil {
+ t.Fatal(err)
+ }
+ created, err := time.ParseInLocation("2006-01-02", day, time.Local)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := db.Model(&models.SYBProduct{}).Where("id = ?", result.SYBProduct.ID).Update("created_at", created).Error; err != nil {
+ t.Fatal(err)
+ }
+ }
+ service := sybimport.NewService(db)
+
+ between, err := service.List(context.Background(), sybimport.ListRequest{CreatedFrom: "2026-09-01", CreatedTo: "2026-09-02"})
+ if err != nil || between.Total != 2 {
+ t.Fatalf("inclusive created date range should return two rows, total=%d err=%v", between.Total, err)
+ }
+ fromOnly, err := service.List(context.Background(), sybimport.ListRequest{CreatedFrom: "2026-09-03"})
+ if err != nil || fromOnly.Total != 1 {
+ t.Fatalf("created-from filter should return one row, total=%d err=%v", fromOnly.Total, err)
+ }
+ toOnly, err := service.List(context.Background(), sybimport.ListRequest{CreatedTo: "2026-09-01"})
+ if err != nil || toOnly.Total != 1 {
+ t.Fatalf("created-to filter should return one row, total=%d err=%v", toOnly.Total, err)
+ }
+ if _, err := service.List(context.Background(), sybimport.ListRequest{CreatedFrom: "2026-09-04", CreatedTo: "2026-09-01"}); serviceErrCode(t, err) != sybimport.CodeInvalidRequest {
+ t.Fatalf("reversed created date range should be rejected: %v", err)
+ }
+}
+
func TestServiceListRejectsInvalidParseStatus(t *testing.T) {
db := openTestDB(t)
service := sybimport.NewService(db)
diff --git a/web/src/views/goauto/syb-products/index.vue b/web/src/views/goauto/syb-products/index.vue
index f7a1d68..91fff83 100644
--- a/web/src/views/goauto/syb-products/index.vue
+++ b/web/src/views/goauto/syb-products/index.vue
@@ -12,6 +12,7 @@
+
查询重置AI 匹配{{ aiMatchCandidates.length }}图搜采集{{ imageSearchRows.length }}创建采集{{ collectionCandidates.length }}创建采购{{ purchaseCandidates.length }}匹配退货{{ returnMatchCandidateIds.length }}
@@ -359,7 +360,7 @@ export default {
imageSearchBatch: this.emptyImageSearchBatch(),
collectionBatchData: { ruleId: null, deviceId: null },
collectionBatchRules: { ruleId: [{ required: true, message: '请选择采集规则', trigger: 'change' }] },
- query: { page: 1, pageSize: 100, shopName: '', orderCodesText: '', parseStatus: '', processStage: '' },
+ query: { page: 1, pageSize: 200, shopName: '', orderCodesText: '', parseStatus: '', processStage: '', createdAtRange: [] },
returnMatchByProductId: {},
returnMatchLoading: false,
returnMatchBatchLoading: false,
@@ -432,7 +433,8 @@ export default {
this.$refs.productTable?.clearSelection()
const requestOptions = allowNetworkRetry ? { suppressNetworkError: true } : {}
try {
- const r = await listSybProducts({ page: this.query.page, pageSize: this.query.pageSize, shopName: this.query.shopName.trim(), orderCodes: orderCodes.join(','), parseStatus: this.query.parseStatus, processStage: this.query.processStage }, requestOptions)
+ const [createdFrom = '', createdTo = ''] = this.query.createdAtRange || []
+ const r = await listSybProducts({ page: this.query.page, pageSize: this.query.pageSize, shopName: this.query.shopName.trim(), orderCodes: orderCodes.join(','), parseStatus: this.query.parseStatus, processStage: this.query.processStage, createdFrom, createdTo }, requestOptions)
if (generation !== this.loadGeneration) return
this.products = r.data.items
this.total = r.data.total
@@ -452,7 +454,7 @@ export default {
}
},
search() { this.query.page = 1; this.load() },
- reset() { this.query = { page: 1, pageSize: 100, shopName: '', orderCodesText: '', parseStatus: '', processStage: '' }; this.load() },
+ reset() { this.query = { page: 1, pageSize: 200, shopName: '', orderCodesText: '', parseStatus: '', processStage: '', createdAtRange: [] }; this.load() },
normalizeShopName(value) { return String(value || '').normalize('NFKC').trim().toLocaleLowerCase() },
async ensureShopOptions() {
if (this.shopOptionsLoaded) return