chore: 完成多系统功能迭代与优化

1. 通用优化:
   - 统一系统标题为"库房客户端"/"MES产线控制",移除北自所前缀
   - 调整内置账号密码为统一123456,优化密码校验逻辑
   - 新增数据库自动创建逻辑,简化部署流程
   - 修复日志时间格式配置,优化日志输出
   - 新增纯数字密码安全提示

2. MES系统优化:
   - 新增日排产管理功能,支持增删改查与自动算料生成备料单
   - 重构BOM模块,改为按产品编码维护而非工单维度
   - 新增工艺参数模板配置与手动报工页面
   - 新增产品类型自动编码功能
   - 优化菜单结构,调整工单管理、扫码报工等页面名称与路由
   - 新增删除日排产接口与权限保护
   - 修复物料清单查询逻辑,适配新BOM结构
   - 新增refreshToken支持,优化登录会话管理

3. WMS系统优化:
   - 新增基础数据维护页面,支持区域与物料档案管理
   - 新增工单列表下拉接口,对接MES获取未完成工单优先展示
   - 优化入库管理提示文案,替换英文提示为中文
   - 修复精密件入库校验逻辑,优化错误提示
   - 优化Excel导入功能,新增备注字段支持
   - 优化登录页面与菜单文案,统一备料台账名称
   - 新增自动打开浏览器功能,优化客户端启动体验
   - 修复备料出库页面查询提示文案
   - 新增WMS与MES对接配置,完善内部API调用逻辑

4. 其他优化:
   - 删除冗余的旧版静态资源文件,更新资源引用路径
   - 新增帮助文档,完善基础数据模块说明
   - 修复多处文案不统一、英文残留问题
