a05f9bd4ec
Deploy / deploy (push) Failing after 8s
- shops 表新增 logo_url 字段,AutoMigrate 自动建列 - 后端新增 POST /api/v1/shop/logo 接口(管理员),图片裁剪为 256×256 JPEG 存储 - UpdateInfo 支持传入 logo_url - 侧边栏「岩美」替换为门店真实名称 + 自定义 logo - 无 logo 时显示店名首字文字头像(深蓝底白字) - 设置页「酒行信息」展示 logo 预览,编辑弹窗新增「更换 Logo」上传按钮 - 修复库存导入计数逻辑:total/imported/updated/errors 四项分别统计 - 库存导入去重改为双路索引(编号 + 名称|系列|规格),避免 key 不一致误判新增 - settings 页导入结果统一显示「重复 X 条」,兼容 skipped 和 updated 两个字段 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
119 lines
3.1 KiB
Go
119 lines
3.1 KiB
Go
package handler
|
||
|
||
import (
|
||
"fmt"
|
||
"image"
|
||
_ "image/jpeg"
|
||
_ "image/png"
|
||
"net/http"
|
||
"os"
|
||
"path/filepath"
|
||
|
||
"github.com/disintegration/imaging"
|
||
"github.com/gin-gonic/gin"
|
||
"gorm.io/gorm"
|
||
|
||
"github.com/wangjia/jiu/backend/config"
|
||
"github.com/wangjia/jiu/backend/internal/middleware"
|
||
"github.com/wangjia/jiu/backend/internal/model"
|
||
)
|
||
|
||
type ShopHandler struct {
|
||
db *gorm.DB
|
||
}
|
||
|
||
func NewShopHandler(db *gorm.DB) *ShopHandler {
|
||
return &ShopHandler{db: db}
|
||
}
|
||
|
||
// GetInfo GET /api/v1/shop/info
|
||
func (h *ShopHandler) GetInfo(c *gin.Context) {
|
||
shopID := middleware.GetShopID(c)
|
||
var shop model.Shop
|
||
if err := h.db.First(&shop, shopID).Error; err != nil {
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "shop not found"})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, shop)
|
||
}
|
||
|
||
// UpdateInfo PUT /api/v1/shop/info (admin only)
|
||
func (h *ShopHandler) UpdateInfo(c *gin.Context) {
|
||
shopID := middleware.GetShopID(c)
|
||
|
||
var req struct {
|
||
Name string `json:"name"`
|
||
Address string `json:"address"`
|
||
Phone string `json:"phone"`
|
||
ManagerName string `json:"manager_name"`
|
||
LogoURL string `json:"logo_url"`
|
||
}
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
updates := map[string]interface{}{
|
||
"name": req.Name,
|
||
"address": req.Address,
|
||
"phone": req.Phone,
|
||
"manager_name": req.ManagerName,
|
||
}
|
||
if req.LogoURL != "" {
|
||
updates["logo_url"] = req.LogoURL
|
||
}
|
||
if err := h.db.Model(&model.Shop{}).Where("id = ?", shopID).Updates(updates).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
var shop model.Shop
|
||
h.db.First(&shop, shopID)
|
||
c.JSON(http.StatusOK, shop)
|
||
}
|
||
|
||
// UploadLogo POST /api/v1/shop/logo (admin only)
|
||
func (h *ShopHandler) UploadLogo(c *gin.Context) {
|
||
shopID := middleware.GetShopID(c)
|
||
|
||
if err := c.Request.ParseMultipartForm(2 << 20); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "文件超过 2MB 限制"})
|
||
return
|
||
}
|
||
file, _, err := c.Request.FormFile("file")
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "请上传文件(field: file)"})
|
||
return
|
||
}
|
||
defer file.Close()
|
||
|
||
img, _, err := image.Decode(file)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "仅支持 JPEG/PNG 图片"})
|
||
return
|
||
}
|
||
|
||
// 裁剪为正方形后缩放到 256×256
|
||
resized := imaging.Fill(img, 256, 256, imaging.Center, imaging.Lanczos)
|
||
|
||
subdir := filepath.Join(config.C.Storage.UploadDir, "shops", fmt.Sprintf("%d", shopID))
|
||
if err := os.MkdirAll(subdir, 0755); err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "存储目录创建失败"})
|
||
return
|
||
}
|
||
fullPath := filepath.Join(subdir, "logo.jpg")
|
||
|
||
if err := imaging.Save(resized, fullPath, imaging.JPEGQuality(90)); err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "图片保存失败"})
|
||
return
|
||
}
|
||
|
||
logoURL := fmt.Sprintf("/images/shops/%d/logo.jpg", shopID)
|
||
if err := h.db.Model(&model.Shop{}).Where("id = ?", shopID).Update("logo_url", logoURL).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{"logo_url": logoURL})
|
||
}
|