feat(search): 库存搜索支持拼音/首字母,按回车触发
- 新增 util.ToPinyin(),使用 go-pinyin 生成全拼和首字母 - Product model 新增 name_pinyin / name_initials 列(AutoMigrate) - 启动时自动回填存量商品拼音 - Create / Update / FindOrCreate 写入时同步生成拼音 - 库存搜索 SQL 加入拼音/首字母 LIKE 条件 - 前端去掉 300ms 防抖,改为回车/点击搜索按钮触发 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -64,9 +64,9 @@ func (h *InventoryHandler) List(c *gin.Context) {
|
||||
inStock := c.Query("in_stock")
|
||||
|
||||
if keyword != "" {
|
||||
baseWhere += " AND (COALESCE(NULLIF(p.name,''), inv.product_name) LIKE ? OR COALESCE(NULLIF(p.code,''), inv.product_code) LIKE ?)"
|
||||
baseWhere += " AND (COALESCE(NULLIF(p.name,''), inv.product_name) LIKE ? OR COALESCE(NULLIF(p.code,''), inv.product_code) LIKE ? OR p.name_pinyin LIKE ? OR p.name_initials LIKE ?)"
|
||||
like := "%" + keyword + "%"
|
||||
args = append(args, like, like)
|
||||
args = append(args, like, like, like, like)
|
||||
}
|
||||
if warehouseIDStr != "" {
|
||||
baseWhere += " AND inv.warehouse_id = ?"
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/wangjia/jiu/backend/config"
|
||||
"github.com/wangjia/jiu/backend/internal/middleware"
|
||||
"github.com/wangjia/jiu/backend/internal/model"
|
||||
"github.com/wangjia/jiu/backend/internal/util"
|
||||
)
|
||||
|
||||
type ProductHandler struct {
|
||||
@@ -70,6 +71,7 @@ func (h *ProductHandler) Create(c *gin.Context) {
|
||||
}
|
||||
product.ShopID = shopID
|
||||
product.PublicID = uuid.New().String()
|
||||
product.NamePinyin, product.NameInitials = util.ToPinyin(product.Name)
|
||||
|
||||
// Auto-generate product code if not provided (e.g. P001, P002)
|
||||
// Retry up to 5 times on duplicate key to handle concurrent creates
|
||||
@@ -122,6 +124,7 @@ func (h *ProductHandler) Update(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
namePinyin, nameInitials := util.ToPinyin(req.Name)
|
||||
// 只更新业务字段,防止 Save() 覆盖 shop_id / created_at 等系统字段
|
||||
if err := h.db.Model(&product).Updates(map[string]interface{}{
|
||||
"code": req.Code,
|
||||
@@ -138,6 +141,8 @@ func (h *ProductHandler) Update(c *gin.Context) {
|
||||
"description": req.Description,
|
||||
"remark": req.Remark,
|
||||
"custom_fields": req.CustomFields,
|
||||
"name_pinyin": namePinyin,
|
||||
"name_initials": nameInitials,
|
||||
}).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
@@ -226,13 +231,16 @@ func (h *ProductHandler) FindOrCreate(c *gin.Context) {
|
||||
|
||||
var count int64
|
||||
h.db.Model(&model.Product{}).Where("shop_id = ? AND deleted_at IS NULL", shopID).Count(&count)
|
||||
namePinyin, nameInitials := util.ToPinyin(req.Name)
|
||||
product = model.Product{
|
||||
TenantBase: model.TenantBase{ShopID: shopID},
|
||||
PublicID: uuid.New().String(),
|
||||
Name: req.Name,
|
||||
Series: req.Series,
|
||||
Spec: req.Spec,
|
||||
Code: fmt.Sprintf("P%03d", count+1),
|
||||
TenantBase: model.TenantBase{ShopID: shopID},
|
||||
PublicID: uuid.New().String(),
|
||||
Name: req.Name,
|
||||
Series: req.Series,
|
||||
Spec: req.Spec,
|
||||
Code: fmt.Sprintf("P%03d", count+1),
|
||||
NamePinyin: namePinyin,
|
||||
NameInitials: nameInitials,
|
||||
}
|
||||
if createErr := h.db.Create(&product).Error; createErr != nil {
|
||||
// Race condition: try to find the record created by another request
|
||||
|
||||
Reference in New Issue
Block a user