This commit is contained in:
SunYF
2026-08-29 15:30:25 +08:00
parent e82b744bbd
commit ebf901ad16
124 changed files with 1815 additions and 480 deletions
+27 -16
View File
@@ -6,34 +6,45 @@ import (
"github.com/zeromicro/go-zero/rest"
)
// PostgresConfig PostgreSQL 连接配置(go-zero 按 json tag + 字段名小写匹配 yaml
type PostgresConfig struct {
Host string `yaml:"Host"`
Port int `yaml:"Port"`
User string `yaml:"User"`
Password string `yaml:"Password"`
DBName string `yaml:"DBName"`
SSLMode string `yaml:"SSLMode"`
Host string `json:",default=127.0.0.1"`
Port int `json:",default=5432"`
User string `json:",default=postgres"`
Password string `json:",default=postgres"`
DBName string `json:",default=bj_power_wms"`
SSLMode string `json:",default=disable"`
}
type AuthConfig struct {
AccessSecret string `yaml:"AccessSecret"`
AccessExpire int64 `yaml:"AccessExpire"`
AccessSecret string `json:",default=Hardman_2026"`
AccessExpire int64 `json:",default=1800"`
}
// InternalConfig 项目间内部 API 配置
type InternalConfig struct {
Token string `yaml:"Token"` // 写死的项目间 API tokenX-API-TOKEN
Token string `json:",default=Hardman_2026"` // 写死的项目间 API tokenX-API-TOKEN
}
// MesConfig 下游 MES 服务对接(备料/台账需取工单与 BOM)
type MesConfig struct {
BaseURL string `json:",default=http://127.0.0.1:8888"` // MES 服务地址
Token string `json:",default=Hardman_2026"` // 调 MES 内部接口的 X-API-TOKEN
}
type Config struct {
rest.RestConf
Postgres PostgresConfig `yaml:"Postgres"`
Auth AuthConfig `yaml:"Auth"`
Internal InternalConfig `yaml:"Internal"`
Redis struct {
Host string `yaml:"Host"`
Type string `yaml:"Type"`
} `yaml:"Redis"`
Postgres PostgresConfig
Auth AuthConfig
Internal InternalConfig
Redis RedisConf
Mes MesConfig
}
// RedisConf Redis 连接配置
type RedisConf struct {
Host string `json:",default=127.0.0.1:6379"`
Type string `json:",default=node"`
}
// DSN 返回 PostgreSQL 连接串
+29 -1
View File
@@ -22,7 +22,7 @@ func MustNewDB(c DatabaseConf) *ent.Client {
}
func NewDB(c DatabaseConf) (*ent.Client, error) {
dsn := fmt.Sprintf("postgresql://%s:%s@%s:%d/%s",
dsn := fmt.Sprintf("postgresql://%s:%s@%s:%d/%s?sslmode=disable",
c.User, c.Password, c.Host, c.Port, c.Dbname,
)
@@ -39,6 +39,34 @@ func NewDB(c DatabaseConf) (*ent.Client, error) {
return ent.NewClient(ent.Driver(drv)), nil
}
// EnsureDB 若目标数据库不存在则自动创建(先连 postgres 维护库做检查/创建)
func EnsureDB(c DatabaseConf) error {
maintenanceDSN := fmt.Sprintf("postgresql://%s:%s@%s:%d/%s?sslmode=disable",
c.User, c.Password, c.Host, c.Port, "postgres")
sqlDB, err := sql.Open("pgx", maintenanceDSN)
if err != nil {
return err
}
defer sqlDB.Close()
var exists bool
if err := sqlDB.QueryRow(
`SELECT EXISTS(SELECT 1 FROM pg_database WHERE datname = $1)`, c.Dbname,
).Scan(&exists); err != nil {
return err
}
if exists {
return nil
}
if _, err := sqlDB.Exec(`CREATE DATABASE "` + c.Dbname + `"`); err != nil {
return err
}
slog.Info("已自动创建数据库 " + c.Dbname)
return nil
}
// AutoMigrate 启动时自动建表/迁移
func AutoMigrate(client *ent.Client) error {
ctx := contextBackdrop()
+8 -8
View File
@@ -31,10 +31,10 @@ func SeedIfEmpty(client *ent.Client) error {
slog.Info("seed: zones created")
}
// 2. 账号
// 2. 账号(全部统一初始密码 123456
users, _ := client.User.Query().Count(ctx)
if users == 0 {
adminHash, _ := bcrypt.GenerateFromPassword([]byte("admin123"), bcrypt.DefaultCost)
adminHash, _ := bcrypt.GenerateFromPassword([]byte("123456"), bcrypt.DefaultCost)
opHash, _ := bcrypt.GenerateFromPassword([]byte("123456"), bcrypt.DefaultCost)
client.User.Create().
SetUsername("admin").
@@ -56,17 +56,17 @@ func SeedIfEmpty(client *ent.Client) error {
SetRole("inspector").
SetDept("质检").
SaveX(ctx)
slog.Info("seed: users created (admin/admin123, store1/123456, insp1/123456)")
slog.Info("seed: users created (admin/123456, store1/123456, insp1/123456)")
}
// 3. 演示物料与库存
materials, _ := client.Material.Query().Count(ctx)
if materials == 0 {
mats := []struct {
code, name, spec, unit string
mode int
mtype string
isBatch, isSerial bool
code, name, spec, unit string
mode int
mtype string
isBatch, isSerial bool
}{
{"GJ-BAN-001", "10mm钢板(结构件)", "2000x1000x10", "张", 1, "raw", true, false},
{"GJ-GANGGUAN-002", "无缝钢管114x8", "114x8x6000", "根", 1, "raw", true, false},
@@ -115,7 +115,7 @@ func SeedIfEmpty(client *ent.Client) error {
for i := 1; i <= 5; i++ {
sn := "SN20260601001" + time.Now().Format("02")
client.SerialNumber.Create().
SetSnCode(sn+itoaPad(i)).
SetSnCode(sn + itoaPad(i)).
SetMaterialCode("JM-ZHOUCHENG-6020").
SetStatus("在库").
SetQualityStatus("合格").
+3 -1
View File
@@ -13,7 +13,7 @@ import (
)
// excelInboundHandler Excel 批量导入入库(结构件)
// 列头(第一行忽略):物料编码 | 批次号(可空,自动生成) | 数量 | 生产日期 | 供应商 | 区域
// 列头(第一行忽略):物料编码 | 批次号(可空,自动生成) | 数量 | 生产日期 | 供应商 | 区域 | 备注
func excelInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if err := r.ParseMultipartForm(20 << 20); err != nil {
@@ -67,6 +67,7 @@ func excelInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
prodDate := cell(3)
supplier := cell(4)
zone := cell(5)
remark := cell(6)
res := resultRow{Row: i + 1, Code: code, BatchNo: batchNo, Qty: qty}
@@ -116,6 +117,7 @@ func excelInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
SetManageMode(1).
SetNillableZoneCode(strPtr(zone)).
SetQuantity(qty).
SetNillableRemark(strPtr(remark)).
SetOperator("excel-import").
SaveX(ctx0())
res.BatchNo = batchNo
+3 -3
View File
@@ -37,7 +37,7 @@ func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return
}
if req.MaterialCode == "" {
fail(w, http.StatusBadRequest, "materialCode 必填")
fail(w, http.StatusBadRequest, "物料编码必填")
return
}
@@ -79,7 +79,7 @@ func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
}
qty := req.Quantity
if qty <= 0 {
fail(w, http.StatusBadRequest, "结构件入库 quantity 必填且 > 0")
fail(w, http.StatusBadRequest, "结构件入库 数量必填且大于0")
return
}
@@ -119,7 +119,7 @@ func createInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
// 精密件(SN 管理)
if m.ManageMode == 2 {
if len(req.SnList) == 0 {
fail(w, http.StatusBadRequest, "精密件入库 snList 必填")
fail(w, http.StatusBadRequest, "精密件入库 序列号(SN)必填")
return
}
for _, sn := range req.SnList {
+68
View File
@@ -0,0 +1,68 @@
package handler
import (
"encoding/json"
"net/http"
"sort"
"time"
"bj_power_wms/internal/svc"
)
// 视为"已完成"的工单状态(查询时排到最后,未完成的优先显示)
var mesDoneStatuses = map[string]bool{
"FINISHED": true, "COMPLETED": true, "DONE": true,
"CLOSED": true, "已完结": true, "已完成": true,
}
type mesOrder struct {
WorkOrderNo string `json:"workOrderNo"`
ProductCode string `json:"productCode"`
ProductName string `json:"productName"`
Quantity int `json:"quantity"`
Status string `json:"status"`
}
// ordersHandler 调用 MES 内部接口获取工单列表,把未完成工单排前面,供备料出库下拉选择
func ordersHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
url := ctx.Config.Mes.BaseURL + "/api/internal/order/query?orderNo=" + r.URL.Query().Get("orderNo")
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
fail(w, http.StatusBadGateway, "构造 MES 请求失败")
return
}
req.Header.Set("X-API-TOKEN", ctx.Config.Mes.Token)
cli := &http.Client{Timeout: 5 * time.Second}
resp, err := cli.Do(req)
if err != nil {
fail(w, http.StatusBadGateway, "无法连接 MES 服务")
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
fail(w, http.StatusBadGateway, "MES 返回异常")
return
}
var body struct {
Data []mesOrder `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
fail(w, http.StatusBadGateway, "解析 MES 响应失败")
return
}
list := body.Data
if list == nil {
list = []mesOrder{}
}
sort.SliceStable(list, func(i, j int) bool {
di := mesDoneStatuses[list[i].Status]
dj := mesDoneStatuses[list[j].Status]
if di != dj {
return !di // 未完成在前
}
return list[i].WorkOrderNo < list[j].WorkOrderNo
})
ok(w, list)
}
}
+1
View File
@@ -106,6 +106,7 @@ func RegisterHandlers(server *rest.Server, ctx *svc.ServiceContext) {
server.AddRoutes(
[]rest.Route{
{Method: http.MethodGet, Path: "/api/ledger/query", Handler: queryLedgerHandler(ctx)},
{Method: http.MethodGet, Path: "/api/orders", Handler: ordersHandler(ctx)},
},
)
+9 -3
View File
@@ -12,14 +12,20 @@ type ServiceContext struct {
}
func NewServiceContext(c config.Config) *ServiceContext {
entClient := db.MustNewDB(db.DatabaseConf{
conf := db.DatabaseConf{
Host: c.Postgres.Host,
Port: c.Postgres.Port,
User: c.Postgres.User,
Password: c.Postgres.Password,
Dbname: c.Postgres.DBName,
Debug: false,
})
}
// 目标数据库不存在则自动创建
if err := db.EnsureDB(conf); err != nil {
panic(err)
}
entClient := db.MustNewDB(conf)
// 启动时自动建表
if err := db.AutoMigrate(entClient); err != nil {