|
|
@@ -26,6 +26,8 @@ import java.util.concurrent.TimeUnit;
|
|
|
* 扫码打标签 —— 主界面
|
|
|
*
|
|
|
* 扫客户码 → 直接打 TSC 标签(TSCLIB.dll),不调服务端、不提交工位结果。
|
|
|
+ * 补打:再扫一次同一个码,或在「最近记录」里选中后点 [补打选中]。
|
|
|
+ * 换纸:装好纸后点 [换纸校准],让打印机重新测量标签间隙。
|
|
|
*/
|
|
|
public class MesClient extends JFrame {
|
|
|
|
|
|
@@ -33,6 +35,20 @@ public class MesClient extends JFrame {
|
|
|
private static final Color OK_COLOR = new Color(0, 130, 0);
|
|
|
private static final Color BUSY_COLOR = new Color(60, 60, 200);
|
|
|
|
|
|
+ /** 最近记录一行:显示文本 + 对应客户码(补打用) */
|
|
|
+ private static final class RecentItem {
|
|
|
+ final String sn;
|
|
|
+ final String text;
|
|
|
+
|
|
|
+ RecentItem(String sn, String text) {
|
|
|
+ this.sn = sn;
|
|
|
+ this.text = text;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String toString() { return text; }
|
|
|
+ }
|
|
|
+
|
|
|
private volatile LabelScanManager labelMgr;
|
|
|
private ScheduledExecutorService statusRefresher;
|
|
|
|
|
|
@@ -40,16 +56,18 @@ public class MesClient extends JFrame {
|
|
|
private JLabel resultLabel;
|
|
|
private JLabel printerLabel;
|
|
|
private JLabel statsLabel;
|
|
|
- private final DefaultListModel<String> recentModel = new DefaultListModel<>();
|
|
|
+ private JButton calibrateBtn;
|
|
|
+ private JList<RecentItem> recentList;
|
|
|
+ private final DefaultListModel<RecentItem> recentModel = new DefaultListModel<>();
|
|
|
|
|
|
// 本次运行计数,只在 EDT 改
|
|
|
private int okCount = 0;
|
|
|
private int failCount = 0;
|
|
|
|
|
|
public MesClient() {
|
|
|
- super("扫码打标签 v2.0");
|
|
|
+ super("扫码打标签 v2.1");
|
|
|
setDefaultCloseOperation(EXIT_ON_CLOSE);
|
|
|
- setSize(760, 560);
|
|
|
+ setSize(820, 580);
|
|
|
setLocationRelativeTo(null);
|
|
|
|
|
|
JPanel root = new JPanel(new BorderLayout(8, 8));
|
|
|
@@ -80,11 +98,9 @@ public class MesClient extends JFrame {
|
|
|
JPanel right = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 0));
|
|
|
statsLabel = new JLabel("成功 0 / 失败 0");
|
|
|
right.add(statsLabel);
|
|
|
- JButton settingBtn = new JButton("标签机设置");
|
|
|
- // 按钮不抢焦点:按钮有焦点时扫码枪的回车会触发按钮
|
|
|
- settingBtn.setFocusable(false);
|
|
|
- settingBtn.addActionListener(e -> openPrinterSetting());
|
|
|
- right.add(settingBtn);
|
|
|
+ calibrateBtn = newButton("换纸校准", e -> onCalibrate());
|
|
|
+ right.add(calibrateBtn);
|
|
|
+ right.add(newButton("标签机设置", e -> openPrinterSetting()));
|
|
|
p.add(right, BorderLayout.EAST);
|
|
|
return p;
|
|
|
}
|
|
|
@@ -115,15 +131,35 @@ public class MesClient extends JFrame {
|
|
|
north.add(resultLabel, BorderLayout.SOUTH);
|
|
|
p.add(north, BorderLayout.NORTH);
|
|
|
|
|
|
- JList<String> recentList = new JList<>(recentModel);
|
|
|
+ recentList = new JList<>(recentModel);
|
|
|
+ // 不可聚焦:鼠标点选仍可用,但焦点留在扫码框
|
|
|
recentList.setFocusable(false);
|
|
|
+ recentList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
|
|
recentList.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
|
|
|
JScrollPane sp = new JScrollPane(recentList);
|
|
|
- sp.setBorder(new TitledBorder("最近记录"));
|
|
|
- p.add(sp, BorderLayout.CENTER);
|
|
|
+
|
|
|
+ JPanel recentBar = new JPanel(new BorderLayout(6, 0));
|
|
|
+ JLabel hint = new JLabel("补打:再扫一次同一个码,或选中下面一条后点右边按钮");
|
|
|
+ hint.setForeground(Color.GRAY);
|
|
|
+ recentBar.add(hint, BorderLayout.CENTER);
|
|
|
+ recentBar.add(newButton("补打选中", e -> reprintSelected()), BorderLayout.EAST);
|
|
|
+
|
|
|
+ JPanel recentPanel = new JPanel(new BorderLayout(0, 4));
|
|
|
+ recentPanel.setBorder(new TitledBorder("最近记录(本次运行)"));
|
|
|
+ recentPanel.add(recentBar, BorderLayout.NORTH);
|
|
|
+ recentPanel.add(sp, BorderLayout.CENTER);
|
|
|
+ p.add(recentPanel, BorderLayout.CENTER);
|
|
|
return p;
|
|
|
}
|
|
|
|
|
|
+ /** 按钮统一不抢焦点:按钮有焦点时扫码枪的回车会触发按钮 */
|
|
|
+ private static JButton newButton(String text, java.awt.event.ActionListener action) {
|
|
|
+ JButton b = new JButton(text);
|
|
|
+ b.setFocusable(false);
|
|
|
+ b.addActionListener(action);
|
|
|
+ return b;
|
|
|
+ }
|
|
|
+
|
|
|
// ============ 生命周期 ============
|
|
|
|
|
|
/** 初始化业务组件(构造完 UI 后调用) */
|
|
|
@@ -181,9 +217,27 @@ public class MesClient extends JFrame {
|
|
|
private void onScan() {
|
|
|
String sn = LabelScanManager.normalize(scanField.getText());
|
|
|
scanField.setText("");
|
|
|
- if (sn.isEmpty() || labelMgr == null) return;
|
|
|
+ if (sn.isEmpty()) return;
|
|
|
AppLog.scan("标签", sn);
|
|
|
+ requestPrint(sn);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 最近记录里选中一条补打(EDT) */
|
|
|
+ private void reprintSelected() {
|
|
|
+ RecentItem item = recentList.getSelectedValue();
|
|
|
+ if (item == null) {
|
|
|
+ showResult(false, "✖ 请先在「最近记录」里选中要补打的一条");
|
|
|
+ } else if (item.sn == null) {
|
|
|
+ showResult(false, "✖ 选中的不是打印记录,请选带客户码的那条");
|
|
|
+ } else {
|
|
|
+ requestPrint(item.sn);
|
|
|
+ }
|
|
|
+ focusScan();
|
|
|
+ }
|
|
|
|
|
|
+ /** 扫码和补打共用:校验 → 防重 → 打过的弹补打确认 → 提交(EDT) */
|
|
|
+ private void requestPrint(String sn) {
|
|
|
+ if (labelMgr == null) return;
|
|
|
String err = labelMgr.validate(sn);
|
|
|
if (err != null) {
|
|
|
showResult(false, "✖ " + err);
|
|
|
@@ -234,18 +288,53 @@ public class MesClient extends JFrame {
|
|
|
if (ok) okCount++; else failCount++;
|
|
|
statsLabel.setText("成功 " + okCount + " / 失败 " + failCount);
|
|
|
showResult(ok && msg == null, text);
|
|
|
- addRecent(text);
|
|
|
+ addRecent(sn, text);
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+ // ============ 换纸校准 ============
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 换纸校准(EDT)。默认按钮是「取消」,防止弹框时扫码枪回车误触发走纸
|
|
|
+ */
|
|
|
+ private void onCalibrate() {
|
|
|
+ if (labelMgr == null) return;
|
|
|
+ String msg = "请确认:\n"
|
|
|
+ + "1. 新纸已装好,两边导纸挡板靠紧,标签从传感器下穿过\n"
|
|
|
+ + "2. 机盖已合上,标签机绿灯亮\n\n"
|
|
|
+ + "校准时标签机会自动走出几张空白标签,属正常现象。\n"
|
|
|
+ + "完成后扫码(或补打)一张确认位置。开始校准?";
|
|
|
+ Object[] options = {"开始校准", "取消"};
|
|
|
+ int r = JOptionPane.showOptionDialog(this, msg, "换纸校准",
|
|
|
+ JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[1]);
|
|
|
+ focusScan();
|
|
|
+ if (r != 0) return;
|
|
|
+
|
|
|
+ calibrateBtn.setEnabled(false);
|
|
|
+ resultLabel.setText("⋯ 正在校准,标签机会走出几张空白标签...");
|
|
|
+ resultLabel.setForeground(BUSY_COLOR);
|
|
|
+ labelMgr.calibrate((ok, err) -> SwingUtilities.invokeLater(() -> {
|
|
|
+ calibrateBtn.setEnabled(true);
|
|
|
+ String text = ok
|
|
|
+ ? "✔ 已发送校准,等标签机停止走纸后扫码打一张确认位置"
|
|
|
+ : "✖ 校准失败: " + err;
|
|
|
+ showResult(ok, text);
|
|
|
+ addRecent(null, text);
|
|
|
+ }));
|
|
|
+ }
|
|
|
+
|
|
|
+ // ============ 通用 ============
|
|
|
+
|
|
|
private void showResult(boolean ok, String text) {
|
|
|
resultLabel.setText(text);
|
|
|
resultLabel.setForeground(ok ? OK_COLOR : Color.RED);
|
|
|
if (!ok) Toolkit.getDefaultToolkit().beep();
|
|
|
}
|
|
|
|
|
|
- private void addRecent(String text) {
|
|
|
- recentModel.add(0, new SimpleDateFormat("HH:mm:ss").format(new Date()) + " " + text);
|
|
|
+ /** sn 为 null 表示非打印记录(如校准),补打时会提示无效 */
|
|
|
+ private void addRecent(String sn, String text) {
|
|
|
+ String line = new SimpleDateFormat("HH:mm:ss").format(new Date()) + " " + text;
|
|
|
+ recentModel.add(0, new RecentItem(sn, line));
|
|
|
while (recentModel.size() > RECENT_MAX) recentModel.remove(recentModel.size() - 1);
|
|
|
}
|
|
|
|
|
|
@@ -272,7 +361,8 @@ public class MesClient extends JFrame {
|
|
|
File dir = cfg.getAbsoluteFile().getParentFile();
|
|
|
if (dir != null && !dir.exists()) dir.mkdirs();
|
|
|
try (Writer w = new OutputStreamWriter(new FileOutputStream(cfg), StandardCharsets.UTF_8)) {
|
|
|
- w.write("# 标签机 Windows 打印机名\r\nlabel.printer.name=TSC TTP-244 PRO\r\n");
|
|
|
+ w.write("# 标签机 Windows 打印机名\r\nlabel.printer.name=TSC TTP-244 PRO\r\n"
|
|
|
+ + "# 标签整体偏移(毫米):上下正数往下、左右正数往右\r\nlabel.offset.y=0\r\nlabel.offset.x=0\r\n");
|
|
|
}
|
|
|
System.out.println("[MesClient] 已创建默认配置: " + cfg.getAbsolutePath());
|
|
|
} catch (Exception e) {
|