diff --git a/.gitignore b/.gitignore
index 0a5d442..4d2998b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -56,3 +56,7 @@ src/CncWebApi/*.xml
# === 测试报告 ===
docs/test-reports/
+
+# === Playwright 测试产物 ===
+test-results/
+playwright-report/
diff --git a/src/CncCollector/Core/CollectWorker.cs b/src/CncCollector/Core/CollectWorker.cs
index 8ddba03..03ddff5 100644
--- a/src/CncCollector/Core/CollectWorker.cs
+++ b/src/CncCollector/Core/CollectWorker.cs
@@ -38,6 +38,9 @@ namespace CncCollector.Core
/// 采集地址名称
public string AddressName => _address.Name;
+ /// 采集地址URL
+ public string AddressUrl => _address.Url;
+
/// 是否运行中
public bool IsRunning => _running;
diff --git a/src/CncCollector/Core/CollectorEngine.cs b/src/CncCollector/Core/CollectorEngine.cs
index 3072041..3cfb5cb 100644
--- a/src/CncCollector/Core/CollectorEngine.cs
+++ b/src/CncCollector/Core/CollectorEngine.cs
@@ -148,6 +148,7 @@ namespace CncCollector.Core
{
["addressId"] = kvp.Key,
["name"] = kvp.Value.AddressName,
+ ["url"] = kvp.Value.AddressUrl,
["isRunning"] = kvp.Value.IsRunning
});
}
diff --git a/src/CncCollector/scripts/package-lock.json b/src/CncCollector/scripts/package-lock.json
new file mode 100644
index 0000000..283a3d8
--- /dev/null
+++ b/src/CncCollector/scripts/package-lock.json
@@ -0,0 +1,75 @@
+{
+ "name": "scripts",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "scripts",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "@playwright/test": "^1.59.1"
+ }
+ },
+ "node_modules/@playwright/test": {
+ "version": "1.59.1",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz",
+ "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright": "1.59.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/playwright": {
+ "version": "1.59.1",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz",
+ "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.59.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.59.1",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz",
+ "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==",
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ }
+ }
+}
diff --git a/src/CncCollector/scripts/package.json b/src/CncCollector/scripts/package.json
new file mode 100644
index 0000000..e2684b3
--- /dev/null
+++ b/src/CncCollector/scripts/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "scripts",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "type": "commonjs",
+ "dependencies": {
+ "@playwright/test": "^1.59.1"
+ }
+}
diff --git a/src/CncCollector/scripts/playwright.config.ts b/src/CncCollector/scripts/playwright.config.ts
new file mode 100644
index 0000000..45a2256
--- /dev/null
+++ b/src/CncCollector/scripts/playwright.config.ts
@@ -0,0 +1,18 @@
+import { defineConfig } from '@playwright/test';
+
+/**
+ * Playwright 配置 - CncCollector 采集服务端到端测试
+ *
+ * 仅测试 HTTP API,不需要浏览器
+ */
+export default defineConfig({
+ testDir: '.',
+ testMatch: 'e2e-collector.spec.ts',
+ timeout: 30000,
+ retries: 0,
+ reporter: [['list'], ['html', { open: 'never' }]],
+ use: {
+ // 不在全局设置extraHTTPHeaders,每个测试自行控制认证
+ // (否则"无API Key"测试也会带上Key)
+ },
+});
diff --git a/src/CncSimulator/Admin/AdminHandler.cs b/src/CncSimulator/Admin/AdminHandler.cs
index 00f3782..a11e789 100644
--- a/src/CncSimulator/Admin/AdminHandler.cs
+++ b/src/CncSimulator/Admin/AdminHandler.cs
@@ -41,6 +41,23 @@ namespace CncSimulator.Admin
sb.AppendLine(".btn-sm-primary { padding:4px 12px; border:1px solid #1890ff; border-radius:4px; background:#1890ff; color:#fff; font-size:13px; }");
sb.AppendLine(".btn-sm-danger { padding:4px 12px; border:1px solid #ff4d4f; border-radius:4px; background:#fff; color:#ff4d4f; font-size:13px; }");
sb.AppendLine(".interval-input { width:60px; padding:2px 6px; border:1px solid #d9d9d9; border-radius:4px; text-align:center; }");
+ sb.AppendLine(".modal-overlay { display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.4); z-index:1000; justify-content:center; align-items:center; }");
+ sb.AppendLine(".modal-overlay.show { display:flex; }");
+ sb.AppendLine(".modal { background:#fff; border-radius:8px; padding:24px; width:520px; max-height:80vh; overflow-y:auto; box-shadow:0 4px 20px rgba(0,0,0,0.2); }");
+ sb.AppendLine(".modal h3 { font-size:16px; margin-bottom:12px; padding-bottom:8px; border-bottom:1px solid #e8e8e8; }");
+ sb.AppendLine(".machine-list { max-height:350px; overflow-y:auto; margin-bottom:16px; }");
+ sb.AppendLine(".machine-item { display:flex; align-items:center; padding:8px 4px; border-bottom:1px solid #f5f5f5; }");
+ sb.AppendLine(".machine-item:hover { background:#fafafa; }");
+ sb.AppendLine(".machine-item input[type=checkbox] { margin-right:10px; width:16px; height:16px; cursor:pointer; }");
+ sb.AppendLine(".machine-item .code { font-weight:600; min-width:100px; font-size:14px; }");
+ sb.AppendLine(".machine-item .name { color:#666; font-size:13px; }");
+ sb.AppendLine(".modal-actions { display:flex; justify-content:space-between; align-items:center; }");
+ sb.AppendLine(".modal-actions .sel-actions { font-size:13px; color:#666; }");
+ sb.AppendLine(".modal-actions .sel-actions a { color:#1890ff; cursor:pointer; text-decoration:none; margin-right:12px; }");
+ sb.AppendLine(".modal-actions .btn-group { display:flex; gap:8px; }");
+ sb.AppendLine(".btn-confirm { padding:6px 20px; border:1px solid #1890ff; border-radius:4px; background:#1890ff; color:#fff; font-size:14px; cursor:pointer; }");
+ sb.AppendLine(".btn-confirm:hover { background:#40a9ff; }");
+ sb.AppendLine(".btn-cancel { padding:6px 20px; border:1px solid #d9d9d9; border-radius:4px; background:#fff; color:#333; font-size:14px; cursor:pointer; }");
sb.AppendLine("
");
sb.AppendLine("");
sb.AppendLine("");
+ // 机床选择弹窗
+ sb.AppendLine("");
+ sb.AppendLine("
");
+ sb.AppendLine("
选择要模拟的机床
");
+ sb.AppendLine("
");
+ sb.AppendLine("
");
+ sb.AppendLine("
");
+ sb.AppendLine("
全选取消全选");
+ sb.AppendLine("
已选 0 台");
+ sb.AppendLine("
");
+ sb.AppendLine("
");
+ sb.AppendLine(" ");
+ sb.AppendLine(" ");
+ sb.AppendLine("
");
+ sb.AppendLine("
");
+ sb.AppendLine("
");
+ sb.AppendLine("
");
sb.AppendLine("