1065 lines
25 KiB
Go
1065 lines
25 KiB
Go
package ui
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"log"
|
||
"os"
|
||
"strconv"
|
||
"time"
|
||
|
||
"github.com/ying32/govcl/vcl"
|
||
"github.com/ying32/govcl/vcl/types"
|
||
"github.com/ying32/govcl/vcl/types/colors"
|
||
"go.bug.st/serial"
|
||
|
||
"bj_power_wms_client/internal/api"
|
||
"bj_power_wms_client/internal/app"
|
||
"bj_power_wms_client/internal/autostart"
|
||
"bj_power_wms_client/internal/config"
|
||
"bj_power_wms_client/internal/serialport"
|
||
)
|
||
|
||
type itemRow struct {
|
||
panel *vcl.TPanel
|
||
name *vcl.TLabel
|
||
value *vcl.TLabel
|
||
unit *vcl.TLabel
|
||
}
|
||
|
||
type MainForm struct {
|
||
*vcl.TForm
|
||
|
||
cfg config.Config
|
||
apiClient *api.Client
|
||
collector *app.Collector
|
||
serial *serialport.Port
|
||
|
||
// 模å�—A:顶部控制æ �
|
||
portLab *vcl.TLabel
|
||
portCombo *vcl.TComboBox
|
||
statusLabel *vcl.TLabel
|
||
connectBtn *vcl.TButton
|
||
refreshBtn *vcl.TButton
|
||
labU *vcl.TLabel
|
||
rbMM *vcl.TRadioButton
|
||
rbInch *vcl.TRadioButton
|
||
autoStartChk *vcl.TCheckBox
|
||
|
||
// 模�B:工�信�
|
||
workOrderNoLabel *vcl.TLabel
|
||
workOrderNoEdit *vcl.TEdit
|
||
queryBtn *vcl.TButton
|
||
typeLabel *vcl.TLabel
|
||
typeCombo *vcl.TComboBox
|
||
countLabel *vcl.TLabel
|
||
countValue *vcl.TLabel
|
||
statusLabel2 *vcl.TLabel
|
||
statusValue *vcl.TLabel
|
||
timeLabel *vcl.TLabel
|
||
timeValue *vcl.TLabel
|
||
|
||
// 模�C:仪表盘显示�
|
||
rows []itemRow
|
||
currentType string
|
||
|
||
// 模�D:�作与数��
|
||
bottomLabel *vcl.TLabel
|
||
workpieceNoEdit *vcl.TEdit
|
||
filterBtn *vcl.TButton
|
||
exportBtn *vcl.TButton
|
||
clearBtn *vcl.TButton
|
||
submitBtn *vcl.TButton
|
||
histView *vcl.TListView
|
||
|
||
// 定时�
|
||
reconnT *vcl.TTimer
|
||
|
||
// 当�工�信�
|
||
currentWorkOrder *api.WorkOrderInfo
|
||
}
|
||
|
||
var MainFormPtr *MainForm
|
||
var injectedCfg config.Config
|
||
var injectedAppName string
|
||
|
||
func InitForm(c config.Config, appName string) {
|
||
injectedCfg = c
|
||
injectedAppName = appName
|
||
}
|
||
|
||
const (
|
||
baseW int32 = 1280
|
||
baseH int32 = 720
|
||
minFont int32 = 20
|
||
maxFont int32 = 96
|
||
panelH int32 = 180
|
||
gapX int32 = 8
|
||
valSize int32 = 28
|
||
nameSize int32 = 16
|
||
unitSize int32 = 16
|
||
topRowH int32 = 44
|
||
)
|
||
|
||
var squareItemDefs = []string{"�弧��径 B"}
|
||
var circleItemDefs = []string{"���直径 A", "���轮廓�B", "����置 C"}
|
||
|
||
func (f *MainForm) OnFormCreate(sender vcl.IObject) {
|
||
f.cfg = injectedCfg
|
||
f.apiClient = api.NewClient(f.cfg.APIURL)
|
||
f.collector = app.NewCollector(squareItemDefs)
|
||
|
||
f.SetCaption("7315-2 �分尺采�)
|
||
f.SetWidth(1280)
|
||
f.SetHeight(720)
|
||
f.SetPosition(types.PoScreenCenter)
|
||
f.SetWindowState(types.WsMaximized)
|
||
f.SetOnResize(f.onResize)
|
||
|
||
f.initModuleA()
|
||
f.initModuleB()
|
||
f.initModuleC("square")
|
||
f.initModuleD()
|
||
|
||
f.reconnT = vcl.NewTimer(f)
|
||
f.reconnT.SetInterval(3000)
|
||
f.reconnT.SetOnTimer(func(s vcl.IObject) { f.tryConnectSerial() })
|
||
f.reconnT.SetEnabled(false)
|
||
|
||
f.relayout()
|
||
f.refreshPortList()
|
||
f.tryConnectSerial()
|
||
f.onFilter(nil)
|
||
}
|
||
|
||
func (f *MainForm) initModuleA() {
|
||
f.portLab = vcl.NewLabel(f)
|
||
f.portLab.SetParent(f)
|
||
f.portLab.SetLeft(16)
|
||
f.portLab.SetTop(12)
|
||
f.portLab.SetCaption("串�:")
|
||
f.portLab.Font().SetSize(14)
|
||
|
||
f.portCombo = vcl.NewComboBox(f)
|
||
f.portCombo.SetParent(f)
|
||
f.portCombo.SetLeft(72)
|
||
f.portCombo.SetTop(8)
|
||
f.portCombo.SetWidth(140)
|
||
f.portCombo.SetHeight(topRowH)
|
||
f.portCombo.SetStyle(types.CsDropDownList)
|
||
f.portCombo.Font().SetSize(14)
|
||
|
||
f.statusLabel = vcl.NewLabel(f)
|
||
f.statusLabel.SetParent(f)
|
||
f.statusLabel.SetLeft(220)
|
||
f.statusLabel.SetTop(14)
|
||
f.statusLabel.SetWidth(140)
|
||
f.statusLabel.SetHeight(32)
|
||
f.statusLabel.SetCaption("�未连接")
|
||
f.statusLabel.Font().SetSize(14)
|
||
|
||
f.connectBtn = vcl.NewButton(f)
|
||
f.connectBtn.SetParent(f)
|
||
f.connectBtn.SetLeft(368)
|
||
f.connectBtn.SetTop(8)
|
||
f.connectBtn.SetWidth(80)
|
||
f.connectBtn.SetHeight(topRowH)
|
||
f.connectBtn.SetCaption("连接")
|
||
f.connectBtn.Font().SetSize(14)
|
||
f.connectBtn.SetOnClick(f.onConnect)
|
||
|
||
f.refreshBtn = vcl.NewButton(f)
|
||
f.refreshBtn.SetParent(f)
|
||
f.refreshBtn.SetLeft(456)
|
||
f.refreshBtn.SetTop(8)
|
||
f.refreshBtn.SetWidth(80)
|
||
f.refreshBtn.SetHeight(topRowH)
|
||
f.refreshBtn.SetCaption("刷新")
|
||
f.refreshBtn.Font().SetSize(14)
|
||
f.refreshBtn.SetOnClick(f.onRefreshPorts)
|
||
|
||
f.labU = vcl.NewLabel(f)
|
||
f.labU.SetParent(f)
|
||
f.labU.SetLeft(544)
|
||
f.labU.SetTop(14)
|
||
f.labU.SetCaption("��:")
|
||
f.labU.Font().SetSize(14)
|
||
|
||
f.rbMM = vcl.NewRadioButton(f)
|
||
f.rbMM.SetParent(f)
|
||
f.rbMM.SetLeft(600)
|
||
f.rbMM.SetTop(10)
|
||
f.rbMM.SetCaption("mm")
|
||
f.rbMM.SetChecked(f.cfg.DisplayUnit != "inch")
|
||
f.rbMM.Font().SetSize(14)
|
||
f.rbMM.SetOnClick(f.onUnitChange)
|
||
|
||
f.rbInch = vcl.NewRadioButton(f)
|
||
f.rbInch.SetParent(f)
|
||
f.rbInch.SetLeft(670)
|
||
f.rbInch.SetTop(10)
|
||
f.rbInch.SetCaption("inch")
|
||
f.rbInch.SetChecked(f.cfg.DisplayUnit == "inch")
|
||
f.rbInch.Font().SetSize(14)
|
||
f.rbInch.SetOnClick(f.onUnitChange)
|
||
|
||
f.autoStartChk = vcl.NewCheckBox(f)
|
||
f.autoStartChk.SetParent(f)
|
||
f.autoStartChk.SetLeft(740)
|
||
f.autoStartChk.SetTop(12)
|
||
f.autoStartChk.SetCaption("开机��)
|
||
f.autoStartChk.Font().SetSize(14)
|
||
f.autoStartChk.SetChecked(autostart.IsEnabled(injectedAppName))
|
||
f.autoStartChk.SetOnClick(f.onAutoStartToggle)
|
||
}
|
||
|
||
func (f *MainForm) initModuleB() {
|
||
f.workOrderNoLabel = vcl.NewLabel(f)
|
||
f.workOrderNoLabel.SetParent(f)
|
||
f.workOrderNoLabel.SetLeft(16)
|
||
f.workOrderNoLabel.SetTop(60)
|
||
f.workOrderNoLabel.SetCaption("工��")
|
||
f.workOrderNoLabel.Font().SetSize(16)
|
||
|
||
f.workOrderNoEdit = vcl.NewEdit(f)
|
||
f.workOrderNoEdit.SetParent(f)
|
||
f.workOrderNoEdit.SetLeft(80)
|
||
f.workOrderNoEdit.SetTop(56)
|
||
f.workOrderNoEdit.SetWidth(350)
|
||
f.workOrderNoEdit.SetHeight(topRowH)
|
||
f.workOrderNoEdit.SetTextHint("请输入工��")
|
||
f.workOrderNoEdit.Font().SetSize(16)
|
||
|
||
f.queryBtn = vcl.NewButton(f)
|
||
f.queryBtn.SetParent(f)
|
||
f.queryBtn.SetLeft(438)
|
||
f.queryBtn.SetTop(56)
|
||
f.queryBtn.SetWidth(100)
|
||
f.queryBtn.SetHeight(topRowH)
|
||
f.queryBtn.SetCaption("查询")
|
||
f.queryBtn.Font().SetSize(16)
|
||
f.queryBtn.SetOnClick(f.onQueryWorkOrder)
|
||
|
||
f.typeLabel = vcl.NewLabel(f)
|
||
f.typeLabel.SetParent(f)
|
||
f.typeLabel.SetLeft(540)
|
||
f.typeLabel.SetTop(60)
|
||
f.typeLabel.SetCaption("工件类型:")
|
||
f.typeLabel.Font().SetSize(14)
|
||
|
||
f.typeCombo = vcl.NewComboBox(f)
|
||
f.typeCombo.SetParent(f)
|
||
f.typeCombo.SetLeft(618)
|
||
f.typeCombo.SetTop(56)
|
||
f.typeCombo.SetWidth(120)
|
||
f.typeCombo.SetHeight(topRowH)
|
||
f.typeCombo.SetStyle(types.CsDropDownList)
|
||
f.typeCombo.SetReadOnly(true)
|
||
f.typeCombo.Font().SetSize(14)
|
||
f.typeCombo.Items().Add("方形")
|
||
f.typeCombo.Items().Add("圆形")
|
||
|
||
f.countLabel = vcl.NewLabel(f)
|
||
f.countLabel.SetParent(f)
|
||
f.countLabel.SetLeft(746)
|
||
f.countLabel.SetTop(60)
|
||
f.countLabel.SetCaption("数�:")
|
||
f.countLabel.Font().SetSize(14)
|
||
|
||
f.countValue = vcl.NewLabel(f)
|
||
f.countValue.SetParent(f)
|
||
f.countValue.SetLeft(800)
|
||
f.countValue.SetTop(60)
|
||
f.countValue.SetWidth(60)
|
||
f.countValue.SetCaption("--")
|
||
f.countValue.Font().SetSize(14)
|
||
|
||
f.statusLabel2 = vcl.NewLabel(f)
|
||
f.statusLabel2.SetParent(f)
|
||
f.statusLabel2.SetLeft(868)
|
||
f.statusLabel2.SetTop(60)
|
||
f.statusLabel2.SetCaption("状�")
|
||
f.statusLabel2.Font().SetSize(14)
|
||
|
||
f.statusValue = vcl.NewLabel(f)
|
||
f.statusValue.SetParent(f)
|
||
f.statusValue.SetLeft(918)
|
||
f.statusValue.SetTop(60)
|
||
f.statusValue.SetWidth(80)
|
||
f.statusValue.SetCaption("--")
|
||
f.statusValue.Font().SetSize(14)
|
||
|
||
f.timeLabel = vcl.NewLabel(f)
|
||
f.timeLabel.SetParent(f)
|
||
f.timeLabel.SetLeft(1006)
|
||
f.timeLabel.SetTop(60)
|
||
f.timeLabel.SetCaption("创建时间:")
|
||
f.timeLabel.Font().SetSize(14)
|
||
|
||
f.timeValue = vcl.NewLabel(f)
|
||
f.timeValue.SetParent(f)
|
||
f.timeValue.SetLeft(1100)
|
||
f.timeValue.SetTop(60)
|
||
f.timeValue.SetWidth(170)
|
||
f.timeValue.SetCaption("--")
|
||
f.timeValue.Font().SetSize(14)
|
||
}
|
||
|
||
func (f *MainForm) initModuleC(workpieceType string) {
|
||
for _, row := range f.rows {
|
||
if row.panel != nil {
|
||
row.panel.Free()
|
||
}
|
||
}
|
||
f.rows = nil
|
||
|
||
defs := squareItemDefs
|
||
if workpieceType == "circle" {
|
||
defs = circleItemDefs
|
||
}
|
||
|
||
f.collector = app.NewCollector(defs)
|
||
f.rows = make([]itemRow, len(defs))
|
||
|
||
for i := 0; i < len(defs); i++ {
|
||
row := itemRow{}
|
||
row.panel = vcl.NewPanel(f)
|
||
row.panel.SetParent(f)
|
||
row.panel.SetBevelOuter(types.BvRaised)
|
||
row.panel.SetColor(colors.ClBtnFace)
|
||
|
||
row.name = vcl.NewLabel(row.panel)
|
||
row.name.SetParent(row.panel)
|
||
row.name.SetCaption(defs[i])
|
||
row.name.Font().SetSize(nameSize)
|
||
row.name.SetAlignment(types.TaCenter)
|
||
|
||
row.value = vcl.NewLabel(row.panel)
|
||
row.value.SetParent(row.panel)
|
||
row.value.SetCaption("----")
|
||
row.value.Font().SetSize(valSize)
|
||
row.value.SetAlignment(types.TaCenter)
|
||
row.value.SetLayout(types.TlCenter)
|
||
|
||
idx := i
|
||
row.value.SetOnClick(func(s vcl.IObject) {
|
||
initial, _ := f.collector.DisplayValue(idx, f.currentUnit())
|
||
if initial == "----" {
|
||
initial = ""
|
||
}
|
||
val, ok := showNumKeyPad(f, initial)
|
||
if ok {
|
||
v, err := strconv.ParseFloat(val, 64)
|
||
if err == nil {
|
||
mm := v
|
||
if f.rbInch.Checked() {
|
||
mm = v * app.MmPerInch
|
||
}
|
||
f.collector.SetValue(idx, mm)
|
||
f.refreshRows()
|
||
}
|
||
}
|
||
})
|
||
|
||
row.unit = vcl.NewLabel(row.panel)
|
||
row.unit.SetParent(row.panel)
|
||
row.unit.SetCaption(f.currentUnitLabel())
|
||
row.unit.Font().SetSize(unitSize)
|
||
row.unit.SetAlignment(types.TaCenter)
|
||
|
||
row.panel.SetOnClick(func(s vcl.IObject) { f.onRowClick(idx) })
|
||
|
||
f.rows[i] = row
|
||
}
|
||
f.currentType = workpieceType
|
||
}
|
||
|
||
func (f *MainForm) initModuleD() {
|
||
f.bottomLabel = vcl.NewLabel(f)
|
||
f.bottomLabel.SetParent(f)
|
||
f.bottomLabel.SetCaption("�列� - 末帧: -")
|
||
f.bottomLabel.Font().SetSize(12)
|
||
|
||
f.workpieceNoEdit = vcl.NewEdit(f)
|
||
f.workpieceNoEdit.SetParent(f)
|
||
f.workpieceNoEdit.SetWidth(180)
|
||
f.workpieceNoEdit.SetHeight(44)
|
||
f.workpieceNoEdit.SetTextHint("请输入工件�")
|
||
f.workpieceNoEdit.Font().SetSize(14)
|
||
f.workpieceNoEdit.SetOnExit(f.onWorkpieceNoExit)
|
||
|
||
f.filterBtn = vcl.NewButton(f)
|
||
f.filterBtn.SetParent(f)
|
||
f.filterBtn.SetWidth(44)
|
||
f.filterBtn.SetHeight(44)
|
||
f.filterBtn.SetCaption("�")
|
||
f.filterBtn.Font().SetSize(14)
|
||
f.filterBtn.SetOnClick(f.onFilter)
|
||
|
||
f.exportBtn = vcl.NewButton(f)
|
||
f.exportBtn.SetParent(f)
|
||
f.exportBtn.SetWidth(120)
|
||
f.exportBtn.SetHeight(44)
|
||
f.exportBtn.SetCaption("导出")
|
||
f.exportBtn.Font().SetSize(14)
|
||
f.exportBtn.SetOnClick(f.onExport)
|
||
|
||
f.clearBtn = vcl.NewButton(f)
|
||
f.clearBtn.SetParent(f)
|
||
f.clearBtn.SetWidth(120)
|
||
f.clearBtn.SetHeight(44)
|
||
f.clearBtn.SetCaption("清除")
|
||
f.clearBtn.Font().SetSize(14)
|
||
f.clearBtn.SetOnClick(f.onClear)
|
||
|
||
f.submitBtn = vcl.NewButton(f)
|
||
f.submitBtn.SetParent(f)
|
||
f.submitBtn.SetWidth(120)
|
||
f.submitBtn.SetHeight(44)
|
||
f.submitBtn.SetCaption("�交")
|
||
f.submitBtn.Font().SetSize(14)
|
||
f.submitBtn.SetOnClick(f.onSubmit)
|
||
|
||
f.histView = vcl.NewListView(f)
|
||
f.histView.SetParent(f)
|
||
f.histView.SetViewStyle(types.VsReport)
|
||
f.histView.SetReadOnly(true)
|
||
f.histView.SetRowSelect(true)
|
||
f.histView.SetGridLines(true)
|
||
f.histView.Font().SetSize(12)
|
||
f.refreshHistoryColumns()
|
||
}
|
||
|
||
func (f *MainForm) refreshHistoryColumns() {
|
||
items := f.histView.Items()
|
||
items.BeginUpdate()
|
||
defer items.EndUpdate()
|
||
|
||
for i := f.histView.Columns().Count() - 1; i >= 0; i-- {
|
||
f.histView.Columns().Delete(i)
|
||
}
|
||
|
||
if f.currentType == "circle" {
|
||
cols := []string{"时间", "工��, "工件�, "分类", "��直径 A", "��轮廓�B", "���置 C"}
|
||
for _, c := range cols {
|
||
col := f.histView.Columns().Add()
|
||
col.SetCaption(c)
|
||
}
|
||
} else {
|
||
cols := []string{"时间", "工��, "工件�, "分类", "弧��径 B"}
|
||
for _, c := range cols {
|
||
col := f.histView.Columns().Add()
|
||
col.SetCaption(c)
|
||
}
|
||
}
|
||
}
|
||
|
||
func (f *MainForm) currentUnit() app.Unit {
|
||
if f.rbInch.Checked() {
|
||
return app.UnitInch
|
||
}
|
||
return app.UnitMM
|
||
}
|
||
|
||
func (f *MainForm) currentUnitLabel() string {
|
||
if f.rbInch.Checked() {
|
||
return "inch"
|
||
}
|
||
return "mm"
|
||
}
|
||
|
||
func (f *MainForm) onUnitChange(sender vcl.IObject) {
|
||
for _, row := range f.rows {
|
||
row.unit.SetCaption(f.currentUnitLabel())
|
||
}
|
||
f.refreshRows()
|
||
}
|
||
|
||
func (f *MainForm) onAutoStartToggle(sender vcl.IObject) {
|
||
if f.autoStartChk.Checked() {
|
||
if err := autostart.Enable(injectedAppName); err != nil {
|
||
vcl.ShowMessage("设置开机�动失� " + err.Error())
|
||
f.autoStartChk.SetChecked(false)
|
||
}
|
||
} else {
|
||
if err := autostart.Disable(injectedAppName); err != nil {
|
||
vcl.ShowMessage("�消开机�动失� " + err.Error())
|
||
f.autoStartChk.SetChecked(true)
|
||
}
|
||
}
|
||
}
|
||
|
||
func (f *MainForm) onRowClick(i int) {
|
||
f.collector.SetCurrent(i)
|
||
f.refreshRows()
|
||
}
|
||
|
||
func (f *MainForm) onQueryWorkOrder(sender vcl.IObject) {
|
||
workOrderNo := f.workOrderNoEdit.Text()
|
||
if workOrderNo == "" {
|
||
vcl.ShowMessage("请输入工��")
|
||
return
|
||
}
|
||
|
||
f.queryBtn.SetEnabled(false)
|
||
go func() {
|
||
ctx := context.Background()
|
||
info, err := f.apiClient.QueryWorkOrder(ctx, workOrderNo)
|
||
vcl.ThreadSync(func() {
|
||
f.queryBtn.SetEnabled(true)
|
||
if err != nil {
|
||
vcl.ShowMessage("未查询到工å�•ä¿¡æ�¯ï¼Œè¯·æ£€æŸ¥å·¥å�•å�·æ˜¯å�¦æ£ç¡®")
|
||
f.currentWorkOrder = nil
|
||
f.countValue.SetCaption("--")
|
||
f.statusValue.SetCaption("--")
|
||
f.timeValue.SetCaption("--")
|
||
return
|
||
}
|
||
f.currentWorkOrder = info
|
||
f.updateWorkOrderInfo(info)
|
||
})
|
||
}()
|
||
}
|
||
|
||
func (f *MainForm) updateWorkOrderInfo(info *api.WorkOrderInfo) {
|
||
f.typeCombo.SetItemIndex(0)
|
||
if info.WorkpieceType == "circle" || info.WorkpieceType == "GENERAL" {
|
||
f.typeCombo.SetItemIndex(1)
|
||
}
|
||
f.countValue.SetCaption(strconv.Itoa(info.WorkpieceCount))
|
||
f.statusValue.SetCaption(info.StatusName)
|
||
f.timeValue.SetCaption(info.CreatedAt)
|
||
|
||
displayType := info.WorkpieceType
|
||
if displayType == "GENERAL" {
|
||
displayType = "circle"
|
||
}
|
||
if displayType != "" && displayType != f.currentType {
|
||
f.initModuleC(displayType)
|
||
f.refreshHistoryColumns()
|
||
f.relayout()
|
||
}
|
||
}
|
||
|
||
func (f *MainForm) onWorkpieceNoExit(sender vcl.IObject) {
|
||
f.onFilter(nil)
|
||
}
|
||
|
||
func (f *MainForm) onFilter(sender vcl.IObject) {
|
||
workOrderNo := f.workOrderNoEdit.Text()
|
||
workpieceNo := f.workpieceNoEdit.Text()
|
||
|
||
f.submitBtn.SetEnabled(false)
|
||
go func() {
|
||
ctx := context.Background()
|
||
resp, err := f.apiClient.QueryMeasurements(ctx, workOrderNo, workpieceNo, 1, 100)
|
||
vcl.ThreadSync(func() {
|
||
f.submitBtn.SetEnabled(true)
|
||
if err != nil {
|
||
return
|
||
}
|
||
f.updateHistoryTable(resp.List)
|
||
})
|
||
}()
|
||
}
|
||
|
||
func (f *MainForm) updateHistoryTable(records []api.MeasurementRecord) {
|
||
items := f.histView.Items()
|
||
items.BeginUpdate()
|
||
defer items.EndUpdate()
|
||
items.Clear()
|
||
|
||
for _, r := range records {
|
||
item := f.histView.Items().Add()
|
||
item.SetCaption(formatTime(r.CreatedAt))
|
||
item.SubItems().Add(r.WorkOrderNo)
|
||
item.SubItems().Add(r.WorkpieceNo)
|
||
item.SubItems().Add(r.Category)
|
||
|
||
if f.currentType == "circle" {
|
||
item.SubItems().Add(fmt.Sprintf("%.3f", r.ValueA))
|
||
item.SubItems().Add(fmt.Sprintf("%.3f", r.ValueB))
|
||
item.SubItems().Add(fmt.Sprintf("%.3f", r.ValueC))
|
||
} else {
|
||
item.SubItems().Add(fmt.Sprintf("%.3f", r.ValueA))
|
||
}
|
||
}
|
||
}
|
||
|
||
func formatTime(s string) string {
|
||
for _, layout := range []string{time.RFC3339, "2006-01-02 15:04:05"} {
|
||
if t, err := time.Parse(layout, s); err == nil {
|
||
return t.Format("01-02 15:04:05")
|
||
}
|
||
}
|
||
return s
|
||
}
|
||
|
||
func (f *MainForm) onExport(sender vcl.IObject) {
|
||
items := f.histView.Items()
|
||
if items.Count() == 0 {
|
||
vcl.ShowMessage("没有数��导�)
|
||
return
|
||
}
|
||
|
||
filename := time.Now().Format("20060102_150405") + ".csv"
|
||
desktop, _ := os.UserHomeDir()
|
||
filepath := desktop + "/Desktop/" + filename
|
||
file, err := os.Create(filepath)
|
||
if err != nil {
|
||
vcl.ShowMessage("创建文件失败: " + err.Error())
|
||
return
|
||
}
|
||
defer file.Close()
|
||
|
||
header := "时间,工��工件�分类"
|
||
if f.currentType == "circle" {
|
||
header += ",��直径 A,��轮廓�B,���置 C"
|
||
} else {
|
||
header += ",弧��径 B"
|
||
}
|
||
file.WriteString(header + "\n")
|
||
|
||
for i := int32(0); i < items.Count(); i++ {
|
||
item := items.Item(i)
|
||
line := fmt.Sprintf("%s,%s,%s,%s",
|
||
escapeCSV(item.Caption()),
|
||
escapeCSV(item.SubItems().Strings(0)),
|
||
escapeCSV(item.SubItems().Strings(1)),
|
||
escapeCSV(item.SubItems().Strings(2)))
|
||
if f.currentType == "circle" {
|
||
line += fmt.Sprintf(",%s,%s,%s",
|
||
item.SubItems().Strings(3),
|
||
item.SubItems().Strings(4),
|
||
item.SubItems().Strings(5))
|
||
} else {
|
||
line += "," + item.SubItems().Strings(3)
|
||
}
|
||
file.WriteString(line + "\n")
|
||
}
|
||
|
||
vcl.ShowMessage("导出�功: " + filepath)
|
||
}
|
||
|
||
func escapeCSV(s string) string {
|
||
if s == "" {
|
||
return ""
|
||
}
|
||
if contains(s, ",") || contains(s, "\"") || contains(s, "\n") {
|
||
return "\"" + replaceAll(s, "\"", "\"\"") + "\""
|
||
}
|
||
return s
|
||
}
|
||
|
||
func contains(s, substr string) bool {
|
||
if len(s) < len(substr) {
|
||
return false
|
||
}
|
||
if s == substr {
|
||
return true
|
||
}
|
||
for i := 0; i <= len(s)-len(substr); i++ {
|
||
if s[i:i+len(substr)] == substr {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func replaceAll(s, old, new string) string {
|
||
result := ""
|
||
for len(s) > 0 {
|
||
if len(s) >= len(old) && s[:len(old)] == old {
|
||
result += new
|
||
s = s[len(old):]
|
||
} else {
|
||
result += s[:1]
|
||
s = s[1:]
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
func (f *MainForm) onClear(sender vcl.IObject) {
|
||
r := vcl.MessageDlg("确认清空当�工件数�?", types.MtConfirmation, types.MbYes, types.MbNo)
|
||
if r == types.MrYes {
|
||
f.collector.Clear()
|
||
f.refreshRows()
|
||
}
|
||
}
|
||
|
||
func (f *MainForm) onSubmit(sender vcl.IObject) {
|
||
workOrderNo := f.workOrderNoEdit.Text()
|
||
if workOrderNo == "" {
|
||
vcl.ShowMessage("请先输入工��)
|
||
return
|
||
}
|
||
if f.currentWorkOrder == nil {
|
||
vcl.ShowMessage("请先查询工�")
|
||
return
|
||
}
|
||
|
||
workpieceNo := f.workpieceNoEdit.Text()
|
||
if workpieceNo == "" {
|
||
vcl.ShowMessage("请输入工件�")
|
||
return
|
||
}
|
||
|
||
if !f.collector.AllFilled() {
|
||
vcl.ShowMessage("请完�所有测�)
|
||
return
|
||
}
|
||
|
||
workpieceType := f.currentWorkOrder.WorkpieceType
|
||
if workpieceType == "GENERAL" {
|
||
workpieceType = "circle"
|
||
}
|
||
|
||
req := api.SubmitRequest{
|
||
WorkOrderNo: workOrderNo,
|
||
WorkpieceNo: workpieceNo,
|
||
Category: "",
|
||
WorkpieceType: workpieceType,
|
||
ValueA: f.collector.Value(0),
|
||
Unit: f.currentUnitLabel(),
|
||
Serial: f.selectedPort(),
|
||
Timestamp: time.Now().Format("2006-01-02 15:04:05"),
|
||
}
|
||
if workpieceType == "circle" {
|
||
vb := f.collector.Value(1)
|
||
vc := f.collector.Value(2)
|
||
req.ValueB = &vb
|
||
req.ValueC = &vc
|
||
}
|
||
|
||
f.submitBtn.SetEnabled(false)
|
||
go func() {
|
||
ctx := context.Background()
|
||
_, err := f.apiClient.SubmitMeasurement(ctx, req)
|
||
vcl.ThreadSync(func() {
|
||
f.submitBtn.SetEnabled(true)
|
||
if err != nil {
|
||
vcl.ShowMessage("�交失败: " + err.Error())
|
||
return
|
||
}
|
||
vcl.ShowMessage("�交�功")
|
||
f.collector.Clear()
|
||
f.refreshRows()
|
||
f.onFilter(nil)
|
||
})
|
||
}()
|
||
}
|
||
|
||
func (f *MainForm) onConnect(sender vcl.IObject) {
|
||
if f.serial != nil {
|
||
_ = f.serial.Close()
|
||
f.serial = nil
|
||
f.statusLabel.SetCaption("�未连接")
|
||
f.statusLabel.Font().SetColor(colors.ClBtnFace)
|
||
f.connectBtn.SetCaption("连接")
|
||
f.reconnT.SetEnabled(false)
|
||
return
|
||
}
|
||
f.tryConnectSerial()
|
||
}
|
||
|
||
func (f *MainForm) selectedPort() string {
|
||
idx := f.portCombo.ItemIndex()
|
||
if idx < 0 {
|
||
return ""
|
||
}
|
||
var s string
|
||
f.portCombo.GetTextBuf(&s, 64)
|
||
return s
|
||
}
|
||
|
||
func (f *MainForm) refreshPortList() {
|
||
ports, err := serial.GetPortsList()
|
||
if err != nil {
|
||
log.Printf("列出串�失败: %v", err)
|
||
return
|
||
}
|
||
f.portCombo.Clear()
|
||
sel := int32(-1)
|
||
for i, p := range ports {
|
||
f.portCombo.Items().Add(p)
|
||
if p == f.cfg.SerialPort {
|
||
sel = int32(i)
|
||
}
|
||
}
|
||
if sel >= 0 {
|
||
f.portCombo.SetItemIndex(sel)
|
||
} else if len(ports) > 0 {
|
||
f.portCombo.SetItemIndex(0)
|
||
}
|
||
}
|
||
|
||
func (f *MainForm) onRefreshPorts(sender vcl.IObject) {
|
||
f.refreshPortList()
|
||
}
|
||
|
||
func (f *MainForm) tryConnectSerial() {
|
||
if f.serial != nil {
|
||
return
|
||
}
|
||
name := f.selectedPort()
|
||
if name == "" {
|
||
f.statusLabel.SetCaption("�未选择串�")
|
||
f.statusLabel.Font().SetColor(colors.ClRed)
|
||
return
|
||
}
|
||
sp, err := serialport.Open(name, f.cfg.BaudRate)
|
||
if err != nil {
|
||
f.statusLabel.SetCaption(fmt.Sprintf("%s �未连接", name))
|
||
f.statusLabel.Font().SetColor(colors.ClRed)
|
||
f.reconnT.SetEnabled(true)
|
||
return
|
||
}
|
||
f.serial = sp
|
||
f.statusLabel.SetCaption(fmt.Sprintf("%s �已连接", name))
|
||
f.statusLabel.Font().SetColor(colors.ClGreen)
|
||
f.connectBtn.SetCaption("æ–å¼€")
|
||
f.reconnT.SetEnabled(false)
|
||
go f.consumeFrames()
|
||
}
|
||
|
||
func (f *MainForm) consumeFrames() {
|
||
for {
|
||
select {
|
||
case fr, ok := <-f.serial.Frames():
|
||
if !ok {
|
||
return
|
||
}
|
||
vcl.ThreadSync(func() {
|
||
f.collector.ApplyFrame(fr)
|
||
f.refreshRows()
|
||
})
|
||
case err, ok := <-f.serial.Errors():
|
||
if !ok {
|
||
return
|
||
}
|
||
log.Printf("串�错误: %v", err)
|
||
vcl.ThreadSync(func() {
|
||
f.statusLabel.SetCaption("â—�å·²æ–å¼€")
|
||
f.statusLabel.Font().SetColor(colors.ClRed)
|
||
f.serial = nil
|
||
f.connectBtn.SetCaption("连接")
|
||
f.reconnT.SetEnabled(true)
|
||
})
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
func (f *MainForm) refreshRows() {
|
||
u := f.currentUnit()
|
||
cur := f.collector.CurrentIndex()
|
||
for i, row := range f.rows {
|
||
s, _ := f.collector.DisplayValue(i, u)
|
||
row.value.SetCaption(s)
|
||
row.unit.SetCaption(f.currentUnitLabel())
|
||
if i == cur {
|
||
row.panel.SetColor(types.TColor(0x00FFE0C0))
|
||
} else {
|
||
row.panel.SetColor(colors.ClBtnFace)
|
||
}
|
||
}
|
||
sn, seq := f.collector.Serial()
|
||
if sn == 0 {
|
||
f.bottomLabel.SetCaption("�列� - 末帧: -")
|
||
} else {
|
||
f.bottomLabel.SetCaption(fmt.Sprintf("�列� %d 末帧 seq=%d %s",
|
||
sn, seq, time.Now().Format("15:04:05")))
|
||
}
|
||
}
|
||
|
||
func (f *MainForm) onResize(sender vcl.IObject) {
|
||
f.relayout()
|
||
}
|
||
|
||
func (f *MainForm) relayout() {
|
||
cw := f.ClientWidth()
|
||
ch := f.ClientHeight()
|
||
if cw <= 0 || ch <= 0 {
|
||
return
|
||
}
|
||
|
||
sx := float64(cw) / float64(baseW)
|
||
sy := float64(ch) / float64(baseH)
|
||
|
||
val := clamp(int32(float64(valSize)*sy), minFont, maxFont)
|
||
name := clamp(int32(float64(nameSize)*sy), minFont/2, maxFont/2)
|
||
unit := clamp(int32(float64(unitSize)*sy), minFont/2, maxFont/2)
|
||
|
||
gap := int32(float64(gapX) * sx)
|
||
if gap < 4 {
|
||
gap = 4
|
||
}
|
||
|
||
ptop := int32(108)
|
||
pH := int32(float64(panelH) * sy)
|
||
if pH < 100 {
|
||
pH = 100
|
||
}
|
||
|
||
var pW int32
|
||
if len(f.rows) == 1 {
|
||
pW = cw - 32
|
||
} else {
|
||
pW = (cw - gap*(int32(len(f.rows))+1) - 32) / int32(len(f.rows))
|
||
}
|
||
if pW < 100 {
|
||
pW = 100
|
||
}
|
||
|
||
for i, row := range f.rows {
|
||
left := int32(16) + int32(i)*(pW+gap)
|
||
row.panel.SetLeft(left)
|
||
row.panel.SetTop(ptop)
|
||
row.panel.SetWidth(pW)
|
||
row.panel.SetHeight(pH)
|
||
|
||
nameH := name + 12
|
||
unitH := unit + 12
|
||
vH := val + 8
|
||
mid := pH / 2
|
||
valTop := mid - vH/2
|
||
if valTop < 8+nameH {
|
||
valTop = 8 + nameH
|
||
}
|
||
if valTop+vH > pH-8-unitH {
|
||
valTop = pH - 8 - unitH - vH
|
||
}
|
||
|
||
row.name.SetLeft(8)
|
||
row.name.SetTop(8)
|
||
row.name.SetWidth(pW - 16)
|
||
row.name.SetHeight(nameH)
|
||
row.name.Font().SetSize(name)
|
||
|
||
row.value.SetLeft(8)
|
||
row.value.SetTop(valTop)
|
||
row.value.SetWidth(pW - 16)
|
||
row.value.SetHeight(vH)
|
||
row.value.Font().SetSize(val)
|
||
|
||
row.unit.SetLeft(8)
|
||
row.unit.SetTop(pH - 8 - unitH)
|
||
row.unit.SetWidth(pW - 16)
|
||
row.unit.SetHeight(unitH)
|
||
row.unit.Font().SetSize(unit)
|
||
}
|
||
|
||
btnH := int32(float64(44) * sy)
|
||
if btnH < 36 {
|
||
btnH = 36
|
||
}
|
||
btnW := int32(float64(120) * sx)
|
||
if btnW < 80 {
|
||
btnW = 80
|
||
}
|
||
btnFont := clamp(int32(float64(14)*sy), 12, 28)
|
||
|
||
bottomY := ptop + pH + 12
|
||
f.bottomLabel.SetLeft(16)
|
||
f.bottomLabel.SetTop(bottomY)
|
||
f.bottomLabel.SetWidth(cw - 32)
|
||
|
||
btnTop := ptop + pH + 36
|
||
f.workpieceNoEdit.SetLeft(16)
|
||
f.workpieceNoEdit.SetTop(btnTop)
|
||
f.workpieceNoEdit.SetWidth(int32(float64(180) * sx))
|
||
f.workpieceNoEdit.SetHeight(btnH)
|
||
|
||
f.filterBtn.SetLeft(16 + int32(float64(180)*sx) + 4)
|
||
f.filterBtn.SetTop(btnTop)
|
||
f.filterBtn.SetWidth(btnH)
|
||
f.filterBtn.SetHeight(btnH)
|
||
|
||
f.exportBtn.SetLeft(cw - 3*(btnW+16) - 16)
|
||
f.exportBtn.SetTop(btnTop)
|
||
f.exportBtn.SetWidth(btnW)
|
||
f.exportBtn.SetHeight(btnH)
|
||
f.exportBtn.Font().SetSize(btnFont)
|
||
|
||
f.clearBtn.SetLeft(cw - 2*(btnW+16) - 16)
|
||
f.clearBtn.SetTop(btnTop)
|
||
f.clearBtn.SetWidth(btnW)
|
||
f.clearBtn.SetHeight(btnH)
|
||
f.clearBtn.Font().SetSize(btnFont)
|
||
|
||
f.submitBtn.SetLeft(cw - btnW - 16)
|
||
f.submitBtn.SetTop(btnTop)
|
||
f.submitBtn.SetWidth(btnW)
|
||
f.submitBtn.SetHeight(btnH)
|
||
f.submitBtn.Font().SetSize(btnFont)
|
||
|
||
histTop := btnTop + btnH + 8
|
||
histH := ch - histTop - 8
|
||
if histH < 60 {
|
||
histH = 60
|
||
}
|
||
f.histView.SetLeft(16)
|
||
f.histView.SetTop(histTop)
|
||
f.histView.SetWidth(cw - 32)
|
||
f.histView.SetHeight(histH)
|
||
|
||
histFont := clamp(int32(float64(12)*sy), 10, 20)
|
||
f.histView.Font().SetSize(histFont)
|
||
|
||
var colWidths []int32
|
||
if f.currentType == "circle" {
|
||
colWidths = []int32{
|
||
int32(float64(130) * sx),
|
||
int32(float64(130) * sx),
|
||
int32(float64(110) * sx),
|
||
int32(float64(90) * sx),
|
||
int32(float64(120) * sx),
|
||
int32(float64(120) * sx),
|
||
}
|
||
} else {
|
||
colWidths = []int32{
|
||
int32(float64(130) * sx),
|
||
int32(float64(130) * sx),
|
||
int32(float64(110) * sx),
|
||
int32(float64(90) * sx),
|
||
}
|
||
}
|
||
|
||
used := int32(0)
|
||
for _, w := range colWidths {
|
||
used += w
|
||
}
|
||
lastW := cw - 32 - used
|
||
if lastW < 100 {
|
||
lastW = 100
|
||
}
|
||
|
||
for i, w := range append(colWidths, lastW) {
|
||
if int32(i) < f.histView.Columns().Count() {
|
||
f.histView.Column(int32(i)).SetWidth(w)
|
||
}
|
||
}
|
||
}
|
||
|
||
func clamp(v, lo, hi int32) int32 {
|
||
if v < lo {
|
||
return lo
|
||
}
|
||
if v > hi {
|
||
return hi
|
||
}
|
||
return v
|
||
}
|
||
|
||
func (f *MainForm) OnFormClose(sender vcl.IObject, action *types.TCloseAction) {
|
||
r := vcl.MessageDlg("确定�退出程��?", types.MtConfirmation, types.MbYes, types.MbNo)
|
||
if r != types.MrYes {
|
||
*action = types.CaNone
|
||
return
|
||
}
|
||
if f.serial != nil {
|
||
_ = f.serial.Close()
|
||
}
|
||
}
|