feat: M3-M7 全模块落地 + 集成接线
- M3 认证/权限/审计/限流 + 全部 REST 接口 - M4 管理面板(MIUIx + 成绩录入/积分榜下钻/大屏控制) - M5a 大屏页面(六页 + 明细覆盖层 + 缩放适配) - M6 WebSocket 实时同步中心 - M7 xlsx 导出 + 赛事包 zip + 备份 - 规则包 YAML 导入导出(差异预览) - 字体 vendor 子集化(4 套 x2 字重) - 集成:realtime/export/rules 接入 main+httpapi;backup 子命令 - 修复:中间件包装 ResponseWriter 丢失 Hijacker,导致 WS 握手 501
This commit is contained in:
1 parent
d0e3b93e76
commit
bc8b1ee6c0
145 files changed
+25159
-321
No files matched your search
@@ -0,0 +1,152 @@
|
||||
package export
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
// EventResultRow 是分项成绩表的一行。
|
||||
// Rank 用字符串是为了承载「1(并列)」这种展示值。
|
||||
type EventResultRow struct {
|
||||
Rank string
|
||||
Bib string
|
||||
TeamName string
|
||||
Participant string
|
||||
ResultText string
|
||||
Unit string
|
||||
Points float64
|
||||
Status string
|
||||
Remark string
|
||||
}
|
||||
|
||||
// EventSheet 是一个小项的一个组别(无组别时 Category 为空)对应的一张表。
|
||||
type EventSheet struct {
|
||||
SportName string
|
||||
EventName string
|
||||
Category string
|
||||
Rows []EventResultRow
|
||||
}
|
||||
|
||||
// EventsWorkbook 是「分项成绩表」的内容:一个小项可含多个组别 sheet,
|
||||
// 也可承载全部小项(ExportAllXLSX 用)。
|
||||
type EventsWorkbook struct {
|
||||
Meta Meta
|
||||
Sheets []EventSheet
|
||||
}
|
||||
|
||||
var eventColumns = []column{
|
||||
{"名次", 8, kindCenter},
|
||||
{"号码", 9, kindCenter},
|
||||
{"单位", 22, kindText},
|
||||
{"运动员/队", 18, kindText},
|
||||
{"成绩", 12, kindCenter},
|
||||
{"积分", 8, kindNumber},
|
||||
{"状态", 8, kindCenter},
|
||||
{"备注", 20, kindText},
|
||||
}
|
||||
|
||||
// ExportEventXLSX 导出单个小项的成绩表;eventID 为 0 时导出全部小项。
|
||||
func ExportEventXLSX(db *sql.DB, eventID int64, dst string, opts Options) error {
|
||||
wb, err := LoadEventWorkbook(db, eventID, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := eventFile(wb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return saveWorkbook(f, dst)
|
||||
}
|
||||
|
||||
// WriteEventXLSX 把分项成绩表写成 xlsx 流。
|
||||
func WriteEventXLSX(w io.Writer, wb EventsWorkbook) error {
|
||||
f, err := eventFile(wb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return f.Write(w)
|
||||
}
|
||||
|
||||
func eventFile(wb EventsWorkbook) (*excelize.File, error) {
|
||||
return buildWorkbook("成绩表", func(f *excelize.File, st *styleSet) error {
|
||||
return writeEventSheets(f, st, wb.Meta, wb.Sheets, &sheetNamer{})
|
||||
})
|
||||
}
|
||||
|
||||
func writeEventSheets(f *excelize.File, st *styleSet, meta Meta, sheets []EventSheet, namer *sheetNamer) error {
|
||||
meta = meta.normalized()
|
||||
for _, sh := range sheets {
|
||||
name := namer.add(eventSheetBase(sh))
|
||||
if err := addSheet(f, name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeSheet(f, st, name, meta, eventSheetTitle(meta, sh), eventColumns, eventRows(sh.Rows)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// eventSheetBase 是工作表名:大项·小项·组别,够短且能区分。
|
||||
func eventSheetBase(sh EventSheet) string {
|
||||
parts := make([]string, 0, 3)
|
||||
if sh.SportName != "" {
|
||||
parts = append(parts, sh.SportName)
|
||||
}
|
||||
parts = append(parts, sh.EventName)
|
||||
if sh.Category != "" {
|
||||
parts = append(parts, sh.Category)
|
||||
}
|
||||
return strings.Join(parts, "·")
|
||||
}
|
||||
|
||||
// eventSheetTitle 是表内大标题,带赛事名。
|
||||
func eventSheetTitle(meta Meta, sh EventSheet) string {
|
||||
base := eventSheetBase(sh)
|
||||
if base == "" {
|
||||
base = "成绩表"
|
||||
}
|
||||
return sheetTitle(meta, base)
|
||||
}
|
||||
|
||||
func eventRows(rows []EventResultRow) [][]any {
|
||||
out := make([][]any, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
out = append(out, []any{
|
||||
r.Rank, r.Bib, r.TeamName, r.Participant,
|
||||
displayResult(r.ResultText, r.Unit), r.Points, r.Status, r.Remark,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// displayResult 把成绩与单位拼成「11.58 秒」;单位已含在文本里时不重复拼接。
|
||||
func displayResult(text, unit string) string {
|
||||
text = strings.TrimSpace(text)
|
||||
unit = strings.TrimSpace(unit)
|
||||
if text == "" {
|
||||
return ""
|
||||
}
|
||||
if unit == "" || strings.HasSuffix(text, unit) {
|
||||
return text
|
||||
}
|
||||
return text + " " + unitLabel(unit)
|
||||
}
|
||||
|
||||
func unitLabel(unit string) string {
|
||||
switch unit {
|
||||
case "s":
|
||||
return "秒"
|
||||
case "m":
|
||||
return "米"
|
||||
case "pts":
|
||||
return "分"
|
||||
case "次":
|
||||
return "次"
|
||||
default:
|
||||
return unit
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user