From 754275cbd6487fa75b0bab0a01ea616dd21c3592 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Tue, 7 Apr 2026 22:29:15 +0800 Subject: [PATCH] =?UTF-8?q?fix(backend):=20=E4=BF=AE=E5=A4=8D=E6=97=A5?= =?UTF-8?q?=E6=9C=9F=E5=AD=97=E6=AE=B5=E8=A7=A3=E6=9E=90=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E5=88=9B=E5=BB=BA=E5=8D=95=E6=8D=AE=E6=8A=A5?= =?UTF-8?q?=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 model.Date 类型,支持前端传入的纯日期格式 YYYY-MM-DD: - UnmarshalJSON 优先解析 YYYY-MM-DD,回退支持 RFC3339 - MarshalJSON 输出 YYYY-MM-DD - Value/Scan 实现 GORM MySQL DATE 列读写 - StockInOrder/StockOutOrder/InventoryCheck 的 OrderDate/CheckDate 改用 Date 类型 feat(client): SelectionArea 全局开启文字可选中复制 Co-Authored-By: Claude Sonnet 4.6 --- backend/cmd/seed/main.go | 8 ++-- backend/internal/model/base.go | 70 +++++++++++++++++++++++++++++++++ backend/internal/model/stock.go | 6 +-- client/lib/main.dart | 1 + 4 files changed, 78 insertions(+), 7 deletions(-) diff --git a/backend/cmd/seed/main.go b/backend/cmd/seed/main.go index 3ef596c..9ba8e4e 100644 --- a/backend/cmd/seed/main.go +++ b/backend/cmd/seed/main.go @@ -203,7 +203,7 @@ func main() { } for _, o := range inOrders { - order := upsertStockInOrder(db, shop.ID, wh1.ID, admin.ID, o.partnerID, o.orderNo, today.AddDate(0, 0, -3)) + order := upsertStockInOrder(db, shop.ID, wh1.ID, admin.ID, o.partnerID, o.orderNo, model.Date{Time: today.AddDate(0, 0, -3)}) total := 0.0 for _, it := range o.items { item := model.StockInItem{ @@ -231,7 +231,7 @@ func main() { // ═══════════════════════════════════════════════════ // 出库单(已审核)→ 减库存 // ═══════════════════════════════════════════════════ - outOrder := upsertStockOutOrder(db, shop.ID, wh1.ID, admin.ID, cus1.ID, "CK20260404001", today.AddDate(0, 0, -1)) + outOrder := upsertStockOutOrder(db, shop.ID, wh1.ID, admin.ID, cus1.ID, "CK20260404001", model.Date{Time: today.AddDate(0, 0, -1)}) outItems := []struct { prodIdx int qty float64 @@ -371,7 +371,7 @@ func upsertNumberRule(db *gorm.DB, shopID uint64, ruleType, prefix string, curre } } -func upsertStockInOrder(db *gorm.DB, shopID, whID, opID, partnerID uint64, orderNo string, date time.Time) model.StockInOrder { +func upsertStockInOrder(db *gorm.DB, shopID, whID, opID, partnerID uint64, orderNo string, date model.Date) model.StockInOrder { var o model.StockInOrder if db.Where("shop_id = ? AND order_no = ?", shopID, orderNo).First(&o).Error != nil { o = model.StockInOrder{ @@ -390,7 +390,7 @@ func upsertStockInOrder(db *gorm.DB, shopID, whID, opID, partnerID uint64, order return o } -func upsertStockOutOrder(db *gorm.DB, shopID, whID, opID, partnerID uint64, orderNo string, date time.Time) model.StockOutOrder { +func upsertStockOutOrder(db *gorm.DB, shopID, whID, opID, partnerID uint64, orderNo string, date model.Date) model.StockOutOrder { var o model.StockOutOrder if db.Where("shop_id = ? AND order_no = ?", shopID, orderNo).First(&o).Error != nil { o = model.StockOutOrder{ diff --git a/backend/internal/model/base.go b/backend/internal/model/base.go index 867530e..92740f4 100644 --- a/backend/internal/model/base.go +++ b/backend/internal/model/base.go @@ -35,6 +35,76 @@ func (j *JSON) Scan(value interface{}) error { return json.Unmarshal(bytes, j) } +// Date 仅含日期(YYYY-MM-DD)的类型,兼容前端只传日期字符串的场景 +type Date struct{ time.Time } + +const dateFmt = "2006-01-02" + +func (d *Date) UnmarshalJSON(b []byte) error { + s := string(b) + // 去掉引号 + if len(s) >= 2 && s[0] == '"' { + s = s[1 : len(s)-1] + } + if s == "" || s == "null" { + d.Time = time.Time{} + return nil + } + // 尝试纯日期格式 YYYY-MM-DD + if t, err := time.ParseInLocation(dateFmt, s, time.Local); err == nil { + d.Time = t + return nil + } + // 回退:标准 RFC3339 + t, err := time.Parse(time.RFC3339, s) + if err != nil { + return fmt.Errorf("cannot parse date %q", s) + } + d.Time = t + return nil +} + +func (d Date) MarshalJSON() ([]byte, error) { + if d.Time.IsZero() { + return []byte(`""`), nil + } + return []byte(`"` + d.Time.Format(dateFmt) + `"`), nil +} + +// Value / Scan 让 GORM 正确读写 MySQL DATE 列 +func (d Date) Value() (driver.Value, error) { + if d.Time.IsZero() { + return nil, nil + } + return d.Time.Format(dateFmt), nil +} + +func (d *Date) Scan(value interface{}) error { + if value == nil { + d.Time = time.Time{} + return nil + } + switch v := value.(type) { + case time.Time: + d.Time = v + case []byte: + t, err := time.ParseInLocation(dateFmt, string(v), time.Local) + if err != nil { + return err + } + d.Time = t + case string: + t, err := time.ParseInLocation(dateFmt, v, time.Local) + if err != nil { + return err + } + d.Time = t + default: + return fmt.Errorf("cannot scan type %T into Date", value) + } + return nil +} + // Base 公共字段 type Base struct { ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"` diff --git a/backend/internal/model/stock.go b/backend/internal/model/stock.go index 3ad185e..903fc0b 100644 --- a/backend/internal/model/stock.go +++ b/backend/internal/model/stock.go @@ -13,7 +13,7 @@ type StockInOrder struct { OperatorID uint64 `gorm:"not null" json:"operator_id"` ReviewerID *uint64 `json:"reviewer_id"` Status string `gorm:"type:enum('draft','pending','approved','rejected');default:'draft'" json:"status"` - OrderDate time.Time `gorm:"type:date" json:"order_date"` + OrderDate Date `gorm:"type:date" json:"order_date"` TotalAmount float64 `gorm:"type:decimal(16,2);default:0" json:"total_amount"` ReviewedAt *time.Time `json:"reviewed_at"` CustomFields JSON `gorm:"type:json" json:"custom_fields,omitempty"` @@ -51,7 +51,7 @@ type StockOutOrder struct { OperatorID uint64 `gorm:"not null" json:"operator_id"` ReviewerID *uint64 `json:"reviewer_id"` Status string `gorm:"type:enum('draft','pending','approved','rejected');default:'draft'" json:"status"` - OrderDate time.Time `gorm:"type:date" json:"order_date"` + OrderDate Date `gorm:"type:date" json:"order_date"` TotalAmount float64 `gorm:"type:decimal(16,2);default:0" json:"total_amount"` ReviewedAt *time.Time `json:"reviewed_at"` CustomFields JSON `gorm:"type:json" json:"custom_fields,omitempty"` @@ -116,7 +116,7 @@ type InventoryCheck struct { WarehouseID uint64 `gorm:"not null" json:"warehouse_id"` OperatorID uint64 `gorm:"not null" json:"operator_id"` Status string `gorm:"type:enum('draft','completed');default:'draft'" json:"status"` - CheckDate time.Time `gorm:"type:date" json:"check_date"` + CheckDate Date `gorm:"type:date" json:"check_date"` Remark string `gorm:"size:500" json:"remark"` Items []InventoryCheckItem `gorm:"foreignKey:CheckID" json:"items,omitempty"` diff --git a/client/lib/main.dart b/client/lib/main.dart index 78e7d2e..f2b9ca0 100644 --- a/client/lib/main.dart +++ b/client/lib/main.dart @@ -80,6 +80,7 @@ class _RouterApp extends ConsumerWidget { theme: AppTheme.light(), routerConfig: router, debugShowCheckedModeBanner: false, + builder: (context, child) => SelectionArea(child: child!), ); } }