754275cbd6
新增 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 <noreply@anthropic.com>
121 lines
2.4 KiB
Go
121 lines
2.4 KiB
Go
package model
|
|
|
|
import (
|
|
"database/sql/driver"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// JSON 类型,用于 custom_fields
|
|
type JSON map[string]interface{}
|
|
|
|
func (j JSON) Value() (driver.Value, error) {
|
|
if j == nil {
|
|
return nil, nil
|
|
}
|
|
b, err := json.Marshal(j)
|
|
return string(b), err
|
|
}
|
|
|
|
func (j *JSON) Scan(value interface{}) error {
|
|
if value == nil {
|
|
*j = nil
|
|
return nil
|
|
}
|
|
var bytes []byte
|
|
switch v := value.(type) {
|
|
case string:
|
|
bytes = []byte(v)
|
|
case []byte:
|
|
bytes = v
|
|
default:
|
|
return fmt.Errorf("cannot scan type %T into JSON", value)
|
|
}
|
|
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"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
DeletedAt *time.Time `gorm:"index" json:"-"`
|
|
}
|
|
|
|
// TenantBase 含租户隔离的公共字段
|
|
type TenantBase struct {
|
|
Base
|
|
ShopID uint64 `gorm:"not null;index" json:"shop_id"`
|
|
}
|