feat: 扫码商品详情页重设计 + 宣传主页 + nginx 路由完善

- public_product_screen: 1:1 复刻设计稿,新增商品介绍、批次防伪(自定义圆形印章)、完整商品参数表、门店营业时间、页脚链接
- app_theme: 完整品牌色系(brand/gray/accent/semantic)
- shop model: 新增 business_hours 字段,public 接口同步返回
- nginx: 新增 /product/ → Flutter app 扫码入口,修复 /docs /download html 后缀兼容
- web/: 新增宣传主页、功能页、文档页、扫码 HTML 及设计资源
- app_shell: 界面优化

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-05-23 18:58:04 +08:00
parent 1b4ca0c675
commit 30a8e791a3
22 changed files with 4280 additions and 364 deletions
+58 -1
View File
@@ -2,6 +2,8 @@ package handler
import (
"net/http"
"os"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
@@ -29,7 +31,37 @@ func (h *PublicHandler) GetProduct(c *gin.Context) {
return
}
// Return only public-safe fields (no price/stock info)
// Fetch shop public info
var shop model.Shop
shopData := gin.H{}
if err := h.db.Where("id = ?", product.ShopID).First(&shop).Error; err == nil {
shopData = gin.H{
"name": shop.Name,
"code": shop.Code,
"address": shop.Address,
"phone": shop.Phone,
"business_hours": shop.BusinessHours,
}
}
// Fetch latest inventory batch for this product
var inv model.Inventory
batchData := gin.H(nil)
if err := h.db.Where("product_id = ? AND quantity > 0 AND deleted_at IS NULL", product.ID).
Order("created_at DESC").
First(&inv).Error; err == nil {
var pdStr *string
if inv.ProductionDate != nil {
s := inv.ProductionDate.Time.Format("2006-01-02")
pdStr = &s
}
batchData = gin.H{
"production_date": pdStr,
"batch_no": inv.BatchNo,
"in_stock_date": inv.CreatedAt.Format("2006-01-02"),
}
}
c.JSON(http.StatusOK, gin.H{
"data": gin.H{
"id": product.ID,
@@ -40,6 +72,31 @@ func (h *PublicHandler) GetProduct(c *gin.Context) {
"unit": product.Unit,
"description": product.Description,
"images": product.Images,
"shop": shopData,
"batch": batchData,
},
})
}
// GetRelease GET /api/v1/public/release (no auth)
func (h *PublicHandler) GetRelease(c *gin.Context) {
version := os.Getenv("APP_VERSION")
if version == "" {
version = "1.0.0"
}
buildDate := os.Getenv("BUILD_DATE")
if buildDate == "" {
buildDate = time.Now().Format("2006-01-02")
}
c.JSON(http.StatusOK, gin.H{
"version": version,
"build_date": buildDate,
"download_urls": gin.H{
"macos": os.Getenv("DOWNLOAD_URL_MACOS"),
"windows": os.Getenv("DOWNLOAD_URL_WINDOWS"),
"web": "https://jiu.51yanmei.com/app",
},
"changelog": []gin.H{},
})
}
+1
View File
@@ -6,6 +6,7 @@ type Shop struct {
Code string `gorm:"size:50;uniqueIndex" json:"code"`
Address string `gorm:"size:255" json:"address"`
Phone string `gorm:"size:30" json:"phone"`
BusinessHours string `gorm:"size:100" json:"business_hours"`
ManagerName string `gorm:"size:50" json:"manager_name"`
BusinessLicense string `gorm:"size:500" json:"business_license"`
ShopPhotos JSON `gorm:"type:json" json:"shop_photos,omitempty"`
+2 -1
View File
@@ -51,10 +51,11 @@ func Setup(r *gin.Engine, db *gorm.DB) {
auth.POST("/refresh", authH.Refresh)
}
// 商品公开详情(无需登录)
// 公开接口(无需登录)
public := v1.Group("/public")
{
public.GET("/products/:public_id", publicH.GetProduct)
public.GET("/release", publicH.GetRelease)
}
// 需要 JWT 的路由(ReadOnly 中间件:只读用户不可执行写操作)