From 724be707ec15ac6558f1561366dd367d75f28bf7 Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期五, 16 十月 2026 18:00:49 +0800
Subject: [PATCH] chore(scripts): 新增二维码与联调诊断脚本
---
scripts/CompareQrRender.java | 82 +++++++++
scripts/diag-pill.cjs | 65 +++++++
scripts/split-hunks.py | 49 +++++
scripts/DecodeQr.java | 146 ++++++++++++++++
scripts/DiagnoseQrFailures.java | 91 ++++++++++
scripts/diag-login.cjs | 51 +++++
scripts/diag-bagcode.cjs | 50 +++++
7 files changed, 534 insertions(+), 0 deletions(-)
diff --git a/scripts/CompareQrRender.java b/scripts/CompareQrRender.java
new file mode 100644
index 0000000..bfd4da6
--- /dev/null
+++ b/scripts/CompareQrRender.java
@@ -0,0 +1,82 @@
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.util.EnumMap;
+import java.util.Map;
+
+/**
+ * 鍒ゅ畾"瑙d笉寮�鐨勬爣绛�"鍒板簳鏄爣绛惧潖浜嗭紝杩樻槸瑙g爜鍣ㄧ殑灞�闄�
+ *
+ * 鐢ㄦ硶锛歫ava CompareQrRender <鏍囩鐩綍> <鏂囦欢鍚�1> [鏂囦欢鍚�2 ...]
+ *
+ * 鍋氭硶锛氱敤涓庡鍑哄畬鍏ㄧ浉鍚岀殑缂栫爜鍙傛暟锛堢籂閿欑骇鍒� H銆侀潤鍖� 4銆佽竟闀� 260锛夐噸鏂扮敓鎴愬悓涓�鐮佸�肩殑浜岀淮鐮侊紝
+ * 鍐嶄笌鏍囩鍥剧墖涓殑浜岀淮鐮佸尯鍩熼�愬儚绱犳瘮瀵广��
+ * - 瀹屽叏涓�鑷� 鈫� 鏍囩鏄鐮佸�肩殑蹇犲疄娓叉煋锛屼簩缁寸爜鏁版嵁鏈夋晥锛岃В涓嶅嚭鏄В鐮佸櫒鐨勯棶棰�
+ * - 瀛樺湪宸紓 鈫� 鏍囩娓叉煋鐜妭纭疄鍑轰簡闂
+ */
+public class CompareQrRender {
+
+ /** 涓� QrCodeImageUtils 淇濇寔涓�鑷� */
+ private static final int PADDING = 16;
+ private static final int QR_SIZE = 260;
+ /** 涓� MesProQrLabelBuilder 涓�鑷达細浜岀淮鐮佸唴瀹逛负杩芥函缃戝潃 + code 鍙傛暟 */
+ private static final String TRACE_BASE = "http://192.168.0.10:48080/trace.html";
+
+ public static void main(String[] args) throws Exception {
+ File dir = new File(args[0]);
+ for (int i = 1; i < args.length; i++) {
+ File f = new File(dir, args[i]);
+ String name = args[i];
+ String code = name.endsWith(".png") ? name.substring(0, name.length() - 4) : name;
+ BufferedImage label = ImageIO.read(f);
+ if (label == null) {
+ System.out.println(name + " -> 鏃犳硶璇诲彇");
+ continue;
+ }
+ BufferedImage expect = generate(code);
+ BufferedImage actual = label.getSubimage(PADDING, PADDING, QR_SIZE, QR_SIZE);
+ if (expect.getWidth() != actual.getWidth() || expect.getHeight() != actual.getHeight()) {
+ System.out.println(name + " -> 灏哄涓嶄竴鑷达紝鏃犳硶姣斿");
+ continue;
+ }
+ long diff = 0;
+ for (int y = 0; y < QR_SIZE; y++) {
+ for (int x = 0; x < QR_SIZE; x++) {
+ if (expect.getRGB(x, y) != actual.getRGB(x, y)) {
+ diff++;
+ }
+ }
+ }
+ long total = (long) QR_SIZE * QR_SIZE;
+ System.out.printf("%s%n 閲嶆柊鐢熸垚涓庢爣绛句簩缁寸爜鍖哄煙閫愬儚绱犳瘮瀵癸細涓嶄竴鑷� %d / %d (%.4f%%)%n",
+ name, diff, total, diff * 100.0 / total);
+ System.out.println(" 鍒ゅ畾锛�" + (diff == 0
+ ? "鏍囩涓鸿鐮佸�肩殑蹇犲疄娓叉煋 鈫� 浜岀淮鐮佹暟鎹湁鏁堬紝瑙d笉鍑哄睘瑙g爜绔棶棰�"
+ : "鏍囩娓叉煋涓庨鏈熶笉绗� 鈫� 娓叉煋鐜妭瀛樺湪闂"));
+ }
+ }
+
+ private static BufferedImage generate(String code) throws Exception {
+ String content = TRACE_BASE + "?code=" + code;
+ Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
+ hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
+ hints.put(EncodeHintType.MARGIN, 4);
+ hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
+ BitMatrix matrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, QR_SIZE, QR_SIZE, hints);
+ int w = matrix.getWidth();
+ int h = matrix.getHeight();
+ BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ img.setRGB(x, y, matrix.get(x, y) ? 0x000000 : 0xFFFFFF);
+ }
+ }
+ return img;
+ }
+}
diff --git a/scripts/DecodeQr.java b/scripts/DecodeQr.java
new file mode 100644
index 0000000..61f9c0a
--- /dev/null
+++ b/scripts/DecodeQr.java
@@ -0,0 +1,146 @@
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.LuminanceSource;
+import com.google.zxing.MultiFormatReader;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.Result;
+import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
+import com.google.zxing.common.GlobalHistogramBinarizer;
+import com.google.zxing.common.HybridBinarizer;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 浜岀淮鐮佹爣绛捐В鐮佹牎楠�
+ *
+ * 鐢ㄦ硶锛歫ava DecodeQr <鏍囩鐩綍>
+ *
+ * 瀵规瘡寮犳爣绛惧垎鍒敤銆屾暣鍥俱�嶅拰銆岃鍒颁簩缁寸爜鍖哄煙銆嶄袱绉嶆柟寮忚В鐮侊細
+ * 鏁村浘澶辫触鑰岃鍥炬垚鍔燂紝璇存槑鏍囩鏈韩娌″潖锛屾槸涓嬫柟鏂囧瓧骞叉壈浜嗘娴嬪櫒锛屽睘瑙g爜绔棶棰橈紱
+ * 涓ょ鏂瑰紡閮藉け璐ワ紝鎵嶆槸鏍囩鐪熺殑涓嶅彲璇嗚銆�
+ */
+public class DecodeQr {
+
+ /** 鏍囩鎺掔増鍙傛暟锛岄』涓� QrCodeImageUtils 淇濇寔涓�鑷� */
+ private static final int PADDING = 16;
+ private static final int QR_SIZE = 260;
+ /** 瑁佸浘鏃跺悜澶栧鐣欑殑浣欓噺锛屼繚璇佷簩缁寸爜闈欏尯涓嶈鍒囨帀 */
+ private static final int CROP_MARGIN = 10;
+
+ public static void main(String[] args) throws Exception {
+ if (args.length == 0) {
+ System.out.println("鐢ㄦ硶: java DecodeQr <鏍囩鐩綍>");
+ return;
+ }
+ File dir = new File(args[0]);
+ File[] files = dir.listFiles((d, n) -> n.toLowerCase().endsWith(".png"));
+ if (files == null || files.length == 0) {
+ System.out.println("鐩綍鍐呮病鏈� PNG 鏂囦欢锛�" + dir.getAbsolutePath());
+ return;
+ }
+ Arrays.sort(files);
+
+ int fullOk = 0;
+ int cropOk = 0;
+ int bothFail = 0;
+ List<String> fullFailCropOk = new ArrayList<>();
+ List<String> hardFail = new ArrayList<>();
+ List<String> mismatched = new ArrayList<>();
+ String sampleContent = null;
+
+ for (File f : files) {
+ String name = f.getName();
+ String code = name.endsWith(".png") ? name.substring(0, name.length() - 4) : name;
+ BufferedImage image = ImageIO.read(f);
+ if (image == null) {
+ hardFail.add(name + " -> 鍥剧墖鏃犳硶璇诲彇");
+ bothFail++;
+ continue;
+ }
+ String full = decode(image);
+ String crop = decode(cropQrArea(image));
+ if (full != null) {
+ fullOk++;
+ }
+ if (crop != null) {
+ cropOk++;
+ }
+ if (full == null && crop == null) {
+ bothFail++;
+ hardFail.add(name + " -> 鏁村浘涓庤鍥惧潎瑙g爜澶辫触");
+ continue;
+ }
+ if (full == null) {
+ fullFailCropOk.add(name);
+ }
+ String text = full != null ? full : crop;
+ if (!text.contains(code)) {
+ mismatched.add(name + " -> 鍐呭涓庣爜鍊间笉绗�: " + text);
+ }
+ if (sampleContent == null) {
+ sampleContent = "鐮佸�� " + code + "\n 浜岀淮鐮佸唴瀹� " + text;
+ }
+ }
+
+ int total = files.length;
+ System.out.println("鏍囩鎬绘暟 : " + total);
+ System.out.printf ("鏁村浘瑙g爜鎴愬姛 : %d (%.2f%%)%n", fullOk, fullOk * 100.0 / total);
+ System.out.printf ("瑁佸浘瑙g爜鎴愬姛 : %d (%.2f%%)%n", cropOk, cropOk * 100.0 / total);
+ System.out.printf ("涓ょ鏂瑰紡閮藉け璐� : %d (%.2f%%)%n", bothFail, bothFail * 100.0 / total);
+ System.out.println("鏁村浘澶辫触浣嗚鍥炬垚鍔燂紙妫�娴嬪彈涓嬫柟鏂囧瓧骞叉壈锛屾爣绛炬湰韬甯革級: " + fullFailCropOk.size());
+ System.out.println("\n--- 鏍锋湰 ---\n " + sampleContent);
+
+ if (!fullFailCropOk.isEmpty()) {
+ System.out.println("\n--- 浠呮暣鍥惧け璐ョ殑鐮侊紙鍓� 10锛� ---");
+ fullFailCropOk.stream().limit(10).forEach(s -> System.out.println(" " + s));
+ }
+ if (!mismatched.isEmpty()) {
+ System.out.println("\n!!! 鍐呭涓庣爜鍊间笉绗︼紙鍓� 10锛� !!!");
+ mismatched.stream().limit(10).forEach(s -> System.out.println(" " + s));
+ }
+ if (!hardFail.isEmpty()) {
+ System.out.println("\n--- 鐪熸涓嶅彲璇嗚鐨勬爣绛撅紙鍓� 20锛� ---");
+ hardFail.stream().limit(20).forEach(s -> System.out.println(" " + s));
+ }
+ }
+
+ /** 瑁佸嚭浜岀淮鐮佹墍鍦ㄥ尯鍩燂紝鍘绘帀涓嬫柟鍙犲姞鐨勬枃瀛� */
+ private static BufferedImage cropQrArea(BufferedImage src) {
+ int x = Math.max(0, PADDING - CROP_MARGIN);
+ int y = Math.max(0, PADDING - CROP_MARGIN);
+ int w = Math.min(src.getWidth() - x, QR_SIZE + CROP_MARGIN * 2);
+ int h = Math.min(src.getHeight() - y, QR_SIZE + CROP_MARGIN * 2);
+ return src.getSubimage(x, y, w, h);
+ }
+
+ /** 灏濊瘯澶氱浜屽�煎寲绛栫暐瑙g爜锛屽叏閮ㄥけ璐ヨ繑鍥� null */
+ private static String decode(BufferedImage image) {
+ LuminanceSource source = new BufferedImageLuminanceSource(image);
+ Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
+ hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
+ hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
+ hints.put(DecodeHintType.POSSIBLE_FORMATS, List.of(BarcodeFormat.QR_CODE));
+ // HybridBinarizer 閫傚悎鍏夌収涓嶅潎锛孏lobalHistogramBinarizer 瀵归珮瀵规瘮搴﹀浘鏇寸ǔ锛屼袱鑰呬簰琛�
+ for (var binarizer : List.of(new HybridBinarizer(source), new GlobalHistogramBinarizer(source))) {
+ try {
+ Result r = new MultiFormatReader().decode(new BinaryBitmap(binarizer), hints);
+ if (r != null && r.getText() != null) {
+ return r.getText();
+ }
+ } catch (NotFoundException ignored) {
+ // 鎹笅涓�绉嶇瓥鐣�
+ } catch (Exception ignored) {
+ // 鍚屼笂
+ }
+ }
+ return null;
+ }
+}
diff --git a/scripts/DiagnoseQrFailures.java b/scripts/DiagnoseQrFailures.java
new file mode 100644
index 0000000..4f6292a
--- /dev/null
+++ b/scripts/DiagnoseQrFailures.java
@@ -0,0 +1,91 @@
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.LuminanceSource;
+import com.google.zxing.MultiFormatReader;
+import com.google.zxing.Result;
+import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
+import com.google.zxing.common.HybridBinarizer;
+
+import javax.imageio.ImageIO;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 瑙g爜澶辫触鏍锋湰鐨勬垚鍥犲垽瀹�
+ *
+ * 鐢ㄦ硶锛歫ava DiagnoseQrFailures <鏍囩鐩綍> <鏂囦欢鍚�1> [鏂囦欢鍚�2 ...]
+ *
+ * 鎬濊矾锛氭妸鍥剧墖鎸夋暣鏁板�嶆渶杩戦偦鏀惧ぇ鍚庨噸鏂拌В鐮併�備簩缁寸爜鏄函榛戠櫧鏂瑰潡锛�
+ * 鏈�杩戦偦鏀惧ぇ涓嶅紩鍏ユ柊淇℃伅锛屽彧鏄粰瑙g爜鍣ㄧ殑閲囨牱缃戞牸鏇村浣欏湴銆�
+ * - 鏀惧ぇ鍚庤兘瑙e嚭 鈫� 浜岀淮鐮佹暟鎹湰韬槸瀵圭殑锛屽彧鏄ā鍧楀瘑搴�/閲囨牱浣嶇疆杩囦簬涓寸晫
+ * 锛堝睘瑙g爜绔棶棰橈紝浣嗗嵃鍒峰昂瀵稿亸灏忔椂鐪熷疄鎵爜鏋篃鍙兘璇讳笉鍑猴紝搴旂缉鐭簩缁寸爜鍐呭鎴栧姞澶ф爣绛撅級
+ * - 鏀惧ぇ鍚庝粛瑙d笉鍑� 鈫� 璇ユ爣绛剧‘鏈夌己闄凤紝闇�鎸夊簾鍝佸鐞�
+ */
+public class DiagnoseQrFailures {
+
+ public static void main(String[] args) throws Exception {
+ File dir = new File(args[0]);
+ for (int i = 1; i < args.length; i++) {
+ File f = new File(dir, args[i]);
+ if (!f.isFile()) {
+ System.out.println(args[i] + " -> 鏂囦欢涓嶅瓨鍦�");
+ continue;
+ }
+ BufferedImage img = ImageIO.read(f);
+ String at1 = decode(img);
+ System.out.println("\n=== " + args[i] + " ===");
+ System.out.println(" 鍘熷灏哄 : " + img.getWidth() + " x " + img.getHeight());
+ System.out.println(" 1x 瑙g爜 : " + (at1 == null ? "澶辫触" : "鎴愬姛 -> " + at1));
+ if (at1 != null) {
+ continue;
+ }
+ boolean rescued = false;
+ for (int scale : List.of(2, 3, 4, 6)) {
+ BufferedImage up = scaleNearest(img, scale);
+ String text = decode(up);
+ System.out.println(" " + scale + "x 鏀惧ぇ瑙g爜 : " + (text == null ? "澶辫触" : "鎴愬姛 -> " + text));
+ if (text != null) {
+ rescued = true;
+ break;
+ }
+ }
+ System.out.println(" 缁撹 : " + (rescued
+ ? "浜岀淮鐮佹暟鎹湁鏁堬紝灞炴ā鍧楀瘑搴︿复鐣岋紙瑙g爜绔級锛岄潪搴熺爜"
+ : "鏀惧ぇ鍚庝粛涓嶅彲璇嗚锛屾爣绛剧‘鏈夌己闄�"));
+ }
+ }
+
+ /** 鏁存暟鍊嶆渶杩戦偦鏀惧ぇ锛氫笉鎻掑�笺�佷笉寮曞叆鏂板儚绱犲�硷紝鍙斁澶ч噰鏍风┖闂� */
+ private static BufferedImage scaleNearest(BufferedImage src, int scale) {
+ BufferedImage out = new BufferedImage(
+ src.getWidth() * scale, src.getHeight() * scale, BufferedImage.TYPE_INT_RGB);
+ var g = out.createGraphics();
+ try {
+ g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
+ g.drawImage(src, 0, 0, out.getWidth(), out.getHeight(), null);
+ } finally {
+ g.dispose();
+ }
+ return out;
+ }
+
+ private static String decode(BufferedImage image) {
+ LuminanceSource source = new BufferedImageLuminanceSource(image);
+ Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
+ hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
+ hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
+ hints.put(DecodeHintType.POSSIBLE_FORMATS, List.of(BarcodeFormat.QR_CODE));
+ try {
+ Result r = new MultiFormatReader().decode(
+ new BinaryBitmap(new HybridBinarizer(source)), hints);
+ return r == null ? null : r.getText();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+}
diff --git a/scripts/diag-bagcode.cjs b/scripts/diag-bagcode.cjs
new file mode 100644
index 0000000..e1252f5
--- /dev/null
+++ b/scripts/diag-bagcode.cjs
@@ -0,0 +1,50 @@
+/** 琚嬬爜椤电粨鏋勮瘖鏂細鐧诲綍鍚� dump 鎼滅储椤逛笌宸ュ叿鏍忔寜閽紝鐢ㄤ簬鏍″噯娴嬭瘯閫夋嫨鍣� */
+const { chromium } = require('playwright');
+
+const BASE = 'http://127.0.0.1:5666';
+
+(async () => {
+ const browser = await chromium.launch({ headless: true });
+ const page = await browser.newPage({ viewport: { width: 1600, height: 900 } });
+ page.on('pageerror', (e) => console.log('pageerror:', e.message));
+
+ await page.goto(`${BASE}/auth/login`, { waitUntil: 'domcontentloaded' });
+ await page.locator('input[name=username]').waitFor({ timeout: 60_000 });
+ await page.locator('input[name=username]').fill('admin');
+ await page.locator('input[name=password]').fill('admin123');
+ await page.locator('button:has-text("鐧诲綍")').last().click();
+ await page.waitForURL((u) => !u.pathname.includes('/auth/login'), { timeout: 60_000 });
+ console.log('鐧诲綍鍚� URL:', page.url());
+ await page.waitForTimeout(3000);
+
+ console.log('\n--- 鑿滃崟椤癸紙鍚�滆鐮�/璧嬬爜鈥濈殑锛�---');
+ for (const el of await page.locator('a, span, div').all()) {
+ const t = (await el.innerText().catch(() => '')).trim();
+ if (t && t.length < 20 && /琚嬬爜|璧嬬爜|涓�琚嬩竴鐮�/.test(t)) {
+ console.log(JSON.stringify(t));
+ }
+ }
+
+ for (const target of ['/bagcode/bag-code', '/bagcode', '/mes/pro/bagCode']) {
+ await page.goto(`${BASE}${target}`, { waitUntil: 'domcontentloaded' });
+ await page.waitForTimeout(4000);
+ const url = page.url();
+ const hasExport = (await page.getByRole('button', { name: '瀵煎嚭浜岀淮鐮�' }).count()) > 0;
+ console.log(`\n>>> 灏濊瘯 ${target} 鈫� 瀹為檯 ${url}锛屽鍑烘寜閽暟=${hasExport}`);
+ if (!hasExport) continue;
+
+ console.log('--- 鎼滅储杈撳叆妗� placeholder ---');
+ for (const el of await page.locator('input').all()) {
+ const ph = await el.getAttribute('placeholder');
+ if (ph && (await el.isVisible().catch(() => false))) console.log(JSON.stringify(ph));
+ }
+ console.log('--- 鍏ㄩ儴鍙鎸夐挳鏂囨湰 ---');
+ for (const el of await page.locator('button').all()) {
+ const t = (await el.innerText().catch(() => '')).trim();
+ if (t && (await el.isVisible().catch(() => false))) console.log(JSON.stringify(t));
+ }
+ break;
+ }
+
+ await browser.close();
+})();
diff --git a/scripts/diag-login.cjs b/scripts/diag-login.cjs
new file mode 100644
index 0000000..cc76649
--- /dev/null
+++ b/scripts/diag-login.cjs
@@ -0,0 +1,51 @@
+/** 鐧诲綍椤电粨鏋勮瘖鏂細鎶婂疄闄呮覆鏌撳嚭鏉ョ殑琛ㄥ崟鍏冪礌鍏ㄩ儴鎵撳嚭鏉� */
+const { chromium } = require('playwright');
+
+(async () => {
+ const browser = await chromium.launch({ headless: true });
+ const page = await browser.newPage({ viewport: { width: 1600, height: 900 } });
+ const errors = [];
+ page.on('pageerror', (e) => errors.push('pageerror: ' + e.message));
+ page.on('console', (m) => {
+ if (m.type() === 'error') errors.push('console: ' + m.text());
+ });
+ page.on('requestfailed', (r) =>
+ errors.push('reqfail: ' + r.url() + ' ' + (r.failure() || {}).errorText),
+ );
+
+ await page.goto('http://127.0.0.1:5666/auth/login', { waitUntil: 'domcontentloaded' });
+ await page.waitForTimeout(8000);
+
+ console.log('URL:', page.url());
+ console.log('TITLE:', await page.title());
+ const bodyText = (await page.locator('body').innerText().catch(() => '')).slice(0, 800);
+ console.log('--- 鍙鏂囨湰 ---');
+ console.log(bodyText);
+
+ console.log('--- input 鍏冪礌 ---');
+ for (const el of await page.locator('input').all()) {
+ console.log(
+ JSON.stringify({
+ type: await el.getAttribute('type'),
+ name: await el.getAttribute('name'),
+ autocomplete: await el.getAttribute('autocomplete'),
+ placeholder: await el.getAttribute('placeholder'),
+ id: await el.getAttribute('id'),
+ visible: await el.isVisible().catch(() => false),
+ }),
+ );
+ }
+
+ console.log('--- button/role=radio 鍏冪礌 ---');
+ for (const el of await page.locator('button, [role=radio], .ant-radio-wrapper').all()) {
+ const t = (await el.innerText().catch(() => '')).trim().slice(0, 40);
+ if (t) console.log(JSON.stringify({ tag: await el.evaluate((n) => n.tagName), text: t }));
+ }
+
+ console.log('--- 閿欒 ---');
+ console.log(errors.slice(0, 20).join('\n') || '(鏃�)');
+ await page.screenshot({
+ path: require('node:path').join(__dirname, 'qr-export-test-out', 'diag-login.png'),
+ });
+ await browser.close();
+})();
diff --git a/scripts/diag-pill.cjs b/scripts/diag-pill.cjs
new file mode 100644
index 0000000..2d5adb8
--- /dev/null
+++ b/scripts/diag-pill.cjs
@@ -0,0 +1,65 @@
+/** 娴爣灞曞紑璇婃柇锛氬垽鏂�岀偣娴爣鎭㈠闈㈡澘銆嶅け璐ユ槸鍓嶇 bug 杩樻槸娴嬭瘯閫夋嫨鍣ㄩ棶棰� */
+const { chromium } = require('playwright');
+
+const BASE = 'http://127.0.0.1:5666';
+const UUID = 'ae1229655a3e48d7b4bc842e3e74e15c';
+
+(async () => {
+ const browser = await chromium.launch({ headless: true });
+ const page = await browser.newPage({ viewport: { width: 1600, height: 900 } });
+ await page.addInitScript(() => {
+ Object.defineProperty(window, 'showSaveFilePicker', { value: undefined, configurable: true });
+ });
+
+ await page.goto(`${BASE}/auth/login`, { waitUntil: 'domcontentloaded' });
+ await page.locator('input[name=username]').waitFor({ timeout: 60_000 });
+ await page.locator('input[name=username]').fill('admin');
+ await page.locator('input[name=password]').fill('admin123');
+ await page.locator('button:has-text("鐧诲綍")').last().click();
+ await page.waitForURL((u) => !u.pathname.includes('/auth/login'), { timeout: 60_000 });
+
+ await page.goto(`${BASE}/bagcode/bag-code`, { waitUntil: 'domcontentloaded' });
+ await page.getByRole('button', { name: '瀵煎嚭浜岀淮鐮�' }).first().waitFor({ timeout: 60_000 });
+ await page.getByPlaceholder('璇疯緭鍏ョ敓鎴愮粍缂栧彿').fill(UUID);
+ const searchBtn = page.locator('button').filter({ hasText: /鎼淺s*绱�/ });
+ await searchBtn.first().click();
+ await page.waitForTimeout(3000);
+ await page.getByRole('button', { name: '瀵煎嚭浜岀淮鐮�' }).first().click();
+ await page.locator('text=姝e湪鐢熸垚浜岀淮鐮�').waitFor({ timeout: 90_000 });
+ console.log('闈㈡澘宸插嚭鐜�');
+
+ // 鐐归殣钘�
+ const hideBtn = page.locator('button').filter({ hasText: /闅怽s*钘�/ });
+ console.log('闅愯棌鎸夐挳鏁�:', await hideBtn.count());
+ await hideBtn.first().click();
+ await page.waitForTimeout(1500);
+
+ // 鎵惧嚭鎵�鏈夊惈銆屼簩缁寸爜瀵煎嚭銆嶇殑 div锛屾墦鍗板眰绾т笌浣嶇疆
+ const cands = page.locator('div').filter({ hasText: /浜岀淮鐮佸鍑�/ });
+ const n = await cands.count();
+ console.log('\n鍚�屼簩缁寸爜瀵煎嚭銆嶇殑 div 鏁伴噺:', n);
+ for (let i = 0; i < n; i++) {
+ const el = cands.nth(i);
+ const info = await el.evaluate((node) => ({
+ cls: node.className?.toString().slice(0, 70),
+ text: (node.innerText || '').replace(/\s+/g, ' ').slice(0, 40),
+ depth: (() => { let d = 0, p = node; while (p.parentElement) { d++; p = p.parentElement; } return d; })(),
+ }));
+ console.log(` [${i}] depth=${info.depth} cls="${info.cls}" text="${info.text}"`);
+ }
+
+ const pill = cands.last();
+ console.log('\n鎷熺偣鍑� last() 鍏冪礌锛屽叾 class:', await pill.evaluate((n) => n.className?.toString().slice(0, 70)));
+ await pill.click();
+ await page.waitForTimeout(2000);
+
+ const bodyText = await page.locator('body').innerText();
+ console.log('\n鐐瑰嚮鍚庯細');
+ console.log(' body 鍚�屾鍦ㄧ敓鎴愪簩缁寸爜銆�:', bodyText.includes('姝e湪鐢熸垚浜岀淮鐮�'));
+ console.log(' body 鍚�屼簩缁寸爜瀵煎嚭銆�:', bodyText.includes('浜岀淮鐮佸鍑�'));
+ console.log(' 闅愯棌鎸夐挳鏄惁杩樺湪:', await page.locator('button').filter({ hasText: /闅怽s*钘�/ }).count());
+ const m = bodyText.match(/姝e湪鐢熸垚锛圽d+\s*\/\s*\d+锛�/);
+ console.log(' 杩涘害鏂囨湰:', m ? m[0] : '(鏈壘鍒�)');
+
+ await browser.close();
+})();
diff --git a/scripts/split-hunks.py b/scripts/split-hunks.py
new file mode 100644
index 0000000..811caf1
--- /dev/null
+++ b/scripts/split-hunks.py
@@ -0,0 +1,49 @@
+"""浠� git diff 琛ヤ竵涓彧淇濈暀鎸囧畾搴忓彿鐨� hunk锛岀敤浜庣簿纭殏瀛橈紙git add -p 鏄氦浜掑紡鐨勶紝鑴氭湰閲岀敤涓嶄簡锛夈��
+
+鐢ㄦ硶:
+ python split-hunks.py <琛ヤ竵鏂囦欢> <淇濈暀鐨刪unk搴忓彿,閫楀彿鍒嗛殧> [--list]
+ --list 鍙垪鍑� hunk 搴忓彿涓庨琛岋紝涓嶈緭鍑鸿ˉ涓�
+"""
+import sys
+
+# 蹇呴』寮哄埗 UTF-8 涓斾笉鍋氭崲琛岀炕璇戯細鍚﹀垯涓枃涓婁笅鏂囪浼氳鎸� GBK 鍐欏潖銆乗n 琚啓鎴� \r\n锛�
+# 涓よ�呴兘浼氳 git apply 鍖归厤涓嶄笂锛堢函 ASCII 鐨勮ˉ涓佷笉浼氭毚闇茶繖涓棶棰橈紝瀹规槗璇垽锛�
+try:
+ sys.stdout.reconfigure(encoding='utf-8', newline='')
+except Exception:
+ pass
+
+path = sys.argv[1]
+if len(sys.argv) < 3:
+ print(__doc__)
+ sys.exit(1)
+list_only = '--list' in sys.argv
+keep = set()
+if not list_only:
+ keep = {int(x) for x in sys.argv[2].split(',') if x.strip() != ''}
+
+lines = open(path, encoding='utf-8', errors='surrogateescape').read().split('\n')
+header, hunks, cur = [], [], None
+for ln in lines:
+ if ln.startswith('@@'):
+ if cur is not None:
+ hunks.append(cur)
+ cur = [ln]
+ elif cur is None:
+ header.append(ln)
+ else:
+ cur.append(ln)
+if cur is not None:
+ hunks.append(cur)
+
+if list_only:
+ for i, h in enumerate(hunks):
+ print(f' [{i}] {h[0]}')
+ sys.exit(0)
+
+out = list(header)
+for i, h in enumerate(hunks):
+ if i in keep:
+ out.extend(h)
+# 缁撳熬琛ュ洖鎹㈣
+sys.stdout.write('\n'.join(out))
--
Gitblit v1.9.3