feat: 修改后端apiclient生成逻辑
fix: 修复debugger获取flag失败的问题 refactor: 重新编写debugger前后端逻辑
This commit is contained in:
		
							
								
								
									
										280
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										280
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -13,6 +13,7 @@
 | 
			
		||||
        "@types/lodash": "^4.17.16",
 | 
			
		||||
        "@vueuse/core": "^13.5.0",
 | 
			
		||||
        "async-mutex": "^0.5.0",
 | 
			
		||||
        "axios": "^1.11.0",
 | 
			
		||||
        "echarts": "^5.6.0",
 | 
			
		||||
        "highlight.js": "^11.11.1",
 | 
			
		||||
        "konva": "^9.3.20",
 | 
			
		||||
@@ -2357,6 +2358,12 @@
 | 
			
		||||
        "tslib": "^2.4.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/asynckit": {
 | 
			
		||||
      "version": "0.4.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
 | 
			
		||||
      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/autoprefixer": {
 | 
			
		||||
      "version": "10.4.21",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
 | 
			
		||||
@@ -2395,6 +2402,17 @@
 | 
			
		||||
        "postcss": "^8.1.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/axios": {
 | 
			
		||||
      "version": "1.11.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
 | 
			
		||||
      "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "follow-redirects": "^1.15.6",
 | 
			
		||||
        "form-data": "^4.0.4",
 | 
			
		||||
        "proxy-from-env": "^1.1.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/balanced-match": {
 | 
			
		||||
      "version": "1.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
 | 
			
		||||
@@ -2496,6 +2514,19 @@
 | 
			
		||||
        "url": "https://github.com/sponsors/sindresorhus"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/call-bind-apply-helpers": {
 | 
			
		||||
      "version": "1.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "es-errors": "^1.3.0",
 | 
			
		||||
        "function-bind": "^1.1.2"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/caniuse-lite": {
 | 
			
		||||
      "version": "1.0.30001715",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz",
 | 
			
		||||
@@ -2542,6 +2573,18 @@
 | 
			
		||||
        "fsevents": "~2.3.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/combined-stream": {
 | 
			
		||||
      "version": "1.0.8",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
 | 
			
		||||
      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "delayed-stream": "~1.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.8"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/complex.js": {
 | 
			
		||||
      "version": "2.4.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.4.2.tgz",
 | 
			
		||||
@@ -2735,6 +2778,15 @@
 | 
			
		||||
      "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/delayed-stream": {
 | 
			
		||||
      "version": "1.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.4.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/detect-libc": {
 | 
			
		||||
      "version": "2.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
 | 
			
		||||
@@ -2755,6 +2807,20 @@
 | 
			
		||||
        "node": ">=0.3.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/dunder-proto": {
 | 
			
		||||
      "version": "1.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "call-bind-apply-helpers": "^1.0.1",
 | 
			
		||||
        "es-errors": "^1.3.0",
 | 
			
		||||
        "gopd": "^1.2.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/echarts": {
 | 
			
		||||
      "version": "5.6.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz",
 | 
			
		||||
@@ -2814,6 +2880,51 @@
 | 
			
		||||
        "url": "https://github.com/sponsors/antfu"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/es-define-property": {
 | 
			
		||||
      "version": "1.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/es-errors": {
 | 
			
		||||
      "version": "1.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
 | 
			
		||||
      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/es-object-atoms": {
 | 
			
		||||
      "version": "1.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "es-errors": "^1.3.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/es-set-tostringtag": {
 | 
			
		||||
      "version": "2.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "es-errors": "^1.3.0",
 | 
			
		||||
        "get-intrinsic": "^1.2.6",
 | 
			
		||||
        "has-tostringtag": "^1.0.2",
 | 
			
		||||
        "hasown": "^2.0.2"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/esbuild": {
 | 
			
		||||
      "version": "0.25.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz",
 | 
			
		||||
@@ -2979,6 +3090,42 @@
 | 
			
		||||
        "node": ">=8"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/follow-redirects": {
 | 
			
		||||
      "version": "1.15.9",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
 | 
			
		||||
      "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
 | 
			
		||||
      "funding": [
 | 
			
		||||
        {
 | 
			
		||||
          "type": "individual",
 | 
			
		||||
          "url": "https://github.com/sponsors/RubenVerborgh"
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=4.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "debug": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/form-data": {
 | 
			
		||||
      "version": "4.0.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
 | 
			
		||||
      "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "asynckit": "^0.4.0",
 | 
			
		||||
        "combined-stream": "^1.0.8",
 | 
			
		||||
        "es-set-tostringtag": "^2.1.0",
 | 
			
		||||
        "hasown": "^2.0.2",
 | 
			
		||||
        "mime-types": "^2.1.12"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 6"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/formdata-polyfill": {
 | 
			
		||||
      "version": "4.0.10",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
 | 
			
		||||
@@ -3036,6 +3183,15 @@
 | 
			
		||||
        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/function-bind": {
 | 
			
		||||
      "version": "1.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
 | 
			
		||||
      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/sponsors/ljharb"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/gensync": {
 | 
			
		||||
      "version": "1.0.0-beta.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
 | 
			
		||||
@@ -3046,6 +3202,43 @@
 | 
			
		||||
        "node": ">=6.9.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/get-intrinsic": {
 | 
			
		||||
      "version": "1.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
 | 
			
		||||
      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "call-bind-apply-helpers": "^1.0.2",
 | 
			
		||||
        "es-define-property": "^1.0.1",
 | 
			
		||||
        "es-errors": "^1.3.0",
 | 
			
		||||
        "es-object-atoms": "^1.1.1",
 | 
			
		||||
        "function-bind": "^1.1.2",
 | 
			
		||||
        "get-proto": "^1.0.1",
 | 
			
		||||
        "gopd": "^1.2.0",
 | 
			
		||||
        "has-symbols": "^1.1.0",
 | 
			
		||||
        "hasown": "^2.0.2",
 | 
			
		||||
        "math-intrinsics": "^1.1.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/sponsors/ljharb"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/get-proto": {
 | 
			
		||||
      "version": "1.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "dunder-proto": "^1.0.1",
 | 
			
		||||
        "es-object-atoms": "^1.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/get-stream": {
 | 
			
		||||
      "version": "9.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz",
 | 
			
		||||
@@ -3099,6 +3292,18 @@
 | 
			
		||||
        "node": ">=4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/gopd": {
 | 
			
		||||
      "version": "1.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/sponsors/ljharb"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/graceful-fs": {
 | 
			
		||||
      "version": "4.2.11",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
 | 
			
		||||
@@ -3106,6 +3311,45 @@
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "ISC"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/has-symbols": {
 | 
			
		||||
      "version": "1.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/sponsors/ljharb"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/has-tostringtag": {
 | 
			
		||||
      "version": "1.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "has-symbols": "^1.0.3"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/sponsors/ljharb"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/hasown": {
 | 
			
		||||
      "version": "2.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "function-bind": "^1.1.2"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/he": {
 | 
			
		||||
      "version": "1.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
 | 
			
		||||
@@ -3723,6 +3967,15 @@
 | 
			
		||||
        "node": ">= 18"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/math-intrinsics": {
 | 
			
		||||
      "version": "1.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mathjs": {
 | 
			
		||||
      "version": "14.4.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-14.4.0.tgz",
 | 
			
		||||
@@ -3768,6 +4021,27 @@
 | 
			
		||||
        "node": ">= 0.10.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mime-db": {
 | 
			
		||||
      "version": "1.52.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
 | 
			
		||||
      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.6"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mime-types": {
 | 
			
		||||
      "version": "2.1.35",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
 | 
			
		||||
      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "mime-db": "1.52.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.6"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/minimatch": {
 | 
			
		||||
      "version": "9.0.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
 | 
			
		||||
@@ -4195,6 +4469,12 @@
 | 
			
		||||
        "url": "https://github.com/sponsors/sindresorhus"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/proxy-from-env": {
 | 
			
		||||
      "version": "1.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/quansync": {
 | 
			
		||||
      "version": "0.2.10",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.10.tgz",
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@
 | 
			
		||||
    "@types/lodash": "^4.17.16",
 | 
			
		||||
    "@vueuse/core": "^13.5.0",
 | 
			
		||||
    "async-mutex": "^0.5.0",
 | 
			
		||||
    "axios": "^1.11.0",
 | 
			
		||||
    "echarts": "^5.6.0",
 | 
			
		||||
    "highlight.js": "^11.11.1",
 | 
			
		||||
    "konva": "^9.3.20",
 | 
			
		||||
 
 | 
			
		||||
@@ -279,10 +279,18 @@ async function postProcessApiClient(): Promise<void> {
 | 
			
		||||
async function generateApiClient(): Promise<void> {
 | 
			
		||||
  console.log('Generating API client...');
 | 
			
		||||
  try {
 | 
			
		||||
    const npxCommand = getCommand('npx');
 | 
			
		||||
    await execAsync(`${npxCommand} nswag openapi2tsclient /input:http://localhost:5000/swagger/v1/swagger.json /output:src/APIClient.ts`);
 | 
			
		||||
    console.log('✓ API client generated successfully');
 | 
			
		||||
    
 | 
			
		||||
    const url = 'http://127.0.0.1:5000/GetAPIClientCode';
 | 
			
		||||
    const response = await fetch(url);
 | 
			
		||||
    if (!response.ok) {
 | 
			
		||||
      throw new Error(`Failed to fetch API client code: ${response.status} ${response.statusText}`);
 | 
			
		||||
    }
 | 
			
		||||
    const code = await response.text();
 | 
			
		||||
 | 
			
		||||
    // 写入 APIClient.ts
 | 
			
		||||
    const filePath = 'src/APIClient.ts';
 | 
			
		||||
    fs.writeFileSync(filePath, code, 'utf8');
 | 
			
		||||
    console.log('✓ API client code fetched and written successfully');
 | 
			
		||||
 | 
			
		||||
    // 添加后处理步骤
 | 
			
		||||
    await postProcessApiClient();
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,11 @@ using Microsoft.AspNetCore.Http.Features;
 | 
			
		||||
using Microsoft.Extensions.FileProviders;
 | 
			
		||||
using Microsoft.IdentityModel.Tokens;
 | 
			
		||||
using Newtonsoft.Json;
 | 
			
		||||
using NJsonSchema.CodeGeneration.TypeScript;
 | 
			
		||||
using NLog;
 | 
			
		||||
using NLog.Web;
 | 
			
		||||
using NSwag;
 | 
			
		||||
using NSwag.CodeGeneration.TypeScript;
 | 
			
		||||
using NSwag.Generation.Processors.Security;
 | 
			
		||||
using server.Services;
 | 
			
		||||
 | 
			
		||||
@@ -191,6 +194,34 @@ try
 | 
			
		||||
    // Setup Program
 | 
			
		||||
    MsgBus.Init();
 | 
			
		||||
 | 
			
		||||
    // Generate API Client
 | 
			
		||||
    app.MapGet("GetAPIClientCode", async (HttpContext context) =>
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var document = await OpenApiDocument.FromUrlAsync($"http://{Global.localhost}:5000/swagger/v1/swagger.json");
 | 
			
		||||
 | 
			
		||||
            var settings = new TypeScriptClientGeneratorSettings
 | 
			
		||||
            {
 | 
			
		||||
                ClassName = "{controller}Client",
 | 
			
		||||
                UseAbortSignal = false,
 | 
			
		||||
                Template = TypeScriptTemplate.Axios,
 | 
			
		||||
                TypeScriptGeneratorSettings = {
 | 
			
		||||
                },
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var generator = new TypeScriptClientGenerator(document, settings);
 | 
			
		||||
            var code = generator.GenerateFile();
 | 
			
		||||
 | 
			
		||||
            return Results.Text(code, "text/plain; charset=utf-8", Encoding.UTF8);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception err)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(err);
 | 
			
		||||
            return Results.Problem(err.ToString());
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    app.Run();
 | 
			
		||||
}
 | 
			
		||||
catch (Exception exception)
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@
 | 
			
		||||
    <PackageReference Include="NLog" Version="5.4.0" />    
 | 
			
		||||
    <PackageReference Include="NLog.Web.AspNetCore" Version="5.4.0" />
 | 
			
		||||
    <PackageReference Include="NSwag.AspNetCore" Version="14.3.0" />
 | 
			
		||||
    <PackageReference Include="NSwag.CodeGeneration.TypeScript" Version="14.4.0" />
 | 
			
		||||
    <PackageReference Include="OpenCvSharp4" Version="4.11.0.20250507" />
 | 
			
		||||
    <PackageReference Include="OpenCvSharp4.runtime.win" Version="4.11.0.20250507" />
 | 
			
		||||
    <PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ using Peripherals.DebuggerClient;
 | 
			
		||||
namespace server.Controllers;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// FPGA调试器控制器
 | 
			
		||||
/// FPGA调试器控制器,提供信号捕获、触发、数据读取等调试相关API
 | 
			
		||||
/// </summary>
 | 
			
		||||
[ApiController]
 | 
			
		||||
[Route("api/[controller]")]
 | 
			
		||||
@@ -15,8 +15,81 @@ public class DebuggerController : ControllerBase
 | 
			
		||||
{
 | 
			
		||||
    private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
    /// <summary> 
 | 
			
		||||
    /// 表示单个信号通道的配置信息
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class ChannelConfig
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary> 
 | 
			
		||||
        /// 通道名称
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public string name;
 | 
			
		||||
        /// <summary> 
 | 
			
		||||
        /// 通道显示颜色(如前端波形显示用)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public string color;
 | 
			
		||||
        /// <summary> 
 | 
			
		||||
        /// 通道信号线宽度(位数)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 wireWidth;
 | 
			
		||||
        /// <summary> 
 | 
			
		||||
        /// 信号线在父端口中的起始索引(bit)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 wireStartIndex;
 | 
			
		||||
        /// <summary> 
 | 
			
		||||
        /// 父端口编号
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 parentPort;
 | 
			
		||||
        /// <summary> 
 | 
			
		||||
        /// 捕获模式(如上升沿、下降沿等)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public CaptureMode mode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary> 
 | 
			
		||||
    /// 调试器整体配置信息
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class DebuggerConfig
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary> 
 | 
			
		||||
        /// 时钟频率
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 clkFreq;
 | 
			
		||||
        /// <summary> 
 | 
			
		||||
        /// 总端口数量
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 totalPortNum;
 | 
			
		||||
        /// <summary> 
 | 
			
		||||
        /// 捕获深度(采样点数)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 captureDepth;
 | 
			
		||||
        /// <summary> 
 | 
			
		||||
        /// 触发器数量
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 triggerNum;
 | 
			
		||||
        /// <summary> 
 | 
			
		||||
        /// 所有信号通道的配置信息
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public ChannelConfig[] channelConfigs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary> 
 | 
			
		||||
    /// 单个通道的捕获数据
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class ChannelCaptureData
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary> 
 | 
			
		||||
        /// 通道名称
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public string name;
 | 
			
		||||
        /// <summary> 
 | 
			
		||||
        /// 通道捕获到的数据(Base64编码的UInt32数组)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public string data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取调试器实例
 | 
			
		||||
    /// 获取当前用户绑定的调试器实例
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    private DebuggerClient? GetDebugger()
 | 
			
		||||
    {
 | 
			
		||||
@@ -50,19 +123,21 @@ public class DebuggerController : ControllerBase
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 设置捕获模式
 | 
			
		||||
    /// 设置指定信号线的捕获模式
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="wireNum">信号线编号(0~511)</param>
 | 
			
		||||
    /// <param name="mode">捕获模式</param>
 | 
			
		||||
    [HttpPost("SetMode")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    public async Task<IActionResult> SetMode(int channelNum, CaptureMode mode)
 | 
			
		||||
    public async Task<IActionResult> SetMode(UInt32 wireNum, CaptureMode mode)
 | 
			
		||||
    {
 | 
			
		||||
        if (channelNum > 0x0F)
 | 
			
		||||
        if (wireNum > 512)
 | 
			
		||||
        {
 | 
			
		||||
            return BadRequest($"最多只能建立16个通道");
 | 
			
		||||
            return BadRequest($"最多只能建立512位信号线");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
@@ -71,7 +146,7 @@ public class DebuggerController : ControllerBase
 | 
			
		||||
            if (debugger == null)
 | 
			
		||||
                return BadRequest("用户未绑定有效的实验板");
 | 
			
		||||
 | 
			
		||||
            var result = await debugger.SetMode((byte)channelNum, mode);
 | 
			
		||||
            var result = await debugger.SetMode(wireNum, mode);
 | 
			
		||||
            if (!result.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"设置捕获模式失败: {result.Error}");
 | 
			
		||||
@@ -88,7 +163,58 @@ public class DebuggerController : ControllerBase
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 启动触发器
 | 
			
		||||
    /// 为每个通道中的每根线设置捕获模式
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="config">调试器配置信息,包含所有通道的捕获模式设置</param>
 | 
			
		||||
    [HttpPost("SetChannelsMode")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    public async Task<IActionResult> SetChannelsMode([FromBody] DebuggerConfig config)
 | 
			
		||||
    {
 | 
			
		||||
        if (config == null || config.channelConfigs == null)
 | 
			
		||||
            return BadRequest("配置无效");
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var debugger = GetDebugger();
 | 
			
		||||
            if (debugger == null)
 | 
			
		||||
                return BadRequest("用户未绑定有效的实验板");
 | 
			
		||||
 | 
			
		||||
            foreach (var channel in config.channelConfigs)
 | 
			
		||||
            {
 | 
			
		||||
                // 检查每个通道的配置
 | 
			
		||||
                if (channel.wireWidth > 32 ||
 | 
			
		||||
                    channel.wireStartIndex > 32 ||
 | 
			
		||||
                    channel.wireStartIndex + channel.wireWidth > 32)
 | 
			
		||||
                {
 | 
			
		||||
                    return BadRequest($"通道 {channel.name} 配置错误");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                for (uint i = 0; i < channel.wireWidth; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    var result = await debugger.SetMode(channel.wireStartIndex * (channel.parentPort * 32) + i, channel.mode);
 | 
			
		||||
                    if (!result.IsSuccessful)
 | 
			
		||||
                    {
 | 
			
		||||
                        logger.Error($"设置通道 {channel.name} 第 {i} 根线捕获模式失败: {result.Error}");
 | 
			
		||||
                        return StatusCode(StatusCodes.Status500InternalServerError, $"设置通道 {channel.name} 第 {i} 根线捕获模式失败");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Ok(true);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(ex, "为每个通道中的每根线设置捕获模式时发生异常");
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 启动触发器,开始信号捕获
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [HttpPost("StartTrigger")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
@@ -120,6 +246,46 @@ public class DebuggerController : ControllerBase
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 重新开始触发(刷新后再启动触发器)
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [HttpPost("RestartTrigger")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    public async Task<IActionResult> RestartTrigger()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var debugger = GetDebugger();
 | 
			
		||||
            if (debugger == null)
 | 
			
		||||
                return BadRequest("用户未绑定有效的实验板");
 | 
			
		||||
 | 
			
		||||
            var refreshResult = await debugger.Refresh();
 | 
			
		||||
            if (!refreshResult.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"刷新调试器状态失败: {refreshResult.Error}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, "刷新调试器状态失败");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var startResult = await debugger.StartTrigger();
 | 
			
		||||
            if (!startResult.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"启动触发器失败: {startResult.Error}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, "启动触发器失败");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Ok(startResult.Value);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(ex, "重新开始触发时发生异常");
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 读取触发器状态标志
 | 
			
		||||
    /// </summary>
 | 
			
		||||
@@ -187,32 +353,105 @@ public class DebuggerController : ControllerBase
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 读取捕获数据
 | 
			
		||||
    /// 读取捕获数据(等待触发完成后返回各通道采样数据)
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="config">调试器配置信息,包含采样深度、端口数、通道配置等</param>
 | 
			
		||||
    /// <param name="cancellationToken">取消操作的令牌</param>
 | 
			
		||||
    [HttpGet("ReadData")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(typeof(ChannelCaptureData[]), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    public async Task<IActionResult> ReadData([FromQuery] ushort offset = 0)
 | 
			
		||||
    public async Task<IActionResult> ReadData([FromBody] DebuggerConfig config, CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        // 检查每个通道的配置
 | 
			
		||||
        foreach (var channel in config.channelConfigs)
 | 
			
		||||
        {
 | 
			
		||||
            if (channel.wireWidth > 32 ||
 | 
			
		||||
                channel.wireStartIndex > 32 ||
 | 
			
		||||
                channel.wireStartIndex + channel.wireWidth > 32)
 | 
			
		||||
            {
 | 
			
		||||
                return BadRequest($"通道 {channel.name} 配置错误");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var debugger = GetDebugger();
 | 
			
		||||
            if (debugger == null)
 | 
			
		||||
                return BadRequest("用户未绑定有效的实验板");
 | 
			
		||||
 | 
			
		||||
            var result = await debugger.ReadData(offset);
 | 
			
		||||
            if (!result.IsSuccessful)
 | 
			
		||||
            // 等待捕获标志位
 | 
			
		||||
            while (true)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"读取捕获数据失败: {result.Error}");
 | 
			
		||||
                cancellationToken.ThrowIfCancellationRequested();
 | 
			
		||||
                var flagResult = await debugger.ReadFlag();
 | 
			
		||||
                if (!flagResult.IsSuccessful)
 | 
			
		||||
                {
 | 
			
		||||
                    logger.Error($"读取捕获标志失败: {flagResult.Error}");
 | 
			
		||||
                    return StatusCode(StatusCodes.Status500InternalServerError, "读取捕获标志失败");
 | 
			
		||||
                }
 | 
			
		||||
                if (flagResult.Value == 1)
 | 
			
		||||
                {
 | 
			
		||||
                    var clearResult = await debugger.ClearFlag();
 | 
			
		||||
                    if (!clearResult.IsSuccessful)
 | 
			
		||||
                    {
 | 
			
		||||
                        logger.Error($"清除捕获标志失败: {clearResult.Error}");
 | 
			
		||||
                        return StatusCode(StatusCodes.Status500InternalServerError, "清除捕获标志失败");
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                await Task.Delay(500, cancellationToken);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var dataResult = await debugger.ReadData(config.totalPortNum);
 | 
			
		||||
            if (!dataResult.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"读取捕获数据失败: {dataResult.Error}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, "读取捕获数据失败");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 返回Base64编码
 | 
			
		||||
            var base64Data = Convert.ToBase64String(result.Value);
 | 
			
		||||
            return Ok(base64Data);
 | 
			
		||||
            var rawData = dataResult.Value;
 | 
			
		||||
            int depth = (int)config.captureDepth;
 | 
			
		||||
            int portDataLen = 4 * depth;
 | 
			
		||||
            int portNum = (int)config.totalPortNum;
 | 
			
		||||
            var channelDataList = new List<ChannelCaptureData>();
 | 
			
		||||
 | 
			
		||||
            foreach (var channel in config.channelConfigs)
 | 
			
		||||
            {
 | 
			
		||||
                int port = (int)channel.parentPort;
 | 
			
		||||
                int wireStart = (int)channel.wireStartIndex;
 | 
			
		||||
                int wireWidth = (int)channel.wireWidth;
 | 
			
		||||
 | 
			
		||||
                // 每个port的数据长度
 | 
			
		||||
                int portOffset = port * portDataLen;
 | 
			
		||||
 | 
			
		||||
                var channelUintArr = new UInt32[depth];
 | 
			
		||||
                for (int i = 0; i < depth; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    // 取出该port的第i个采样点的4字节
 | 
			
		||||
                    int sampleOffset = portOffset + i * 4;
 | 
			
		||||
                    if (sampleOffset + 4 > rawData.Length)
 | 
			
		||||
                    {
 | 
			
		||||
                        logger.Error($"数据越界: port {port}, sample {i}");
 | 
			
		||||
                        return StatusCode(StatusCodes.Status500InternalServerError, "数据越界");
 | 
			
		||||
                    }
 | 
			
		||||
                    UInt32 sample = BitConverter.ToUInt32(rawData, sampleOffset);
 | 
			
		||||
                    // 提取wireWidth位
 | 
			
		||||
                    UInt32 mask = (wireWidth == 32) ? 0xFFFFFFFF : ((1u << wireWidth) - 1u);
 | 
			
		||||
                    channelUintArr[i] = (sample >> wireStart) & mask;
 | 
			
		||||
                }
 | 
			
		||||
                var base64 = Convert.ToBase64String(channelUintArr.SelectMany(BitConverter.GetBytes).ToArray());
 | 
			
		||||
                channelDataList.Add(new ChannelCaptureData { name = channel.name, data = base64 });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Ok(channelDataList.ToArray());
 | 
			
		||||
        }
 | 
			
		||||
        catch (OperationCanceledException)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Info("读取捕获数据请求被取消");
 | 
			
		||||
            return StatusCode(StatusCodes.Status499ClientClosedRequest, "客户端已取消请求");
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
@@ -222,7 +461,7 @@ public class DebuggerController : ControllerBase
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 刷新调试器状态
 | 
			
		||||
    /// 刷新调试器状态(重置采集状态等)
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [HttpPost("Refresh")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
 
 | 
			
		||||
@@ -119,18 +119,18 @@ public class DebuggerClient
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 设置信号捕获模式
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="channelNum">要设置的通道</param>
 | 
			
		||||
    /// <param name="wireNum">要设置的线</param>
 | 
			
		||||
    /// <param name="mode">要设置的捕获模式</param>
 | 
			
		||||
    /// <returns>操作结果,成功返回true,失败返回错误信息</returns>
 | 
			
		||||
    public async ValueTask<Result<bool>> SetMode(byte channelNum, CaptureMode mode)
 | 
			
		||||
    public async ValueTask<Result<bool>> SetMode(UInt32 wireNum, CaptureMode mode)
 | 
			
		||||
    {
 | 
			
		||||
        if (channelNum > 0x0F)
 | 
			
		||||
        if (wireNum > 512)
 | 
			
		||||
        {
 | 
			
		||||
            return new(new ArgumentException($"Channel Num can't be over 16, but receive num: {channelNum}"));
 | 
			
		||||
            return new(new ArgumentException($"Wire Num can't be over 512, but receive num: {wireNum}"));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        UInt32 data = ((UInt32)mode);
 | 
			
		||||
        var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, DebuggerAddr.Mode + channelNum, data, this.timeout);
 | 
			
		||||
        var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, DebuggerAddr.Mode + wireNum, data, this.timeout);
 | 
			
		||||
        if (!ret.IsSuccessful)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"Failed to set mode: {ret.Error}");
 | 
			
		||||
@@ -181,7 +181,7 @@ public class DebuggerClient
 | 
			
		||||
            logger.Error("ReadAddr returned invalid data for flag");
 | 
			
		||||
            return new(new Exception("Failed to read flag"));
 | 
			
		||||
        }
 | 
			
		||||
        return ret.Value.Options.Data[0];
 | 
			
		||||
        return ret.Value.Options.Data[3];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
@@ -207,30 +207,26 @@ public class DebuggerClient
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 从指定偏移地址读取捕获的数据
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="offset">数据读取的偏移地址</param>
 | 
			
		||||
    /// <returns>操作结果,成功返回32KB的捕获数据,失败返回错误信息</returns>
 | 
			
		||||
    public async ValueTask<Result<byte[]>> ReadData(UInt16 offset)
 | 
			
		||||
    /// <param name="portNum">Port数量</param>
 | 
			
		||||
    /// <returns>操作结果,成功返回捕获数据,失败返回错误信息</returns>
 | 
			
		||||
    public async ValueTask<Result<byte[]>> ReadData(UInt32 portNum)
 | 
			
		||||
    {
 | 
			
		||||
        var captureData = new byte[1024 * 32];
 | 
			
		||||
        var captureData = new byte[1024 * 4 * portNum];
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.ReadAddr4BytesAsync(this.ep, this.taskID, this.captureDataAddr + offset, 512, this.timeout);
 | 
			
		||||
            var ret = await UDPClientPool.ReadAddr4BytesAsync(this.ep, this.taskID, this.captureDataAddr, captureData.Length, this.timeout);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to read data: {ret.Error}");
 | 
			
		||||
                return new(ret.Error);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Buffer.BlockCopy(ret.Value, 0, captureData, 0, 512 * 4);
 | 
			
		||||
        }
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.ReadAddr4BytesAsync(this.ep, this.taskID, this.captureDataAddr + offset + 512, 512, this.timeout);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            if (ret.Value.Length != captureData.Length)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to read data: {ret.Error}");
 | 
			
		||||
                return new(ret.Error);
 | 
			
		||||
                logger.Error($"Receive capture data length should be {captureData.Length} instead of {ret.Value.Length}");
 | 
			
		||||
                return new(new Exception($"Receive capture data length should be {captureData.Length} instead of {ret.Value.Length}"));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Buffer.BlockCopy(ret.Value, 0, captureData, 512 * 4, 512 * 4);
 | 
			
		||||
            Buffer.BlockCopy(ret.Value, 0, captureData, 0, captureData.Length);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return captureData;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4757
									
								
								src/APIClient.ts
									
									
									
									
									
								
							
							
						
						
									
										4757
									
								
								src/APIClient.ts
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -21,7 +21,7 @@
 | 
			
		||||
        <h3 class="text-xl font-semibold text-slate-600 mb-2">
 | 
			
		||||
          暂无逻辑分析数据
 | 
			
		||||
        </h3>
 | 
			
		||||
        <p class="text-sm text-slate-500">点击下方按钮生成测试数据用于观察</p>
 | 
			
		||||
        <p class="text-sm text-slate-500">点击下方按钮开始捕获</p>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <!-- <button
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ import {
 | 
			
		||||
  OscilloscopeApiClient,
 | 
			
		||||
  DebuggerClient,
 | 
			
		||||
} from "@/APIClient";
 | 
			
		||||
import axios, { type AxiosInstance } from "axios";
 | 
			
		||||
 | 
			
		||||
// 支持的客户端类型联合类型
 | 
			
		||||
type SupportedClient =
 | 
			
		||||
@@ -108,13 +109,27 @@ export class AuthManager {
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 私有方法:创建带认证的Axios实例
 | 
			
		||||
  private static createAuthenticatedAxiosInstance(): AxiosInstance | null {
 | 
			
		||||
    const token = AuthManager.getToken();
 | 
			
		||||
    if (!token) return null;
 | 
			
		||||
 | 
			
		||||
    const instance = axios.create();
 | 
			
		||||
    instance.interceptors.request.use(config => {
 | 
			
		||||
      config.headers = config.headers || {};
 | 
			
		||||
      (config.headers as any)["Authorization"] = `Bearer ${token}`;
 | 
			
		||||
      return config;
 | 
			
		||||
    });
 | 
			
		||||
    return instance;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 通用的创建已认证客户端的方法(使用泛型)
 | 
			
		||||
  public static createAuthenticatedClient<T extends SupportedClient>(
 | 
			
		||||
    ClientClass: new (baseUrl?: string, http?: any) => T,
 | 
			
		||||
    ClientClass: new (baseUrl?: string, instance?: AxiosInstance) => T,
 | 
			
		||||
  ): T {
 | 
			
		||||
    const customHttp = AuthManager.createAuthenticatedHttp();
 | 
			
		||||
    return customHttp
 | 
			
		||||
      ? new ClientClass(undefined, customHttp)
 | 
			
		||||
    const axiosInstance = AuthManager.createAuthenticatedAxiosInstance();
 | 
			
		||||
    return axiosInstance
 | 
			
		||||
      ? new ClientClass(undefined, axiosInstance)
 | 
			
		||||
      : new ClientClass();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,13 @@
 | 
			
		||||
            调试器波形捕获
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="flex items-center gap-2">
 | 
			
		||||
            <button
 | 
			
		||||
              class="btn btn-sm btn-primary"
 | 
			
		||||
              @click="startCapture(true)"
 | 
			
		||||
              :disabled="!captureData"
 | 
			
		||||
            >
 | 
			
		||||
              重新捕获
 | 
			
		||||
            </button>
 | 
			
		||||
            <button
 | 
			
		||||
              class="btn btn-sm btn-error"
 | 
			
		||||
              @click="handleDeleteData"
 | 
			
		||||
@@ -18,6 +25,12 @@
 | 
			
		||||
          </div>
 | 
			
		||||
        </h2>
 | 
			
		||||
        <WaveformDisplay :data="captureData">
 | 
			
		||||
          <div class="text-center">
 | 
			
		||||
            <h3 class="text-xl font-semibold text-slate-600 mb-2">
 | 
			
		||||
              暂无逻辑分析数据
 | 
			
		||||
            </h3>
 | 
			
		||||
            <p class="text-sm text-slate-500">点击下方按钮开始捕获</p>
 | 
			
		||||
          </div>
 | 
			
		||||
          <button
 | 
			
		||||
            class="group relative px-8 py-3 bg-gradient-to-r text-white font-medium rounded-lg shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200 ease-in-out focus:outline-none focus:ring-4 active:scale-95"
 | 
			
		||||
            :class="{
 | 
			
		||||
@@ -45,51 +58,94 @@
 | 
			
		||||
    <!-- Debugger 通道配置 -->
 | 
			
		||||
    <div class="card m-5 bg-base-200 shadow-2xl">
 | 
			
		||||
      <div class="card-body">
 | 
			
		||||
        <h2 class="card-title mb-4">调试器通道配置</h2>
 | 
			
		||||
        <div class="overflow-x-auto flex flex-col gap-10">
 | 
			
		||||
          <!-- 通道状态概览 -->
 | 
			
		||||
        <div class="flex justify-between">
 | 
			
		||||
          <h2 class="card-title mb-4">调试器通道配置</h2>
 | 
			
		||||
          <div class="flex items-center gap-2">
 | 
			
		||||
            <button
 | 
			
		||||
              class="btn btn-sm btn-primary"
 | 
			
		||||
              @click="addChannel"
 | 
			
		||||
              :disabled="!configInited"
 | 
			
		||||
            >
 | 
			
		||||
              添加通道
 | 
			
		||||
            </button>
 | 
			
		||||
            <button
 | 
			
		||||
              class="btn btn-sm btn-secondary"
 | 
			
		||||
              @click="showConfigDialog = true"
 | 
			
		||||
            >
 | 
			
		||||
              配置
 | 
			
		||||
            </button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <!-- 配置未初始化时 -->
 | 
			
		||||
        <div
 | 
			
		||||
          v-if="!configInited"
 | 
			
		||||
          class="flex flex-col items-center justify-center py-10"
 | 
			
		||||
        >
 | 
			
		||||
          <div class="text-lg text-slate-500 mb-4">请先进行调试器基本配置</div>
 | 
			
		||||
          <button class="btn btn-primary" @click="showConfigDialog = true">
 | 
			
		||||
            配置调试器
 | 
			
		||||
          </button>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div v-if="configInited" class="overflow-x-auto flex flex-col gap-10">
 | 
			
		||||
          <!-- 状态概览 -->
 | 
			
		||||
          <div
 | 
			
		||||
            class="stats stats-horizontal bg-base-100 shadow flex justify-between"
 | 
			
		||||
          >
 | 
			
		||||
            <div class="stat">
 | 
			
		||||
              <div class="stat-title">总通道数</div>
 | 
			
		||||
              <div class="stat-value text-primary">16</div>
 | 
			
		||||
              <div class="stat-desc">逻辑分析仪通道</div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="stat">
 | 
			
		||||
              <div class="stat-title">启用通道</div>
 | 
			
		||||
              <div class="stat-value text-success">
 | 
			
		||||
                {{ channels.filter((ch) => ch.visible).length }}
 | 
			
		||||
              <div class="stat-title">启用端口数</div>
 | 
			
		||||
              <div class="stat-value text-primary">
 | 
			
		||||
                {{ config.totalPortNum }}
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="stat-desc">当前激活通道</div>
 | 
			
		||||
              <div class="stat-desc">每端口最大32线</div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="stat">
 | 
			
		||||
              <div class="stat-title">采样率</div>
 | 
			
		||||
              <div class="stat-value text-info">5MHz</div>
 | 
			
		||||
              <div class="stat-desc">最大采样频率</div>
 | 
			
		||||
              <div class="stat-title">最大线宽数</div>
 | 
			
		||||
              <div class="stat-value text-info">
 | 
			
		||||
                {{ config.totalPortNum * 32 }}
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="stat-desc">启用端口数 × 32</div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="stat">
 | 
			
		||||
              <div class="stat-title">已用线宽数</div>
 | 
			
		||||
              <div class="stat-value text-success">
 | 
			
		||||
                {{ channels.reduce((sum, ch) => sum + ch.width, 0) }}
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="stat-desc">所有通道线宽总和</div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="stat">
 | 
			
		||||
              <div class="stat-title">采样深度</div>
 | 
			
		||||
              <div class="stat-value text-warning">
 | 
			
		||||
                {{ config.captureDepth }}
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="stat-desc">每通道采样点数</div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="stat">
 | 
			
		||||
              <div class="stat-title">时钟频率</div>
 | 
			
		||||
              <div class="stat-value text-accent">{{ config.clkFreq }} MHz</div>
 | 
			
		||||
              <div class="stat-desc">采样时钟</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <!-- 表格 -->
 | 
			
		||||
          <!-- 通道表格 -->
 | 
			
		||||
          <div class="space-y-2">
 | 
			
		||||
            <!-- 表头 -->
 | 
			
		||||
            <div
 | 
			
		||||
              class="grid grid-cols-6 justify-items-center gap-2 p-2 bg-base-300 rounded-lg text-sm font-medium"
 | 
			
		||||
              class="grid grid-cols-7 justify-items-center gap-2 p-2 bg-base-300 rounded-lg text-sm font-medium"
 | 
			
		||||
            >
 | 
			
		||||
              <span>名称</span>
 | 
			
		||||
              <span>显示</span>
 | 
			
		||||
              <span>颜色</span>
 | 
			
		||||
              <span>触发模式</span>
 | 
			
		||||
              <span>数据位数</span>
 | 
			
		||||
              <span>数据位宽(起始:宽度)</span>
 | 
			
		||||
              <span>父端口编号</span>
 | 
			
		||||
              <span>操作</span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <!-- 通道列表 -->
 | 
			
		||||
            <div
 | 
			
		||||
              v-for="(ch, idx) in channels"
 | 
			
		||||
              :key="idx"
 | 
			
		||||
              class="grid grid-cols-6 justify-items-center gap-2 p-2 bg-base-200 rounded-lg hover:bg-base-300 transition-colors"
 | 
			
		||||
              class="grid grid-cols-7 place-items-center gap-4 p-4 bg-base-200 rounded-lg hover:bg-base-300 transition-colors"
 | 
			
		||||
            >
 | 
			
		||||
              <input
 | 
			
		||||
                v-model="ch.name"
 | 
			
		||||
@@ -118,11 +174,17 @@
 | 
			
		||||
                  {{ mode.label }}
 | 
			
		||||
                </option>
 | 
			
		||||
              </select>
 | 
			
		||||
              <input
 | 
			
		||||
                v-model="ch.widthStr"
 | 
			
		||||
                class="input input-bordered w-full"
 | 
			
		||||
                placeholder="如0:8"
 | 
			
		||||
                @change="parseWidthStr(idx)"
 | 
			
		||||
              />
 | 
			
		||||
              <input
 | 
			
		||||
                type="number"
 | 
			
		||||
                min="1"
 | 
			
		||||
                max="32"
 | 
			
		||||
                v-model.number="ch.width"
 | 
			
		||||
                min="0"
 | 
			
		||||
                :max="config.totalPortNum - 1"
 | 
			
		||||
                v-model.number="ch.parentPort"
 | 
			
		||||
                class="input input-bordered w-full"
 | 
			
		||||
              />
 | 
			
		||||
              <button class="btn btn-error" @click="removeChannel(idx)">
 | 
			
		||||
@@ -139,19 +201,78 @@
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- 配置Dialog -->
 | 
			
		||||
    <dialog v-if="showConfigDialog" class="modal modal-open">
 | 
			
		||||
      <form
 | 
			
		||||
        method="dialog"
 | 
			
		||||
        class="modal-box max-w-fit"
 | 
			
		||||
        @submit.prevent="onConfigSubmit"
 | 
			
		||||
      >
 | 
			
		||||
        <h3 class="font-bold text-lg mb-4">调试器基本配置</h3>
 | 
			
		||||
        <div class="flex flex-col gap-4 w-80">
 | 
			
		||||
          <BaseInputField
 | 
			
		||||
            v-model="config.clkFreq"
 | 
			
		||||
            label="时钟频率 (MHz)"
 | 
			
		||||
            type="number"
 | 
			
		||||
            min="1"
 | 
			
		||||
            max="200"
 | 
			
		||||
            :error="
 | 
			
		||||
              config.clkFreq < 1 || config.clkFreq > 500 ? '范围1~200' : ''
 | 
			
		||||
            "
 | 
			
		||||
            required
 | 
			
		||||
          />
 | 
			
		||||
          <BaseInputField
 | 
			
		||||
            v-model="config.totalPortNum"
 | 
			
		||||
            label="启用端口数"
 | 
			
		||||
            type="number"
 | 
			
		||||
            min="1"
 | 
			
		||||
            max="16"
 | 
			
		||||
            :error="
 | 
			
		||||
              config.totalPortNum < 1 || config.totalPortNum > 16
 | 
			
		||||
                ? '范围1~16'
 | 
			
		||||
                : ''
 | 
			
		||||
            "
 | 
			
		||||
            required
 | 
			
		||||
          />
 | 
			
		||||
          <BaseInputField
 | 
			
		||||
            v-model="config.captureDepth"
 | 
			
		||||
            label="采样深度"
 | 
			
		||||
            type="number"
 | 
			
		||||
            min="1"
 | 
			
		||||
            max="1048576"
 | 
			
		||||
            :error="
 | 
			
		||||
              config.captureDepth < 1 || config.captureDepth > 1048576
 | 
			
		||||
                ? '范围1~1048576'
 | 
			
		||||
                : ''
 | 
			
		||||
            "
 | 
			
		||||
            required
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="modal-action mt-6">
 | 
			
		||||
          <button class="btn btn-primary" type="submit">确定</button>
 | 
			
		||||
          <button class="btn" type="button" @click="showConfigDialog = false">
 | 
			
		||||
            取消
 | 
			
		||||
          </button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </form>
 | 
			
		||||
    </dialog>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { CaptureMode } from "@/APIClient";
 | 
			
		||||
import { CaptureMode, ChannelConfig, DebuggerConfig } from "@/APIClient";
 | 
			
		||||
import { useAlertStore } from "@/components/Alert";
 | 
			
		||||
import BaseInputField from "@/components/InputField/BaseInputField.vue";
 | 
			
		||||
import type { LogicDataType } from "@/components/WaveformDisplay";
 | 
			
		||||
import WaveformDisplay from "@/components/WaveformDisplay/WaveformDisplay.vue";
 | 
			
		||||
import { AuthManager } from "@/utils/AuthManager";
 | 
			
		||||
import { useRequiredInjection } from "@/utils/Common";
 | 
			
		||||
import { useLocalStorage } from "@vueuse/core";
 | 
			
		||||
import axios, { type CancelTokenSource } from "axios";
 | 
			
		||||
import { isNull } from "lodash";
 | 
			
		||||
import { Play, Square, Zap } from "lucide-vue-next";
 | 
			
		||||
import { ref } from "vue";
 | 
			
		||||
import { ref, reactive, computed } from "vue";
 | 
			
		||||
 | 
			
		||||
interface DebugChannel {
 | 
			
		||||
  name: string;
 | 
			
		||||
@@ -159,6 +280,15 @@ interface DebugChannel {
 | 
			
		||||
  color: string;
 | 
			
		||||
  trigger: CaptureMode;
 | 
			
		||||
  width: number;
 | 
			
		||||
  widthStr: string; // "start:宽度"
 | 
			
		||||
  start: number;
 | 
			
		||||
  parentPort: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface DebuggerSettings {
 | 
			
		||||
  clkFreq: number;
 | 
			
		||||
  totalPortNum: number;
 | 
			
		||||
  captureDepth: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const triggerModes = [
 | 
			
		||||
@@ -169,153 +299,234 @@ const triggerModes = [
 | 
			
		||||
  { value: CaptureMode.Fall, label: "↓ (下降沿)" },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// 基本配置
 | 
			
		||||
const config = reactive<DebuggerSettings>({
 | 
			
		||||
  clkFreq: 50,
 | 
			
		||||
  totalPortNum: 1,
 | 
			
		||||
  captureDepth: 1024,
 | 
			
		||||
});
 | 
			
		||||
const configInited = ref(false);
 | 
			
		||||
const showConfigDialog = ref(false);
 | 
			
		||||
 | 
			
		||||
function onConfigSubmit() {
 | 
			
		||||
  configInited.value = true;
 | 
			
		||||
  showConfigDialog.value = false;
 | 
			
		||||
  // 清空通道
 | 
			
		||||
  channels.value = [];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 通道配置
 | 
			
		||||
const channels = useLocalStorage<DebugChannel[]>("debugger-channels", []);
 | 
			
		||||
const captureData = ref<LogicDataType>();
 | 
			
		||||
const alert = useRequiredInjection(useAlertStore);
 | 
			
		||||
 | 
			
		||||
const isCapturing = ref(false);
 | 
			
		||||
const readCancelTokenSource = ref<CancelTokenSource | null>(null);
 | 
			
		||||
 | 
			
		||||
async function startCapture() {
 | 
			
		||||
  if (channels.value.length === 0) {
 | 
			
		||||
    alert.error("请至少添加一个通道");
 | 
			
		||||
// 解析widthStr为start/width
 | 
			
		||||
function parseWidthStr(idx: number) {
 | 
			
		||||
  const ch = channels.value[idx];
 | 
			
		||||
  const match = /^(\d+)\s*:\s*(\d+)$/.exec(ch.widthStr);
 | 
			
		||||
  if (isNull(match)) {
 | 
			
		||||
    alert.error("格式错误,应为 起始位:宽度,如 0:7");
 | 
			
		||||
    ch.widthStr = `${ch.start}:${ch.width}`;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isCapturing.value = true;
 | 
			
		||||
  const client = AuthManager.createAuthenticatedDebuggerClient();
 | 
			
		||||
  const min = Math.min(parseInt(match[1]), parseInt(match[2]));
 | 
			
		||||
  const max = Math.max(parseInt(match[1]), parseInt(match[2]));
 | 
			
		||||
 | 
			
		||||
  for (let i = 0; i < channels.value.length; i++) {
 | 
			
		||||
    const channel = channels.value[i];
 | 
			
		||||
    if (!channel.visible) continue;
 | 
			
		||||
    if (!channel.name) {
 | 
			
		||||
      alert.error(`通道 ${i + 1} 名称不能为空`);
 | 
			
		||||
      isCapturing.value = false;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (channel.width < 1 || channel.width > 32) {
 | 
			
		||||
      alert.error(`通道 ${i + 1} 数据位数必须在1到32之间`);
 | 
			
		||||
      isCapturing.value = false;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
      let ret = await client.setMode(i, channel.trigger);
 | 
			
		||||
      if (!ret) {
 | 
			
		||||
        alert.error(`设置通道 ${i + 1} 触发模式失败`);
 | 
			
		||||
        isCapturing.value = false;
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error: any) {
 | 
			
		||||
      alert.error(`设置通道 ${i + 1} 触发模式失败: ${error.message}`);
 | 
			
		||||
      isCapturing.value = false;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    let ret = await client.startTrigger();
 | 
			
		||||
    if (!ret) {
 | 
			
		||||
      alert.error("开始捕获失败,请检查连接");
 | 
			
		||||
      isCapturing.value = false;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    while ((await client.readFlag()) !== 1 && isCapturing.value) {
 | 
			
		||||
      await new Promise((resolve) => setTimeout(resolve, 500));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!isCapturing.value) {
 | 
			
		||||
      alert.info("捕获已停止");
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const base64Data = await client.readData(0);
 | 
			
		||||
    const binaryString = atob(base64Data);
 | 
			
		||||
    const bytes = new Uint8Array(binaryString.length);
 | 
			
		||||
    for (let i = 0; i < binaryString.length; i++) {
 | 
			
		||||
      bytes[i] = binaryString.charCodeAt(i);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 数据分割
 | 
			
		||||
    // 1. 统计每个通道的位宽
 | 
			
		||||
    const enabledChannels = channels.value.filter((ch) => ch.visible);
 | 
			
		||||
    const widths = enabledChannels.map((ch) => ch.width);
 | 
			
		||||
    const totalBitsPerSample = widths.reduce((a, b) => a + b, 0);
 | 
			
		||||
 | 
			
		||||
    // 2. 计算总采样点数
 | 
			
		||||
    const totalBits = bytes.length * 8;
 | 
			
		||||
    const sampleCount = Math.floor(totalBits / totalBitsPerSample);
 | 
			
		||||
 | 
			
		||||
    // 3. 逐采样点解析
 | 
			
		||||
    let bitOffset = 0;
 | 
			
		||||
    const channelValues: number[][] = enabledChannels.map(() => []);
 | 
			
		||||
    for (let sampleIdx = 0; sampleIdx < sampleCount; sampleIdx++) {
 | 
			
		||||
      for (let chIdx = 0; chIdx < enabledChannels.length; chIdx++) {
 | 
			
		||||
        const width = widths[chIdx];
 | 
			
		||||
        let value = 0;
 | 
			
		||||
        for (let w = 0; w < width; w++) {
 | 
			
		||||
          const absBit = bitOffset + w;
 | 
			
		||||
          const byteIdx = Math.floor(absBit / 8);
 | 
			
		||||
          const bitInByte = absBit % 8;
 | 
			
		||||
          const bit = (bytes[byteIdx] >> (7 - bitInByte)) & 1;
 | 
			
		||||
          value = (value << 1) | bit;
 | 
			
		||||
        }
 | 
			
		||||
        channelValues[chIdx].push(value);
 | 
			
		||||
        bitOffset += width;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 4. 构造LogicDataType
 | 
			
		||||
    const x: number[] = [];
 | 
			
		||||
    const xUnit = "us"; // 5MHz -> 0.2us/point, 但WaveformDisplay支持ns/ms/us/s,选us
 | 
			
		||||
    for (let i = 0; i < sampleCount; i++) {
 | 
			
		||||
      x.push(i * 0.2); // 0.2us per sample
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const y = enabledChannels.map((ch, idx) => ({
 | 
			
		||||
      enabled: true,
 | 
			
		||||
      type: ch.width === 1 ? ("logic" as const) : ("number" as const),
 | 
			
		||||
      name: ch.name,
 | 
			
		||||
      color: ch.color,
 | 
			
		||||
      value: channelValues[idx],
 | 
			
		||||
      base: ch.width === 1 ? ("bin" as const) : ("hex" as const),
 | 
			
		||||
    }));
 | 
			
		||||
 | 
			
		||||
    captureData.value = {
 | 
			
		||||
      x: x,
 | 
			
		||||
      y: y,
 | 
			
		||||
      xUnit: "us",
 | 
			
		||||
    };
 | 
			
		||||
  } catch (error: any) {
 | 
			
		||||
    alert.error(`开始捕获失败: ${error.message}`);
 | 
			
		||||
    isCapturing.value = false;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function stopCapture() {
 | 
			
		||||
  isCapturing.value = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleDeleteData() {
 | 
			
		||||
  captureData.value = undefined;
 | 
			
		||||
  ch.start = min;
 | 
			
		||||
  ch.width = max - min + 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function addChannel() {
 | 
			
		||||
  if (channels.value.length >= 16) {
 | 
			
		||||
    alert.error("最多只能添加16个通道");
 | 
			
		||||
  if (!configInited.value) {
 | 
			
		||||
    alert.error("请先配置调试器基本参数");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (channels.value.length >= config.totalPortNum * 32) {
 | 
			
		||||
    alert.error("通道数已达最大线宽数");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  channels.value.push({
 | 
			
		||||
    name: `CH${channels.value.length + 1}`,
 | 
			
		||||
    visible: true,
 | 
			
		||||
    color: "#00bcd4",
 | 
			
		||||
    trigger: CaptureMode.None,
 | 
			
		||||
    width: 1,
 | 
			
		||||
    widthStr: "0:1",
 | 
			
		||||
    start: 0,
 | 
			
		||||
    parentPort: 0,
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function removeChannel(idx: number) {
 | 
			
		||||
  channels.value.splice(idx, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleDeleteData() {
 | 
			
		||||
  captureData.value = undefined;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function stopCapture() {
 | 
			
		||||
  isCapturing.value = false;
 | 
			
		||||
  if (readCancelTokenSource.value) {
 | 
			
		||||
    readCancelTokenSource.value.cancel("用户手动停止捕获");
 | 
			
		||||
    readCancelTokenSource.value = null;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function startCapture(isRestart = false) {
 | 
			
		||||
  if (!configInited.value) {
 | 
			
		||||
    alert.error("请先配置调试器基本参数");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (channels.value.length === 0) {
 | 
			
		||||
    alert.error("请至少添加一个通道");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  // 校验通道参数
 | 
			
		||||
  if (!isRestart) {
 | 
			
		||||
    let usedWires = 0;
 | 
			
		||||
    for (let i = 0; i < channels.value.length; i++) {
 | 
			
		||||
      const ch = channels.value[i];
 | 
			
		||||
      if (!ch.visible) continue;
 | 
			
		||||
      if (!ch.name) {
 | 
			
		||||
        alert.error(`通道 ${i + 1} 名称不能为空`);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      if (ch.width < 1 || ch.width > 32) {
 | 
			
		||||
        alert.error(`通道 ${i + 1} 数据位宽必须在1到32之间`);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      if (ch.start < 0 || ch.start + ch.width > 32) {
 | 
			
		||||
        alert.error(`通道 ${i + 1} 起始位+宽度不能超过32`);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      if (ch.parentPort < 0 || ch.parentPort >= config.totalPortNum) {
 | 
			
		||||
        alert.error(`通道 ${i + 1} 父端口编号超出范围`);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      usedWires += ch.width;
 | 
			
		||||
    }
 | 
			
		||||
    if (usedWires > config.totalPortNum * 32) {
 | 
			
		||||
      alert.error("所有通道线宽总和不能超过最大线宽数");
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isCapturing.value = true;
 | 
			
		||||
  const client = AuthManager.createAuthenticatedDebuggerClient();
 | 
			
		||||
 | 
			
		||||
  // 构造API配置
 | 
			
		||||
  const channelConfigs = channels.value
 | 
			
		||||
    .filter((ch) => ch.visible)
 | 
			
		||||
    .map(
 | 
			
		||||
      (ch) =>
 | 
			
		||||
        new ChannelConfig({
 | 
			
		||||
          name: ch.name,
 | 
			
		||||
          color: ch.color,
 | 
			
		||||
          wireWidth: ch.width,
 | 
			
		||||
          wireStartIndex: ch.start,
 | 
			
		||||
          parentPort: ch.parentPort,
 | 
			
		||||
          mode: ch.trigger,
 | 
			
		||||
        }),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  const apiConfig = new DebuggerConfig({
 | 
			
		||||
    clkFreq: config.clkFreq,
 | 
			
		||||
    totalPortNum: config.totalPortNum,
 | 
			
		||||
    captureDepth: config.captureDepth,
 | 
			
		||||
    triggerNum: 0,
 | 
			
		||||
    channelConfigs: channelConfigs,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    // 设置通道模式
 | 
			
		||||
    if (!isRestart) {
 | 
			
		||||
      let ret = await client.setChannelsMode(apiConfig);
 | 
			
		||||
      if (!ret) {
 | 
			
		||||
        alert.error("设置通道模式失败");
 | 
			
		||||
        isCapturing.value = false;
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // 启动捕获
 | 
			
		||||
      ret = await client.startTrigger();
 | 
			
		||||
      if (!ret) {
 | 
			
		||||
        alert.error("开始捕获失败,请检查连接");
 | 
			
		||||
        isCapturing.value = false;
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      let ret = await client.restartTrigger();
 | 
			
		||||
      if (!ret) {
 | 
			
		||||
        alert.error("重新开始捕获失败,请检查连接");
 | 
			
		||||
        isCapturing.value = false;
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 读取数据
 | 
			
		||||
    readCancelTokenSource.value = axios.CancelToken.source();
 | 
			
		||||
    const readDataPromise = client
 | 
			
		||||
      .readData(apiConfig, readCancelTokenSource.value.token)
 | 
			
		||||
      .then((data) => {
 | 
			
		||||
        const enabledChannels = channelConfigs;
 | 
			
		||||
        const sampleCount = config.captureDepth;
 | 
			
		||||
 | 
			
		||||
        // 解析数据
 | 
			
		||||
        const y = data.map((cd, idx) => {
 | 
			
		||||
          const ch = enabledChannels[idx];
 | 
			
		||||
          const bin = atob(cd.data);
 | 
			
		||||
          // UInt32数组
 | 
			
		||||
          const arr = [];
 | 
			
		||||
          for (let i = 0; i < bin.length; i += 4) {
 | 
			
		||||
            arr.push(
 | 
			
		||||
              (bin.charCodeAt(i) << 24) |
 | 
			
		||||
                (bin.charCodeAt(i + 1) << 16) |
 | 
			
		||||
                (bin.charCodeAt(i + 2) << 8) |
 | 
			
		||||
                bin.charCodeAt(i + 3),
 | 
			
		||||
            );
 | 
			
		||||
          }
 | 
			
		||||
          // 截取采样深度
 | 
			
		||||
          return {
 | 
			
		||||
            enabled: true,
 | 
			
		||||
            type: ch.wireWidth === 1 ? ("logic" as const) : ("number" as const),
 | 
			
		||||
            name: ch.name,
 | 
			
		||||
            color: ch.color,
 | 
			
		||||
            value: arr.slice(0, sampleCount),
 | 
			
		||||
            base: ch.wireWidth === 1 ? ("bin" as const) : ("hex" as const),
 | 
			
		||||
          };
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const x: number[] = [];
 | 
			
		||||
        for (let i = 0; i < sampleCount; i++) {
 | 
			
		||||
          x.push(i * (1 / config.clkFreq)); // us
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        captureData.value = {
 | 
			
		||||
          x,
 | 
			
		||||
          y,
 | 
			
		||||
          xUnit: "us",
 | 
			
		||||
        };
 | 
			
		||||
      })
 | 
			
		||||
      .catch((error) => {
 | 
			
		||||
        if (axios.isCancel(error)) {
 | 
			
		||||
          alert.info("捕获已取消");
 | 
			
		||||
        } else {
 | 
			
		||||
          alert.error(`读取数据失败: ${error.message}`);
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      .finally(() => {
 | 
			
		||||
        isCapturing.value = false;
 | 
			
		||||
        readCancelTokenSource.value = null;
 | 
			
		||||
      });
 | 
			
		||||
  } catch (error: any) {
 | 
			
		||||
    alert.error(`开始捕获失败: ${error.message}`);
 | 
			
		||||
    isCapturing.value = false;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user