Files
bj_power/bj_power_mes/internal/handler/processcard.go
T
SunYF 4ec6784629 feat: 完成MES工位终端系统全量功能迭代
本次提交包含以下核心变更:
1. 新增按钮级权限控制框架与前端权限校验
2. 新增工位管理、工艺流程、巡检终端等核心业务模块
3. 完善用户体系,支持工位终端专属登录权限
4. 新增文件上传下载、报表查询等配套功能
5. 修复多系统兼容性与交互体验问题
6. 完成所有文档与配置项的规范化更新
2026-08-31 08:06:55 +08:00

209 lines
6.0 KiB
Go
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handler
import (
"bytes"
"fmt"
"html/template"
"net/http"
"strings"
"time"
"bj_power_mes/common/httpx"
"bj_power_mes/internal/logic"
"bj_power_mes/internal/svc"
)
// ProcessCardHandler GET /api/v1/process-card?sn=xxx
// 按 SN 生成可打印流程卡(无公司名、含签名栏、可调样式),返回 text/html。
func ProcessCardHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
sn := strings.TrimSpace(r.URL.Query().Get("sn"))
if sn == "" {
httpx.BadRequest(w, "缺少 sn 参数")
return
}
data, err := logic.New(svcCtx).Trace(r.Context(), sn)
if err != nil {
httpx.Fail(w, 2401, err.Error())
return
}
html, err := renderProcessCard(sn, data)
if err != nil {
httpx.Fail(w, 2402, "流程卡生成失败:"+err.Error())
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = w.Write([]byte(html))
}
}
type cardTmpl struct {
Sn string
OrderNo string
Product string
CreatedAt string
Processes []cardProcess
Materials []string
Operator string
Inspector string
DoneTime string
Result string
}
type cardProcess struct {
Code string
Name string
Station string
Operator string
Status string
Result string
Steps []cardStep
}
type cardStep struct {
Name string
Value string
OK bool
}
func renderProcessCard(sn string, data map[string]any) (string, error) {
t := cardTmpl{Sn: sn}
wp, _ := data["workpiece"].(map[string]any)
if wp != nil {
t.OrderNo = strAny(wp["orderNo"])
t.Product = strAny(wp["productType"])
if c, ok := wp["createdAt"]; ok {
if ts, ok := c.(time.Time); ok {
t.CreatedAt = ts.Format("2006-01-02 15:04")
} else {
t.CreatedAt = fmt.Sprintf("%v", c)
}
}
}
procList, _ := data["processTimeline"].([]any)
for _, p := range procList {
pm, _ := p.(map[string]any)
cp := cardProcess{
Code: strAny(pm["processCode"]),
Name: strAny(pm["processName"]),
Station: strAny(pm["stationNo"]),
Operator: strAny(pm["operator"]),
Status: strAny(pm["status"]),
Result: strAny(pm["result"]),
}
t.Processes = append(t.Processes, cp)
}
steps, _ := data["stepData"].([]any)
stepByProc := map[string][]cardStep{}
for _, s := range steps {
sm, _ := s.(map[string]any)
key := fmt.Sprintf("%v", sm["processCode"])
ok, _ := sm["isOK"].(bool)
stepByProc[key] = append(stepByProc[key], cardStep{
Name: strAny(sm["stepName"]),
Value: strAny(sm["valueText"]),
OK: ok,
})
}
for i := range t.Processes {
t.Processes[i].Steps = stepByProc[t.Processes[i].Code]
}
assoc, _ := data["associationTrace"].(map[string]any)
if assoc != nil {
if b, ok := assoc["batchItems"].([]any); ok {
for _, x := range b {
t.Materials = append(t.Materials, fmt.Sprintf("%v", x))
}
}
if s, ok := assoc["serialItems"].([]any); ok {
for _, x := range s {
t.Materials = append(t.Materials, fmt.Sprintf("%v", x))
}
}
t.Operator = strAny(assoc["operator"])
}
done := "待完工"
for _, p := range t.Processes {
if p.Result == "NG" {
done = "NG"
}
}
t.Result = done
const tpl = `<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<title>流程卡 {{.Sn}}</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family:"Microsoft YaHei",sans-serif; color:#1f2937; padding:24px; }
.card { max-width:900px; margin:0 auto; border:2px solid #333; border-radius:8px; padding:20px 24px; }
h1 { font-size:22px; text-align:center; border-bottom:2px solid #333; padding-bottom:10px; margin-bottom:14px; letter-spacing:4px; }
.meta { display:flex; flex-wrap:wrap; gap:8px 32px; font-size:14px; margin-bottom:14px; }
.meta b { font-weight:700; }
table { width:100%; border-collapse:collapse; font-size:13px; }
th, td { border:1px solid #999; padding:6px 8px; text-align:center; }
th { background:#f3f4f6; }
.ok { color:#15803d; font-weight:700; }
.ng { color:#dc2626; font-weight:700; }
.sub td { text-align:left; color:#374151; background:#fafafa; }
.sign { display:flex; justify-content:space-between; margin-top:22px; font-size:14px; }
.sign div { width:45%; border-top:1px solid #333; padding-top:6px; text-align:center; }
@media print { body { padding:0; } .card { border-color:#000; box-shadow:none; } .no-print { display:none; } }
</style>
</head>
<body>
<div class="card">
<h1>产品流程卡</h1>
<div class="meta">
<span>SN<b>{{.Sn}}</b></span>
<span>工单号:<b>{{.OrderNo}}</b></span>
<span>产品型号:<b>{{.Product}}</b></span>
<span>登记时间:<b>{{.CreatedAt}}</b></span>
<span>判定:<b>{{if eq .Result "NG"}}<span class="ng">NG</span>{{else}}{{.Result}}{{end}}</b></span>
</div>
<table>
<thead><tr><th>工序</th><th>工序名称</th><th>工位</th><th>操作人</th><th>采集参数</th><th>结果</th></tr></thead>
<tbody>
{{range .Processes}}
<tr>
<td>工序{{.Code}}</td><td>{{.Name}}</td><td>{{.Station}}</td><td>{{.Operator}}</td>
<td>{{if .Steps}}{{range .Steps}}{{.Name}}={{.Value}}({{if .OK}}<span class="ok">OK</span>{{else}}<span class="ng">NG</span>{{end}}) {{end}}{{else}}-{{end}}</td>
<td>{{if eq .Result "NG"}}<span class="ng">NG</span>{{else}}OK{{end}}</td>
</tr>
{{end}}
</tbody>
</table>
{{if .Materials}}
<p style="margin-top:10px;font-size:13px;">关联物料/批次:{{range .Materials}}{{.}} {{end}}</p>
{{end}}
<div class="sign">
<div>操作人签字:</div>
<div>检验员签字:</div>
</div>
<div style="text-align:center;margin-top:14px;">
<button class="no-print" onclick="window.print()" style="padding:8px 28px;font-size:15px;">打 印</button>
</div>
</div>
</body>
</html>`
tmpl, err := template.New("card").Parse(tpl)
if err != nil {
return "", err
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, t); err != nil {
return "", err
}
return buf.String(), nil
}
func strAny(v any) string {
if v == nil {
return ""
}
return fmt.Sprintf("%v", v)
}