Files
cmroubao/backend-api/internal/transport/webui/renderer.go
T

117 lines
2.4 KiB
Go

package webui
import (
"embed"
"errors"
"fmt"
"html/template"
"io"
"path"
"strings"
"time"
)
//go:embed templates/*.gohtml static/admin.css static/admin.js
var embeddedFiles embed.FS
type Renderer struct {
templates *template.Template
}
func NewRenderer() (*Renderer, error) {
templates, err := template.New("admin").
Option("missingkey=error").
Funcs(template.FuncMap{
"displayTime": displayTime,
"machineTime": machineTime,
"pathPart": pathPart,
"formatMoney": formatMinorCurrency,
"imageStatusLabel": freightImageStatusLabel,
}).
ParseFS(embeddedFiles, "templates/*.gohtml")
if err != nil {
return nil, errors.New("parse admin templates")
}
return &Renderer{templates: templates}, nil
}
func formatMinorCurrency(value *int64, currency string) string {
if value == nil {
return "未提供"
}
if currency == "" {
currency = "TWD"
}
return fmt.Sprintf(
"%s %d.%02d",
currency,
*value/100,
*value%100,
)
}
func freightImageStatusLabel(status string) string {
switch status {
case "PENDING":
return "图片待获取"
case "MISSING":
return "ERP 无图片"
case "FAILED":
return "图片获取失败"
default:
return "未提供图片"
}
}
func (r *Renderer) Execute(
writer io.Writer,
name string,
data any,
) error {
if r == nil || r.templates == nil {
return errors.New("admin renderer is not configured")
}
if err := r.templates.ExecuteTemplate(writer, name, data); err != nil {
return errors.New("render admin template")
}
return nil
}
func staticFile(name string) ([]byte, error) {
if name != "admin.css" && name != "admin.js" {
return nil, errors.New("unknown admin static file")
}
content, err := embeddedFiles.ReadFile("static/" + name)
if err != nil {
return nil, errors.New("read admin static file")
}
return content, nil
}
func displayTime(value time.Time) string {
if value.IsZero() {
return "尚未记录"
}
return value.Local().Format("2006-01-02 15:04")
}
func machineTime(value time.Time) string {
if value.IsZero() {
return ""
}
return value.UTC().Format(time.RFC3339)
}
func pathPart(value string) string {
return pathEscape(strings.TrimSpace(value))
}
func pathEscape(value string) string {
// Keep IDs as one opaque path component.
if value == "" || value == "." || value == ".." ||
path.Base(value) != value || strings.ContainsAny(value, `/\`) {
return "invalid"
}
return value
}