feat: 商品详情页、XLS导入修复、分页选择器、导出功能
后端: - 新增 product_images 表,支持每商品最多5张图(服务端压缩至1200px/JPEG85%) - products 表新增 public_id(UUID)、description 字段 - 新增商品详情接口、二维码接口、公开商品接口(无鉴权) - 修复 XLS 导入:OLE2 magic bytes 检测 + 临时文件解析,兼容 extrame/xls - 修复商品/名称/系列/规格三张表导入数据为0(LastCol()=0 bug) - 所有导入接口返回 total/imported/skipped 统计 - config 新增 StorageConfig,支持 STORAGE_* 环境变量覆盖 - 种子数据修复:products 补 public_id、新增 product_images TRUNCATE、schema.sql 表名修正 前端: - 商品详情页:图片上传/删除、描述内联编辑、二维码弹窗、公开链接复制 - 公开商品页:无鉴权路由 /product/:public_id,Flutter Web SPA - 商品详情列表(批次追踪)商品名超链接跳转详情页 - 导航「商品管理」改名「商品详情」 - 所有列表表格新增每页条数选择(10/20/50/100) - 表格列头内嵌筛选(FilterableColumnHeader) - 导出 Excel 功能(入库/出库/库存/财务/批次/往来单位) - 网络恢复自动刷新 + 离线缓存展示 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
@@ -11,11 +12,13 @@ type Config struct {
|
||||
Database DatabaseConfig
|
||||
JWT JWTConfig
|
||||
License LicenseConfig
|
||||
Storage StorageConfig
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Port string `mapstructure:"port"`
|
||||
Mode string `mapstructure:"mode"` // debug | release
|
||||
Port string `mapstructure:"port"`
|
||||
Mode string `mapstructure:"mode"` // debug | release
|
||||
CORSOrigin string `mapstructure:"cors_origin"` // 允许的 CORS 来源,生产设为具体域名
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
@@ -32,6 +35,11 @@ type LicenseConfig struct {
|
||||
HMACSecret string `mapstructure:"hmac_secret"` // 许可证签名密钥
|
||||
}
|
||||
|
||||
type StorageConfig struct {
|
||||
UploadDir string `mapstructure:"upload_dir"`
|
||||
BaseURL string `mapstructure:"base_url"`
|
||||
}
|
||||
|
||||
var C Config
|
||||
|
||||
func Load() {
|
||||
@@ -41,13 +49,24 @@ func Load() {
|
||||
viper.AddConfigPath("./config")
|
||||
|
||||
// 环境变量覆盖(生产部署时使用)
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
viper.AutomaticEnv()
|
||||
|
||||
// 显式绑定没有默认值的 key,确保 AutomaticEnv 能找到对应 env var
|
||||
_ = viper.BindEnv("database.dsn", "DATABASE_DSN")
|
||||
_ = viper.BindEnv("jwt.secret", "JWT_SECRET")
|
||||
_ = viper.BindEnv("license.hmac_secret", "LICENSE_HMAC_SECRET")
|
||||
_ = viper.BindEnv("storage.upload_dir", "STORAGE_UPLOAD_DIR")
|
||||
_ = viper.BindEnv("storage.base_url", "STORAGE_BASE_URL")
|
||||
|
||||
// 默认值
|
||||
viper.SetDefault("server.port", "8080")
|
||||
viper.SetDefault("server.mode", "debug")
|
||||
viper.SetDefault("server.cors_origin", "*")
|
||||
viper.SetDefault("jwt.access_expire_min", 60)
|
||||
viper.SetDefault("jwt.refresh_expire_h", 168) // 7天
|
||||
viper.SetDefault("storage.upload_dir", "./uploads/images")
|
||||
viper.SetDefault("storage.base_url", "http://localhost:8080/images")
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
log.Println("[config] no config file found, using defaults and env vars")
|
||||
|
||||
@@ -13,3 +13,7 @@ jwt:
|
||||
|
||||
license:
|
||||
hmac_secret: "change-this-license-secret-in-production"
|
||||
|
||||
storage:
|
||||
upload_dir: "./uploads/images"
|
||||
base_url: "http://localhost:8080/images"
|
||||
|
||||
Reference in New Issue
Block a user