Merge branch 'master' of ssh://git.swordlost.top:222/SikongJueluo/FPGA_WebLab
This commit is contained in:
commit
683d918d30
|
@ -13,6 +13,7 @@ declare module 'vue' {
|
||||||
BaseBoard: typeof import('./src/components/equipments/BaseBoard.vue')['default']
|
BaseBoard: typeof import('./src/components/equipments/BaseBoard.vue')['default']
|
||||||
BaseInputField: typeof import('./src/components/InputField/BaseInputField.vue')['default']
|
BaseInputField: typeof import('./src/components/InputField/BaseInputField.vue')['default']
|
||||||
Canvas: typeof import('./src/components/Canvas.vue')['default']
|
Canvas: typeof import('./src/components/Canvas.vue')['default']
|
||||||
|
ChannelConfig: typeof import('./src/components/LogicAnalyzer/ChannelConfig.vue')['default']
|
||||||
CollapsibleSection: typeof import('./src/components/CollapsibleSection.vue')['default']
|
CollapsibleSection: typeof import('./src/components/CollapsibleSection.vue')['default']
|
||||||
ComponentSelector: typeof import('./src/components/LabCanvas/ComponentSelector.vue')['default']
|
ComponentSelector: typeof import('./src/components/LabCanvas/ComponentSelector.vue')['default']
|
||||||
DDR: typeof import('./src/components/equipments/DDR.vue')['default']
|
DDR: typeof import('./src/components/equipments/DDR.vue')['default']
|
||||||
|
@ -67,6 +68,7 @@ declare module 'vue' {
|
||||||
TabsTrigger: typeof import('reka-ui')['TabsTrigger']
|
TabsTrigger: typeof import('reka-ui')['TabsTrigger']
|
||||||
ThemeControlButton: typeof import('./src/components/ThemeControlButton.vue')['default']
|
ThemeControlButton: typeof import('./src/components/ThemeControlButton.vue')['default']
|
||||||
ThemeControlToggle: typeof import('./src/components/ThemeControlToggle.vue')['default']
|
ThemeControlToggle: typeof import('./src/components/ThemeControlToggle.vue')['default']
|
||||||
|
TriggerSettings: typeof import('./src/components/LogicAnalyzer/TriggerSettings.vue')['default']
|
||||||
TutorialCarousel: typeof import('./src/components/TutorialCarousel.vue')['default']
|
TutorialCarousel: typeof import('./src/components/TutorialCarousel.vue')['default']
|
||||||
UploadCard: typeof import('./src/components/UploadCard.vue')['default']
|
UploadCard: typeof import('./src/components/UploadCard.vue')['default']
|
||||||
WaveformDisplay: typeof import('./src/components/Oscilloscope/WaveformDisplay.vue')['default']
|
WaveformDisplay: typeof import('./src/components/Oscilloscope/WaveformDisplay.vue')['default']
|
||||||
|
|
|
@ -346,25 +346,28 @@ public class UDPServer
|
||||||
return retPack.Value;
|
return retPack.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReceiveHandler(byte[] data, IPEndPoint endPoint)
|
private async Task ReceiveHandler(byte[] data, IPEndPoint endPoint, DateTime time)
|
||||||
{
|
{
|
||||||
// Handle RemoteEP
|
// 异步锁保护 udpData
|
||||||
if (endPoint is null)
|
await Task.Run(() =>
|
||||||
{
|
{
|
||||||
logger.Debug($"Receive Data from Unknown at {DateTime.Now.ToString()}:");
|
lock (udpData)
|
||||||
logger.Debug($" Original Data : {BitConverter.ToString(data).Replace("-", " ")}");
|
{
|
||||||
return;
|
// Handle RemoteEP
|
||||||
}
|
if (endPoint is null)
|
||||||
|
{
|
||||||
|
logger.Debug($"Receive Data from Unknown at {DateTime.Now.ToString()}:");
|
||||||
|
logger.Debug($" Original Data : {BitConverter.ToString(data).Replace("-", " ")}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 异步处理数据包
|
var udpDataObj = RecordUDPData(data, endPoint, time, Convert.ToInt32(data[1]));
|
||||||
Task.Run(() =>
|
PrintData(udpDataObj);
|
||||||
{
|
}
|
||||||
var udpData = RecordUDPData(data, endPoint, Convert.ToInt32(data[1]));
|
|
||||||
PrintData(udpData);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private UDPData RecordUDPData(byte[] bytes, IPEndPoint remoteEP, int taskID)
|
private UDPData RecordUDPData(byte[] bytes, IPEndPoint remoteEP, DateTime time, int taskID)
|
||||||
{
|
{
|
||||||
var remoteAddress = remoteEP.Address.ToString();
|
var remoteAddress = remoteEP.Address.ToString();
|
||||||
var remotePort = remoteEP.Port;
|
var remotePort = remoteEP.Port;
|
||||||
|
@ -374,7 +377,7 @@ public class UDPServer
|
||||||
Port = remotePort,
|
Port = remotePort,
|
||||||
TaskID = taskID,
|
TaskID = taskID,
|
||||||
Data = bytes,
|
Data = bytes,
|
||||||
DateTime = DateTime.Now,
|
DateTime = time,
|
||||||
HasRead = false,
|
HasRead = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -482,7 +485,7 @@ public class UDPServer
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
UdpReceiveResult result = await client.ReceiveAsync();
|
UdpReceiveResult result = await client.ReceiveAsync();
|
||||||
ReceiveHandler(result.Buffer, result.RemoteEndPoint);
|
ReceiveHandler(result.Buffer, result.RemoteEndPoint, DateTime.Now);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -510,6 +513,7 @@ public class UDPServer
|
||||||
item.Close();
|
item.Close();
|
||||||
}
|
}
|
||||||
this.isRunning = false;
|
this.isRunning = false;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
677
src/APIClient.ts
677
src/APIClient.ts
|
@ -465,6 +465,90 @@ export class VideoStreamClient {
|
||||||
}
|
}
|
||||||
return Promise.resolve<any>(null as any);
|
return Promise.resolve<any>(null as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化摄像头自动对焦功能
|
||||||
|
* @return 初始化结果
|
||||||
|
*/
|
||||||
|
initAutoFocus(): Promise<FileResponse | null> {
|
||||||
|
let url_ = this.baseUrl + "/api/VideoStream/InitAutoFocus";
|
||||||
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
let options_: RequestInit = {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/octet-stream"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||||
|
return this.processInitAutoFocus(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processInitAutoFocus(response: Response): Promise<FileResponse | null> {
|
||||||
|
const status = response.status;
|
||||||
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
|
if (status === 200 || status === 206) {
|
||||||
|
const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
|
||||||
|
let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined;
|
||||||
|
let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined;
|
||||||
|
if (fileName) {
|
||||||
|
fileName = decodeURIComponent(fileName);
|
||||||
|
} else {
|
||||||
|
fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
|
||||||
|
fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
|
||||||
|
}
|
||||||
|
return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; });
|
||||||
|
} else if (status !== 200 && status !== 204) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve<FileResponse | null>(null as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行自动对焦
|
||||||
|
* @return 对焦结果
|
||||||
|
*/
|
||||||
|
autoFocus(): Promise<FileResponse | null> {
|
||||||
|
let url_ = this.baseUrl + "/api/VideoStream/AutoFocus";
|
||||||
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
let options_: RequestInit = {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/octet-stream"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||||
|
return this.processAutoFocus(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processAutoFocus(response: Response): Promise<FileResponse | null> {
|
||||||
|
const status = response.status;
|
||||||
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
|
if (status === 200 || status === 206) {
|
||||||
|
const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
|
||||||
|
let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined;
|
||||||
|
let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined;
|
||||||
|
if (fileName) {
|
||||||
|
fileName = decodeURIComponent(fileName);
|
||||||
|
} else {
|
||||||
|
fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
|
||||||
|
fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
|
||||||
|
}
|
||||||
|
return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; });
|
||||||
|
} else if (status !== 200 && status !== 204) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve<FileResponse | null>(null as any);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BsdlParserClient {
|
export class BsdlParserClient {
|
||||||
|
@ -1885,6 +1969,451 @@ export class JtagClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class LogicAnalyzerClient {
|
||||||
|
private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
|
||||||
|
private baseUrl: string;
|
||||||
|
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
|
||||||
|
|
||||||
|
constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
|
||||||
|
this.http = http ? http : window as any;
|
||||||
|
this.baseUrl = baseUrl ?? "http://localhost:5000";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置捕获模式
|
||||||
|
* @param captureOn (optional) 是否开始捕获
|
||||||
|
* @param force (optional) 是否强制捕获
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
setCaptureMode(captureOn: boolean | undefined, force: boolean | undefined): Promise<boolean> {
|
||||||
|
let url_ = this.baseUrl + "/api/LogicAnalyzer/SetCaptureMode?";
|
||||||
|
if (captureOn === null)
|
||||||
|
throw new Error("The parameter 'captureOn' cannot be null.");
|
||||||
|
else if (captureOn !== undefined)
|
||||||
|
url_ += "captureOn=" + encodeURIComponent("" + captureOn) + "&";
|
||||||
|
if (force === null)
|
||||||
|
throw new Error("The parameter 'force' cannot be null.");
|
||||||
|
else if (force !== undefined)
|
||||||
|
url_ += "force=" + encodeURIComponent("" + force) + "&";
|
||||||
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
let options_: RequestInit = {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||||
|
return this.processSetCaptureMode(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processSetCaptureMode(response: Response): Promise<boolean> {
|
||||||
|
const status = response.status;
|
||||||
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
|
if (status === 200) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result200: any = null;
|
||||||
|
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result200 = resultData200 !== undefined ? resultData200 : <any>null;
|
||||||
|
|
||||||
|
return result200;
|
||||||
|
});
|
||||||
|
} else if (status === 400) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result400: any = null;
|
||||||
|
let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result400 = ProblemDetails.fromJS(resultData400);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result400);
|
||||||
|
});
|
||||||
|
} else if (status === 500) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
} else if (status === 401) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result401: any = null;
|
||||||
|
let resultData401 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result401 = ProblemDetails.fromJS(resultData401);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result401);
|
||||||
|
});
|
||||||
|
} else if (status !== 200 && status !== 204) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve<boolean>(null as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取捕获状态
|
||||||
|
* @return 捕获状态
|
||||||
|
*/
|
||||||
|
getCaptureStatus(): Promise<CaptureStatus> {
|
||||||
|
let url_ = this.baseUrl + "/api/LogicAnalyzer/GetCaptureStatus";
|
||||||
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
let options_: RequestInit = {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||||
|
return this.processGetCaptureStatus(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processGetCaptureStatus(response: Response): Promise<CaptureStatus> {
|
||||||
|
const status = response.status;
|
||||||
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
|
if (status === 200) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result200: any = null;
|
||||||
|
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result200 = resultData200 !== undefined ? resultData200 : <any>null;
|
||||||
|
|
||||||
|
return result200;
|
||||||
|
});
|
||||||
|
} else if (status === 400) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result400: any = null;
|
||||||
|
let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result400 = ProblemDetails.fromJS(resultData400);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result400);
|
||||||
|
});
|
||||||
|
} else if (status === 500) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
} else if (status === 401) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result401: any = null;
|
||||||
|
let resultData401 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result401 = ProblemDetails.fromJS(resultData401);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result401);
|
||||||
|
});
|
||||||
|
} else if (status !== 200 && status !== 204) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve<CaptureStatus>(null as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置全局触发模式
|
||||||
|
* @param mode (optional) 全局触发模式
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
setGlobalTrigMode(mode: GlobalCaptureMode | undefined): Promise<boolean> {
|
||||||
|
let url_ = this.baseUrl + "/api/LogicAnalyzer/SetGlobalTrigMode?";
|
||||||
|
if (mode === null)
|
||||||
|
throw new Error("The parameter 'mode' cannot be null.");
|
||||||
|
else if (mode !== undefined)
|
||||||
|
url_ += "mode=" + encodeURIComponent("" + mode) + "&";
|
||||||
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
let options_: RequestInit = {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||||
|
return this.processSetGlobalTrigMode(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processSetGlobalTrigMode(response: Response): Promise<boolean> {
|
||||||
|
const status = response.status;
|
||||||
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
|
if (status === 200) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result200: any = null;
|
||||||
|
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result200 = resultData200 !== undefined ? resultData200 : <any>null;
|
||||||
|
|
||||||
|
return result200;
|
||||||
|
});
|
||||||
|
} else if (status === 400) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result400: any = null;
|
||||||
|
let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result400 = ProblemDetails.fromJS(resultData400);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result400);
|
||||||
|
});
|
||||||
|
} else if (status === 500) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
} else if (status === 401) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result401: any = null;
|
||||||
|
let resultData401 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result401 = ProblemDetails.fromJS(resultData401);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result401);
|
||||||
|
});
|
||||||
|
} else if (status !== 200 && status !== 204) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve<boolean>(null as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置信号触发模式
|
||||||
|
* @param signalIndex (optional) 信号索引 (0-7)
|
||||||
|
* @param op (optional) 操作符
|
||||||
|
* @param val (optional) 信号值
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
setSignalTrigMode(signalIndex: number | undefined, op: SignalOperator | undefined, val: SignalValue | undefined): Promise<boolean> {
|
||||||
|
let url_ = this.baseUrl + "/api/LogicAnalyzer/SetSignalTrigMode?";
|
||||||
|
if (signalIndex === null)
|
||||||
|
throw new Error("The parameter 'signalIndex' cannot be null.");
|
||||||
|
else if (signalIndex !== undefined)
|
||||||
|
url_ += "signalIndex=" + encodeURIComponent("" + signalIndex) + "&";
|
||||||
|
if (op === null)
|
||||||
|
throw new Error("The parameter 'op' cannot be null.");
|
||||||
|
else if (op !== undefined)
|
||||||
|
url_ += "op=" + encodeURIComponent("" + op) + "&";
|
||||||
|
if (val === null)
|
||||||
|
throw new Error("The parameter 'val' cannot be null.");
|
||||||
|
else if (val !== undefined)
|
||||||
|
url_ += "val=" + encodeURIComponent("" + val) + "&";
|
||||||
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
let options_: RequestInit = {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||||
|
return this.processSetSignalTrigMode(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processSetSignalTrigMode(response: Response): Promise<boolean> {
|
||||||
|
const status = response.status;
|
||||||
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
|
if (status === 200) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result200: any = null;
|
||||||
|
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result200 = resultData200 !== undefined ? resultData200 : <any>null;
|
||||||
|
|
||||||
|
return result200;
|
||||||
|
});
|
||||||
|
} else if (status === 400) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result400: any = null;
|
||||||
|
let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result400 = ProblemDetails.fromJS(resultData400);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result400);
|
||||||
|
});
|
||||||
|
} else if (status === 500) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
} else if (status === 401) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result401: any = null;
|
||||||
|
let resultData401 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result401 = ProblemDetails.fromJS(resultData401);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result401);
|
||||||
|
});
|
||||||
|
} else if (status !== 200 && status !== 204) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve<boolean>(null as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量配置捕获参数
|
||||||
|
* @param config 捕获配置
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
configureCapture(config: CaptureConfig): Promise<boolean> {
|
||||||
|
let url_ = this.baseUrl + "/api/LogicAnalyzer/ConfigureCapture";
|
||||||
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
const content_ = JSON.stringify(config);
|
||||||
|
|
||||||
|
let options_: RequestInit = {
|
||||||
|
body: content_,
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||||
|
return this.processConfigureCapture(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processConfigureCapture(response: Response): Promise<boolean> {
|
||||||
|
const status = response.status;
|
||||||
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
|
if (status === 200) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result200: any = null;
|
||||||
|
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result200 = resultData200 !== undefined ? resultData200 : <any>null;
|
||||||
|
|
||||||
|
return result200;
|
||||||
|
});
|
||||||
|
} else if (status === 400) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result400: any = null;
|
||||||
|
let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result400 = ProblemDetails.fromJS(resultData400);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result400);
|
||||||
|
});
|
||||||
|
} else if (status === 500) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
} else if (status === 401) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result401: any = null;
|
||||||
|
let resultData401 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result401 = ProblemDetails.fromJS(resultData401);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result401);
|
||||||
|
});
|
||||||
|
} else if (status !== 200 && status !== 204) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve<boolean>(null as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 强制捕获
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
forceCapture(): Promise<boolean> {
|
||||||
|
let url_ = this.baseUrl + "/api/LogicAnalyzer/ForceCapture";
|
||||||
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
let options_: RequestInit = {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||||
|
return this.processForceCapture(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processForceCapture(response: Response): Promise<boolean> {
|
||||||
|
const status = response.status;
|
||||||
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
|
if (status === 200) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result200: any = null;
|
||||||
|
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result200 = resultData200 !== undefined ? resultData200 : <any>null;
|
||||||
|
|
||||||
|
return result200;
|
||||||
|
});
|
||||||
|
} else if (status === 400) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result400: any = null;
|
||||||
|
let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result400 = ProblemDetails.fromJS(resultData400);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result400);
|
||||||
|
});
|
||||||
|
} else if (status === 500) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
} else if (status === 401) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result401: any = null;
|
||||||
|
let resultData401 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result401 = ProblemDetails.fromJS(resultData401);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result401);
|
||||||
|
});
|
||||||
|
} else if (status !== 200 && status !== 204) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve<boolean>(null as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取捕获数据
|
||||||
|
* @return 捕获的波形数据(Base64编码)
|
||||||
|
*/
|
||||||
|
getCaptureData(): Promise<string> {
|
||||||
|
let url_ = this.baseUrl + "/api/LogicAnalyzer/GetCaptureData";
|
||||||
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
|
|
||||||
|
let options_: RequestInit = {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||||
|
return this.processGetCaptureData(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processGetCaptureData(response: Response): Promise<string> {
|
||||||
|
const status = response.status;
|
||||||
|
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||||
|
if (status === 200) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result200: any = null;
|
||||||
|
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result200 = resultData200 !== undefined ? resultData200 : <any>null;
|
||||||
|
|
||||||
|
return result200;
|
||||||
|
});
|
||||||
|
} else if (status === 400) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result400: any = null;
|
||||||
|
let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result400 = ProblemDetails.fromJS(resultData400);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result400);
|
||||||
|
});
|
||||||
|
} else if (status === 500) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
} else if (status === 401) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
let result401: any = null;
|
||||||
|
let resultData401 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
|
||||||
|
result401 = ProblemDetails.fromJS(resultData401);
|
||||||
|
return throwException("A server side error occurred.", status, _responseText, _headers, result401);
|
||||||
|
});
|
||||||
|
} else if (status !== 200 && status !== 204) {
|
||||||
|
return response.text().then((_responseText) => {
|
||||||
|
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve<string>(null as any);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class MatrixKeyClient {
|
export class MatrixKeyClient {
|
||||||
private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
|
private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
|
||||||
private baseUrl: string;
|
private baseUrl: string;
|
||||||
|
@ -3286,6 +3815,154 @@ export interface IArgumentException extends ISystemException {
|
||||||
paramName?: string | undefined;
|
paramName?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 逻辑分析仪运行状态枚举 */
|
||||||
|
export enum CaptureStatus {
|
||||||
|
None = 0,
|
||||||
|
CaptureOn = 1,
|
||||||
|
CaptureForce = 256,
|
||||||
|
CaptureBusy = 65536,
|
||||||
|
CaptureDone = 16777216,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 全局触发模式枚举,定义多路信号触发条件的逻辑组合方式 */
|
||||||
|
export enum GlobalCaptureMode {
|
||||||
|
AND = 0,
|
||||||
|
OR = 1,
|
||||||
|
NAND = 2,
|
||||||
|
NOR = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 信号M的操作符枚举 */
|
||||||
|
export enum SignalOperator {
|
||||||
|
Equal = 0,
|
||||||
|
NotEqual = 1,
|
||||||
|
LessThan = 2,
|
||||||
|
LessThanOrEqual = 3,
|
||||||
|
GreaterThan = 4,
|
||||||
|
GreaterThanOrEqual = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 信号M的值枚举 */
|
||||||
|
export enum SignalValue {
|
||||||
|
Logic0 = 0,
|
||||||
|
Logic1 = 1,
|
||||||
|
NotCare = 2,
|
||||||
|
Rise = 3,
|
||||||
|
Fall = 4,
|
||||||
|
RiseOrFall = 5,
|
||||||
|
NoChange = 6,
|
||||||
|
SomeNumber = 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 捕获配置 */
|
||||||
|
export class CaptureConfig implements ICaptureConfig {
|
||||||
|
/** 全局触发模式 */
|
||||||
|
globalMode!: GlobalCaptureMode;
|
||||||
|
/** 信号触发配置列表 */
|
||||||
|
signalConfigs!: SignalTriggerConfig[];
|
||||||
|
|
||||||
|
constructor(data?: ICaptureConfig) {
|
||||||
|
if (data) {
|
||||||
|
for (var property in data) {
|
||||||
|
if (data.hasOwnProperty(property))
|
||||||
|
(<any>this)[property] = (<any>data)[property];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!data) {
|
||||||
|
this.signalConfigs = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_data?: any) {
|
||||||
|
if (_data) {
|
||||||
|
this.globalMode = _data["globalMode"];
|
||||||
|
if (Array.isArray(_data["signalConfigs"])) {
|
||||||
|
this.signalConfigs = [] as any;
|
||||||
|
for (let item of _data["signalConfigs"])
|
||||||
|
this.signalConfigs!.push(SignalTriggerConfig.fromJS(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJS(data: any): CaptureConfig {
|
||||||
|
data = typeof data === 'object' ? data : {};
|
||||||
|
let result = new CaptureConfig();
|
||||||
|
result.init(data);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(data?: any) {
|
||||||
|
data = typeof data === 'object' ? data : {};
|
||||||
|
data["globalMode"] = this.globalMode;
|
||||||
|
if (Array.isArray(this.signalConfigs)) {
|
||||||
|
data["signalConfigs"] = [];
|
||||||
|
for (let item of this.signalConfigs)
|
||||||
|
data["signalConfigs"].push(item.toJSON());
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 捕获配置 */
|
||||||
|
export interface ICaptureConfig {
|
||||||
|
/** 全局触发模式 */
|
||||||
|
globalMode: GlobalCaptureMode;
|
||||||
|
/** 信号触发配置列表 */
|
||||||
|
signalConfigs: SignalTriggerConfig[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 信号触发配置 */
|
||||||
|
export class SignalTriggerConfig implements ISignalTriggerConfig {
|
||||||
|
/** 信号索引 (0-7) */
|
||||||
|
signalIndex!: number;
|
||||||
|
/** 操作符 */
|
||||||
|
operator!: SignalOperator;
|
||||||
|
/** 信号值 */
|
||||||
|
value!: SignalValue;
|
||||||
|
|
||||||
|
constructor(data?: ISignalTriggerConfig) {
|
||||||
|
if (data) {
|
||||||
|
for (var property in data) {
|
||||||
|
if (data.hasOwnProperty(property))
|
||||||
|
(<any>this)[property] = (<any>data)[property];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_data?: any) {
|
||||||
|
if (_data) {
|
||||||
|
this.signalIndex = _data["signalIndex"];
|
||||||
|
this.operator = _data["operator"];
|
||||||
|
this.value = _data["value"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJS(data: any): SignalTriggerConfig {
|
||||||
|
data = typeof data === 'object' ? data : {};
|
||||||
|
let result = new SignalTriggerConfig();
|
||||||
|
result.init(data);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(data?: any) {
|
||||||
|
data = typeof data === 'object' ? data : {};
|
||||||
|
data["signalIndex"] = this.signalIndex;
|
||||||
|
data["operator"] = this.operator;
|
||||||
|
data["value"] = this.value;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 信号触发配置 */
|
||||||
|
export interface ISignalTriggerConfig {
|
||||||
|
/** 信号索引 (0-7) */
|
||||||
|
signalIndex: number;
|
||||||
|
/** 操作符 */
|
||||||
|
operator: SignalOperator;
|
||||||
|
/** 信号值 */
|
||||||
|
value: SignalValue;
|
||||||
|
}
|
||||||
|
|
||||||
/** Package options which to send address to read or write */
|
/** Package options which to send address to read or write */
|
||||||
export class SendAddrPackOptions implements ISendAddrPackOptions {
|
export class SendAddrPackOptions implements ISendAddrPackOptions {
|
||||||
/** 突发类型 */
|
/** 突发类型 */
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
<template>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- 通道状态概览 -->
|
||||||
|
<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">8</div>
|
||||||
|
<div class="stat-desc">逻辑分析仪通道</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-title">启用通道</div>
|
||||||
|
<div class="stat-value text-success">{{ enabledChannelCount }}</div>
|
||||||
|
<div class="stat-desc">当前激活通道</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-title">采样率</div>
|
||||||
|
<div class="stat-value text-info">100MHz</div>
|
||||||
|
<div class="stat-desc">最大采样频率</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 通道配置列表 -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex items-center justify-between p-2 bg-base-300 rounded-lg">
|
||||||
|
<span class="font-medium">通道</span>
|
||||||
|
<span class="font-medium">启用</span>
|
||||||
|
<span class="font-medium">标签</span>
|
||||||
|
<span class="font-medium">颜色</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="(channel, index) in channels"
|
||||||
|
:key="index"
|
||||||
|
class="flex items-center justify-between p-3 bg-base-200 rounded-lg hover:bg-base-300 transition-colors"
|
||||||
|
>
|
||||||
|
<!-- 通道编号 -->
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="font-mono font-medium w-12">CH{{ index }}</span>
|
||||||
|
<div
|
||||||
|
class="w-3 h-3 rounded-full border-2 border-white shadow-sm"
|
||||||
|
:style="{ backgroundColor: channel.color }"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 启用开关 -->
|
||||||
|
<div class="form-control">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
v-model="channel.enabled"
|
||||||
|
class="toggle toggle-sm toggle-primary"
|
||||||
|
@change="updateChannelStatus(index)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 通道标签 -->
|
||||||
|
<div class="form-control w-32">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
v-model="channel.label"
|
||||||
|
:placeholder="`通道 ${index}`"
|
||||||
|
class="input input-sm input-bordered w-full"
|
||||||
|
:disabled="!channel.enabled"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 颜色选择 -->
|
||||||
|
<div class="form-control">
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
v-model="channel.color"
|
||||||
|
class="w-8 h-8 rounded border-2 border-base-300 cursor-pointer"
|
||||||
|
:disabled="!channel.enabled"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 批量操作 -->
|
||||||
|
<div class="flex gap-2 pt-4">
|
||||||
|
<button @click="enableAllChannels" class="btn btn-primary btn-sm">
|
||||||
|
全部启用
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button @click="disableAllChannels" class="btn btn-outline btn-sm">
|
||||||
|
全部禁用
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button @click="resetChannelLabels" class="btn btn-outline btn-sm">
|
||||||
|
重置标签
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, reactive } from "vue";
|
||||||
|
|
||||||
|
// 通道接口定义
|
||||||
|
interface Channel {
|
||||||
|
enabled: boolean;
|
||||||
|
label: string;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预设配置接口
|
||||||
|
interface Preset {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
channels: Partial<Channel>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认颜色数组
|
||||||
|
const defaultColors = [
|
||||||
|
"#FF5733",
|
||||||
|
"#33FF57",
|
||||||
|
"#3357FF",
|
||||||
|
"#FF33F5",
|
||||||
|
"#F5FF33",
|
||||||
|
"#33FFF5",
|
||||||
|
"#FF8C33",
|
||||||
|
"#8C33FF",
|
||||||
|
];
|
||||||
|
|
||||||
|
// 通道配置
|
||||||
|
const channels = reactive<Channel[]>(
|
||||||
|
Array.from({ length: 8 }, (_, index) => ({
|
||||||
|
enabled: false,
|
||||||
|
label: `CH${index}`,
|
||||||
|
color: defaultColors[index],
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 计算启用的通道数量
|
||||||
|
const enabledChannelCount = computed(
|
||||||
|
() => channels.filter((channel) => channel.enabled).length,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 更新通道状态
|
||||||
|
const updateChannelStatus = (index: number) => {
|
||||||
|
console.log(`通道 ${index} 状态更新:`, channels[index].enabled);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 启用所有通道
|
||||||
|
const enableAllChannels = () => {
|
||||||
|
channels.forEach((channel) => {
|
||||||
|
channel.enabled = true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 禁用所有通道
|
||||||
|
const disableAllChannels = () => {
|
||||||
|
channels.forEach((channel) => {
|
||||||
|
channel.enabled = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置通道标签
|
||||||
|
const resetChannelLabels = () => {
|
||||||
|
channels.forEach((channel, index) => {
|
||||||
|
channel.label = `CH${index}`;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 应用预设配置
|
||||||
|
const applyPreset = (preset: Preset) => {
|
||||||
|
preset.channels.forEach((presetChannel, index) => {
|
||||||
|
if (index < channels.length && presetChannel) {
|
||||||
|
Object.assign(channels[index], presetChannel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -1,6 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full h-100">
|
<div class="w-full h-150">
|
||||||
<v-chart v-if="true" class="w-full h-full" :option="option" autoresize />
|
<v-chart
|
||||||
|
v-if="data"
|
||||||
|
class="w-full h-full"
|
||||||
|
:option="option"
|
||||||
|
autoresize
|
||||||
|
:update-options="updateOptions"
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="w-full h-full flex items-center justify-center text-gray-500"
|
class="w-full h-full flex items-center justify-center text-gray-500"
|
||||||
|
@ -11,144 +17,194 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue";
|
import { computed, shallowRef } from "vue";
|
||||||
|
import VChart from "vue-echarts";
|
||||||
|
import type { LogicDataType } from "./index";
|
||||||
|
|
||||||
// Echarts
|
// Echarts
|
||||||
import { use } from "echarts/core";
|
import { use } from "echarts/core";
|
||||||
import { LineChart } from "echarts/charts";
|
import { LineChart } from "echarts/charts";
|
||||||
import {
|
import {
|
||||||
TitleComponent,
|
|
||||||
TooltipComponent,
|
TooltipComponent,
|
||||||
LegendComponent,
|
|
||||||
ToolboxComponent,
|
|
||||||
GridComponent,
|
GridComponent,
|
||||||
|
DataZoomComponent,
|
||||||
|
AxisPointerComponent,
|
||||||
|
ToolboxComponent,
|
||||||
} from "echarts/components";
|
} from "echarts/components";
|
||||||
import { CanvasRenderer } from "echarts/renderers";
|
import { CanvasRenderer } from "echarts/renderers";
|
||||||
import type { ComposeOption } from "echarts/core";
|
import type { ComposeOption } from "echarts/core";
|
||||||
import type { LineSeriesOption } from "echarts/charts";
|
import type { LineSeriesOption } from "echarts/charts";
|
||||||
import type {
|
import type {
|
||||||
TitleComponentOption,
|
AxisPointerComponentOption,
|
||||||
TooltipComponentOption,
|
TooltipComponentOption,
|
||||||
LegendComponentOption,
|
|
||||||
ToolboxComponentOption,
|
|
||||||
GridComponentOption,
|
GridComponentOption,
|
||||||
|
DataZoomComponentOption,
|
||||||
} from "echarts/components";
|
} from "echarts/components";
|
||||||
|
import type {
|
||||||
|
ToolboxComponentOption,
|
||||||
|
XAXisOption,
|
||||||
|
YAXisOption,
|
||||||
|
} from "echarts/types/dist/shared";
|
||||||
|
|
||||||
use([
|
use([
|
||||||
TitleComponent,
|
|
||||||
TooltipComponent,
|
TooltipComponent,
|
||||||
LegendComponent,
|
|
||||||
ToolboxComponent,
|
ToolboxComponent,
|
||||||
GridComponent,
|
GridComponent,
|
||||||
|
AxisPointerComponent,
|
||||||
|
DataZoomComponent,
|
||||||
LineChart,
|
LineChart,
|
||||||
CanvasRenderer,
|
CanvasRenderer,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
type EChartsOption = ComposeOption<
|
type EChartsOption = ComposeOption<
|
||||||
| TitleComponentOption
|
| AxisPointerComponentOption
|
||||||
| TooltipComponentOption
|
| TooltipComponentOption
|
||||||
| LegendComponentOption
|
|
||||||
| ToolboxComponentOption
|
| ToolboxComponentOption
|
||||||
| GridComponentOption
|
| GridComponentOption
|
||||||
|
| DataZoomComponentOption
|
||||||
| LineSeriesOption
|
| LineSeriesOption
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
// Define props
|
||||||
|
interface Props {
|
||||||
|
data?: LogicDataType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
// 添加更新选项来减少重绘
|
||||||
|
const updateOptions = shallowRef({
|
||||||
|
notMerge: false,
|
||||||
|
lazyUpdate: true,
|
||||||
|
silent: false
|
||||||
|
});
|
||||||
|
|
||||||
const option = computed((): EChartsOption => {
|
const option = computed((): EChartsOption => {
|
||||||
return {
|
if (!props.data) return {};
|
||||||
title: {
|
|
||||||
text: "Stacked Area Chart",
|
const channelCount = props.data.y.length;
|
||||||
|
const channelSpacing = 2; // 每个通道之间的间距
|
||||||
|
|
||||||
|
// 使用单个网格
|
||||||
|
const grids: GridComponentOption[] = [
|
||||||
|
{
|
||||||
|
left: "5%",
|
||||||
|
right: "5%",
|
||||||
|
top: "5%",
|
||||||
|
bottom: "15%",
|
||||||
},
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 单个X轴
|
||||||
|
const xAxis: XAXisOption[] = [
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
boundaryGap: false,
|
||||||
|
data: props.data!.x.map((x) => x.toFixed(3)),
|
||||||
|
axisLabel: {
|
||||||
|
formatter: (value: string) => `${value}${props.data!.xUnit}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 单个Y轴,范围根据通道数量调整
|
||||||
|
const yAxis: YAXisOption[] = [
|
||||||
|
{
|
||||||
|
type: "value",
|
||||||
|
min: -0.5,
|
||||||
|
max: channelCount * channelSpacing - 0.5,
|
||||||
|
interval: channelSpacing,
|
||||||
|
axisLabel: {
|
||||||
|
formatter: (value: number) => {
|
||||||
|
const channelIndex = Math.round(value / channelSpacing);
|
||||||
|
return channelIndex < channelCount
|
||||||
|
? props.data!.channelNames[channelIndex]
|
||||||
|
: "";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
splitLine: { show: false },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 创建系列数据,每个通道有不同的Y偏移
|
||||||
|
const series: LineSeriesOption[] = props.data.y.map((channelData, index) => ({
|
||||||
|
name: props.data!.channelNames[index],
|
||||||
|
type: "line",
|
||||||
|
data: channelData.map((value) => value + index * channelSpacing + 0.2),
|
||||||
|
step: "end",
|
||||||
|
lineStyle: {
|
||||||
|
width: 2,
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.3,
|
||||||
|
origin: index * channelSpacing,
|
||||||
|
},
|
||||||
|
symbol: "none",
|
||||||
|
// 优化性能配置
|
||||||
|
sampling: "lttb",
|
||||||
|
// large: true,
|
||||||
|
// largeThreshold: 2000,
|
||||||
|
// progressive: 2000,
|
||||||
|
// 减少动画以避免闪烁
|
||||||
|
animation: false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 全局动画配置
|
||||||
|
animation: false,
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: "axis",
|
trigger: "axis",
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
type: "cross",
|
type: "line",
|
||||||
label: {
|
label: {
|
||||||
backgroundColor: "#6a7985",
|
backgroundColor: "#6a7985",
|
||||||
},
|
},
|
||||||
|
// 减少axisPointer的动画
|
||||||
|
animation: false,
|
||||||
},
|
},
|
||||||
},
|
formatter: (params: any) => {
|
||||||
legend: {
|
if (Array.isArray(params) && params.length > 0) {
|
||||||
data: ["Email", "Union Ads", "Video Ads", "Direct", "Search Engine"],
|
const timeValue = props.data!.x[params[0].dataIndex];
|
||||||
|
const dataIndex = params[0].dataIndex;
|
||||||
|
|
||||||
|
let tooltip = `Time: ${timeValue.toFixed(3)}${props.data!.xUnit}<br/>`;
|
||||||
|
|
||||||
|
// 显示所有通道在当前时间点的原始数值(0或1)
|
||||||
|
props.data!.channelNames.forEach((channelName, index) => {
|
||||||
|
const originalValue = props.data!.y[index][dataIndex];
|
||||||
|
tooltip += `${channelName}: ${originalValue}<br/>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return tooltip;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
// 优化tooltip性能
|
||||||
|
hideDelay: 100,
|
||||||
},
|
},
|
||||||
toolbox: {
|
toolbox: {
|
||||||
feature: {
|
feature: {
|
||||||
saveAsImage: {},
|
restore: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
grid: {
|
grid: grids,
|
||||||
left: "3%",
|
xAxis: xAxis,
|
||||||
right: "4%",
|
yAxis: yAxis,
|
||||||
bottom: "3%",
|
dataZoom: [
|
||||||
containLabel: true,
|
|
||||||
},
|
|
||||||
xAxis: [
|
|
||||||
{
|
{
|
||||||
type: "category",
|
show: true,
|
||||||
boundaryGap: false,
|
realtime: true,
|
||||||
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
start: 0,
|
||||||
},
|
end: 100,
|
||||||
],
|
},
|
||||||
yAxis: [
|
{
|
||||||
{
|
type: "inside",
|
||||||
type: "value",
|
realtime: true,
|
||||||
},
|
start: 0,
|
||||||
],
|
end: 100,
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: "Email",
|
|
||||||
type: "line",
|
|
||||||
stack: "Total",
|
|
||||||
areaStyle: {},
|
|
||||||
emphasis: {
|
|
||||||
focus: "series",
|
|
||||||
},
|
|
||||||
data: [120, 132, 101, 134, 90, 230, 210],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Union Ads",
|
|
||||||
type: "line",
|
|
||||||
stack: "Total",
|
|
||||||
areaStyle: {},
|
|
||||||
emphasis: {
|
|
||||||
focus: "series",
|
|
||||||
},
|
|
||||||
data: [220, 182, 191, 234, 290, 330, 310],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Video Ads",
|
|
||||||
type: "line",
|
|
||||||
stack: "Total",
|
|
||||||
areaStyle: {},
|
|
||||||
emphasis: {
|
|
||||||
focus: "series",
|
|
||||||
},
|
|
||||||
data: [150, 232, 201, 154, 190, 330, 410],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Direct",
|
|
||||||
type: "line",
|
|
||||||
stack: "Total",
|
|
||||||
areaStyle: {},
|
|
||||||
emphasis: {
|
|
||||||
focus: "series",
|
|
||||||
},
|
|
||||||
data: [320, 332, 301, 334, 390, 330, 320],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Search Engine",
|
|
||||||
type: "line",
|
|
||||||
stack: "Total",
|
|
||||||
label: {
|
|
||||||
show: true,
|
|
||||||
position: "top",
|
|
||||||
},
|
|
||||||
areaStyle: {},
|
|
||||||
emphasis: {
|
|
||||||
focus: "series",
|
|
||||||
},
|
|
||||||
data: [820, 932, 901, 934, 1290, 1330, 1320],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
series: series,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,266 @@
|
||||||
|
<template>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- 全局触发模式配置 -->
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text font-medium">全局触发模式</span>
|
||||||
|
</label>
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-2">
|
||||||
|
<button
|
||||||
|
v-for="mode in globalModes"
|
||||||
|
:key="mode.value"
|
||||||
|
@click="setGlobalMode(mode.value)"
|
||||||
|
:class="[
|
||||||
|
'btn btn-sm',
|
||||||
|
currentGlobalMode === mode.value ? 'btn-primary' : 'btn-outline',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ mode.label }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text-alt text-gray-500">
|
||||||
|
选择多路信号触发条件的逻辑组合方式
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 信号触发配置 -->
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text font-medium">信号触发配置</span>
|
||||||
|
</label>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div
|
||||||
|
v-for="(signal, index) in signalConfigs"
|
||||||
|
:key="index"
|
||||||
|
class="flex items-center gap-2 p-3 bg-base-200 rounded-lg"
|
||||||
|
>
|
||||||
|
<span class="text-sm font-medium w-16">CH{{ index }}</span>
|
||||||
|
|
||||||
|
<!-- 操作符选择 -->
|
||||||
|
<select
|
||||||
|
v-model="signal.operator"
|
||||||
|
class="select select-sm select-bordered w-32"
|
||||||
|
>
|
||||||
|
<option v-for="op in operators" :key="op.value" :value="op.value">
|
||||||
|
{{ op.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 信号值选择 -->
|
||||||
|
<select
|
||||||
|
v-model="signal.value"
|
||||||
|
class="select select-sm select-bordered w-32"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="val in signalValues"
|
||||||
|
:key="val.value"
|
||||||
|
:value="val.value"
|
||||||
|
>
|
||||||
|
{{ val.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 启用/禁用开关 -->
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label cursor-pointer gap-2">
|
||||||
|
<span class="label-text text-sm">启用</span>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
v-model="signal.enabled"
|
||||||
|
class="toggle toggle-sm toggle-primary"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div class="flex gap-2 pt-4">
|
||||||
|
<button
|
||||||
|
@click="applyConfiguration"
|
||||||
|
:disabled="isApplying"
|
||||||
|
class="btn btn-primary btn-sm"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="isApplying"
|
||||||
|
class="loading loading-spinner loading-sm"
|
||||||
|
></span>
|
||||||
|
应用配置
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button @click="resetConfiguration" class="btn btn-outline btn-sm">
|
||||||
|
重置
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 状态指示 -->
|
||||||
|
<div
|
||||||
|
v-if="lastApplyResult"
|
||||||
|
class="alert alert-sm"
|
||||||
|
:class="lastApplyResult.success ? 'alert-success' : 'alert-error'"
|
||||||
|
>
|
||||||
|
<span>{{ lastApplyResult.message }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed } from "vue";
|
||||||
|
import { useEquipments } from "@/stores/equipments";
|
||||||
|
import {
|
||||||
|
CaptureConfig,
|
||||||
|
LogicAnalyzerClient,
|
||||||
|
GlobalCaptureMode,
|
||||||
|
SignalOperator,
|
||||||
|
SignalValue,
|
||||||
|
type SignalTriggerConfig,
|
||||||
|
} from "@/APIClient";
|
||||||
|
import { AuthManager } from "@/utils/AuthManager";
|
||||||
|
|
||||||
|
// 使用设备配置
|
||||||
|
const equipments = useEquipments();
|
||||||
|
|
||||||
|
// 当前全局模式
|
||||||
|
const currentGlobalMode = ref<GlobalCaptureMode>(GlobalCaptureMode.AND);
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
const isApplying = ref(false);
|
||||||
|
const lastApplyResult = ref<{ success: boolean; message: string } | null>(null);
|
||||||
|
|
||||||
|
// 全局模式选项
|
||||||
|
const globalModes = [
|
||||||
|
{
|
||||||
|
value: GlobalCaptureMode.AND,
|
||||||
|
label: "AND",
|
||||||
|
description: "所有条件都满足时触发",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: GlobalCaptureMode.OR,
|
||||||
|
label: "OR",
|
||||||
|
description: "任一条件满足时触发",
|
||||||
|
},
|
||||||
|
{ value: GlobalCaptureMode.NAND, label: "NAND", description: "AND的非" },
|
||||||
|
{ value: GlobalCaptureMode.NOR, label: "NOR", description: "OR的非" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 操作符选项
|
||||||
|
const operators = [
|
||||||
|
{ value: SignalOperator.Equal, label: "=" },
|
||||||
|
{ value: SignalOperator.NotEqual, label: "≠" },
|
||||||
|
{ value: SignalOperator.LessThan, label: "<" },
|
||||||
|
{ value: SignalOperator.LessThanOrEqual, label: "≤" },
|
||||||
|
{ value: SignalOperator.GreaterThan, label: ">" },
|
||||||
|
{ value: SignalOperator.GreaterThanOrEqual, label: "≥" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 信号值选项
|
||||||
|
const signalValues = [
|
||||||
|
{ value: SignalValue.Logic0, label: "0" },
|
||||||
|
{ value: SignalValue.Logic1, label: "1" },
|
||||||
|
{ value: SignalValue.NotCare, label: "X" },
|
||||||
|
{ value: SignalValue.Rise, label: "↑" },
|
||||||
|
{ value: SignalValue.Fall, label: "↓" },
|
||||||
|
{ value: SignalValue.RiseOrFall, label: "↕" },
|
||||||
|
{ value: SignalValue.NoChange, label: "—" },
|
||||||
|
{ value: SignalValue.SomeNumber, label: "#" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 8个信号通道的配置
|
||||||
|
const signalConfigs = reactive(
|
||||||
|
Array.from({ length: 8 }, (_, index) => ({
|
||||||
|
signalIndex: index,
|
||||||
|
operator: SignalOperator.Equal,
|
||||||
|
value: SignalValue.Logic1,
|
||||||
|
enabled: false,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 设置全局触发模式
|
||||||
|
const setGlobalMode = async (mode: GlobalCaptureMode) => {
|
||||||
|
try {
|
||||||
|
const client = AuthManager.createAuthenticatedLogicAnalyzerClient();
|
||||||
|
const success = await client.setGlobalTrigMode(mode);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
currentGlobalMode.value = mode;
|
||||||
|
lastApplyResult.value = {
|
||||||
|
success: true,
|
||||||
|
message: `全局触发模式已设置为 ${globalModes.find((m) => m.value === mode)?.label}`,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new Error("设置失败");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("设置全局触发模式失败:", error);
|
||||||
|
lastApplyResult.value = {
|
||||||
|
success: false,
|
||||||
|
message: "设置全局触发模式失败",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 应用完整配置
|
||||||
|
const applyConfiguration = async () => {
|
||||||
|
isApplying.value = true;
|
||||||
|
lastApplyResult.value = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = AuthManager.createAuthenticatedLogicAnalyzerClient();
|
||||||
|
|
||||||
|
// 准备配置数据
|
||||||
|
const enabledSignals = signalConfigs.filter((signal) => signal.enabled);
|
||||||
|
const config = new CaptureConfig({
|
||||||
|
globalMode: currentGlobalMode.value,
|
||||||
|
signalConfigs: enabledSignals.map(
|
||||||
|
(signal) =>
|
||||||
|
({
|
||||||
|
signalIndex: signal.signalIndex,
|
||||||
|
operator: signal.operator,
|
||||||
|
value: signal.value,
|
||||||
|
}) as SignalTriggerConfig,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 发送配置
|
||||||
|
const success = await client.configureCapture(config);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
lastApplyResult.value = {
|
||||||
|
success: true,
|
||||||
|
message: `配置已成功应用,启用了 ${enabledSignals.length} 个通道`,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new Error("应用配置失败");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("应用配置失败:", error);
|
||||||
|
lastApplyResult.value = {
|
||||||
|
success: false,
|
||||||
|
message: "应用配置失败,请检查设备连接",
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
isApplying.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置配置
|
||||||
|
const resetConfiguration = () => {
|
||||||
|
currentGlobalMode.value = GlobalCaptureMode.AND;
|
||||||
|
signalConfigs.forEach((signal) => {
|
||||||
|
signal.operator = SignalOperator.Equal;
|
||||||
|
signal.value = SignalValue.Logic1;
|
||||||
|
signal.enabled = false;
|
||||||
|
});
|
||||||
|
lastApplyResult.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清除状态消息
|
||||||
|
setTimeout(() => {
|
||||||
|
if (lastApplyResult.value) {
|
||||||
|
lastApplyResult.value = null;
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
</script>
|
|
@ -0,0 +1,78 @@
|
||||||
|
import LogicalWaveFormDisplay from "./LogicalWaveFormDisplay.vue";
|
||||||
|
|
||||||
|
type LogicDataType = {
|
||||||
|
x: number[];
|
||||||
|
y: number[][]; // 8 channels of digital data (0 or 1)
|
||||||
|
xUnit: "s" | "ms" | "us" | "ns";
|
||||||
|
channelNames: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test data generator for 8-channel digital signals
|
||||||
|
function generateTestLogicData(): LogicDataType {
|
||||||
|
const sampleRate = 10000; // 10kHz sampling
|
||||||
|
const duration = 1;
|
||||||
|
const points = Math.floor(sampleRate * duration);
|
||||||
|
|
||||||
|
const x = Array.from({ length: points }, (_, i) => (i / sampleRate) * 1000); // time in ms
|
||||||
|
|
||||||
|
// Generate 8 channels with different digital patterns
|
||||||
|
const y = [
|
||||||
|
// Channel 0: Clock signal 100Hz
|
||||||
|
Array.from(
|
||||||
|
{ length: points },
|
||||||
|
(_, i) => Math.floor((100 * i) / sampleRate) % 2,
|
||||||
|
),
|
||||||
|
// Channel 1: Clock/2 signal 50Hz
|
||||||
|
Array.from(
|
||||||
|
{ length: points },
|
||||||
|
(_, i) => Math.floor((50 * i) / sampleRate) % 2,
|
||||||
|
),
|
||||||
|
// Channel 2: Clock/4 signal 25Hz
|
||||||
|
Array.from(
|
||||||
|
{ length: points },
|
||||||
|
(_, i) => Math.floor((25 * i) / sampleRate) % 2,
|
||||||
|
),
|
||||||
|
// Channel 3: Clock/8 signal 12.5Hz
|
||||||
|
Array.from(
|
||||||
|
{ length: points },
|
||||||
|
(_, i) => Math.floor((12.5 * i) / sampleRate) % 2,
|
||||||
|
),
|
||||||
|
// Channel 4: Data signal (pseudo-random pattern)
|
||||||
|
Array.from(
|
||||||
|
{ length: points },
|
||||||
|
(_, i) => Math.abs( Math.floor(Math.sin(i * 0.01) * 10) % 2 ),
|
||||||
|
),
|
||||||
|
// Channel 5: Enable signal (periodic pulse)
|
||||||
|
Array.from(
|
||||||
|
{ length: points },
|
||||||
|
(_, i) => (Math.floor(i / 50) % 10) < 3 ? 1 : 0,
|
||||||
|
),
|
||||||
|
// Channel 6: Reset signal (occasional pulse)
|
||||||
|
Array.from(
|
||||||
|
{ length: points },
|
||||||
|
(_, i) => (Math.floor(i / 200) % 20) === 0 ? 1 : 0,
|
||||||
|
),
|
||||||
|
// Channel 7: Status signal (slow changing)
|
||||||
|
Array.from(
|
||||||
|
{ length: points },
|
||||||
|
(_, i) => Math.floor(i / 1000) % 2,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
const channelNames = [
|
||||||
|
"CLK",
|
||||||
|
"CLK/2",
|
||||||
|
"CLK/4",
|
||||||
|
"CLK/8",
|
||||||
|
"PWM",
|
||||||
|
"ENABLE",
|
||||||
|
"RESET",
|
||||||
|
"STATUS"
|
||||||
|
];
|
||||||
|
|
||||||
|
return { x, y, xUnit: "ms", channelNames };
|
||||||
|
}
|
||||||
|
|
||||||
|
export { LogicalWaveFormDisplay, generateTestLogicData, type LogicDataType };
|
||||||
|
export { default as TriggerSettings } from './TriggerSettings.vue'
|
||||||
|
export { default as ChannelConfig } from './ChannelConfig.vue'
|
|
@ -31,7 +31,6 @@ import { CanvasRenderer } from "echarts/renderers";
|
||||||
import type { ComposeOption } from "echarts/core";
|
import type { ComposeOption } from "echarts/core";
|
||||||
import type { LineSeriesOption } from "echarts/charts";
|
import type { LineSeriesOption } from "echarts/charts";
|
||||||
import type {
|
import type {
|
||||||
TitleComponentOption,
|
|
||||||
TooltipComponentOption,
|
TooltipComponentOption,
|
||||||
LegendComponentOption,
|
LegendComponentOption,
|
||||||
ToolboxComponentOption,
|
ToolboxComponentOption,
|
||||||
|
@ -40,7 +39,6 @@ import type {
|
||||||
} from "echarts/components";
|
} from "echarts/components";
|
||||||
|
|
||||||
use([
|
use([
|
||||||
TitleComponent,
|
|
||||||
TooltipComponent,
|
TooltipComponent,
|
||||||
LegendComponent,
|
LegendComponent,
|
||||||
ToolboxComponent,
|
ToolboxComponent,
|
||||||
|
@ -51,7 +49,6 @@ use([
|
||||||
]);
|
]);
|
||||||
|
|
||||||
type EChartsOption = ComposeOption<
|
type EChartsOption = ComposeOption<
|
||||||
| TitleComponentOption
|
|
||||||
| TooltipComponentOption
|
| TooltipComponentOption
|
||||||
| LegendComponentOption
|
| LegendComponentOption
|
||||||
| ToolboxComponentOption
|
| ToolboxComponentOption
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
RemoteUpdateClient,
|
RemoteUpdateClient,
|
||||||
TutorialClient,
|
TutorialClient,
|
||||||
UDPClient,
|
UDPClient,
|
||||||
|
LogicAnalyzerClient,
|
||||||
} from "@/APIClient";
|
} from "@/APIClient";
|
||||||
|
|
||||||
// 支持的客户端类型联合类型
|
// 支持的客户端类型联合类型
|
||||||
|
@ -22,6 +23,7 @@ type SupportedClient =
|
||||||
| PowerClient
|
| PowerClient
|
||||||
| RemoteUpdateClient
|
| RemoteUpdateClient
|
||||||
| TutorialClient
|
| TutorialClient
|
||||||
|
| LogicAnalyzerClient
|
||||||
| UDPClient;
|
| UDPClient;
|
||||||
|
|
||||||
export class AuthManager {
|
export class AuthManager {
|
||||||
|
@ -151,6 +153,10 @@ export class AuthManager {
|
||||||
return AuthManager.createAuthenticatedClient(UDPClient);
|
return AuthManager.createAuthenticatedClient(UDPClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static createAuthenticatedLogicAnalyzerClient(): LogicAnalyzerClient {
|
||||||
|
return AuthManager.createAuthenticatedClient(LogicAnalyzerClient);
|
||||||
|
}
|
||||||
|
|
||||||
// 登录函数
|
// 登录函数
|
||||||
public static async login(
|
public static async login(
|
||||||
username: string,
|
username: string,
|
||||||
|
@ -212,11 +218,7 @@ export class AuthManager {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 只有在token完全无效的情况下才清除token
|
// 只有在token完全无效的情况下才清除token
|
||||||
// 401错误表示token有效但权限不足,不应清除token
|
// 401错误表示token有效但权限不足,不应清除token
|
||||||
if (
|
if (error && typeof error === "object" && "status" in error) {
|
||||||
error &&
|
|
||||||
typeof error === "object" &&
|
|
||||||
"status" in error
|
|
||||||
) {
|
|
||||||
// 如果是403 (Forbidden) 或401 (Unauthorized),说明token有效但权限不足
|
// 如果是403 (Forbidden) 或401 (Unauthorized),说明token有效但权限不足
|
||||||
if (error.status === 401 || error.status === 403) {
|
if (error.status === 401 || error.status === 403) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -225,7 +227,7 @@ export class AuthManager {
|
||||||
AuthManager.clearToken();
|
AuthManager.clearToken();
|
||||||
} else {
|
} else {
|
||||||
// 网络错误等,不清除token
|
// 网络错误等,不清除token
|
||||||
console.error('管理员权限验证失败:', error);
|
console.error("管理员权限验证失败:", error);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
type="radio"
|
type="radio"
|
||||||
name="function-bar"
|
name="function-bar"
|
||||||
id="1"
|
id="1"
|
||||||
checked
|
:checked="checkID === 1"
|
||||||
@change="handleTabChange"
|
@change="handleTabChange"
|
||||||
/>
|
/>
|
||||||
<TerminalIcon class="icon" />
|
<TerminalIcon class="icon" />
|
||||||
|
@ -17,6 +17,7 @@
|
||||||
type="radio"
|
type="radio"
|
||||||
name="function-bar"
|
name="function-bar"
|
||||||
id="2"
|
id="2"
|
||||||
|
:checked="checkID === 2"
|
||||||
@change="handleTabChange"
|
@change="handleTabChange"
|
||||||
/>
|
/>
|
||||||
<VideoIcon class="icon" />
|
<VideoIcon class="icon" />
|
||||||
|
@ -27,6 +28,7 @@
|
||||||
type="radio"
|
type="radio"
|
||||||
name="function-bar"
|
name="function-bar"
|
||||||
id="3"
|
id="3"
|
||||||
|
:checked="checkID === 3"
|
||||||
@change="handleTabChange"
|
@change="handleTabChange"
|
||||||
/>
|
/>
|
||||||
<SquareActivityIcon class="icon" />
|
<SquareActivityIcon class="icon" />
|
||||||
|
@ -37,6 +39,7 @@
|
||||||
type="radio"
|
type="radio"
|
||||||
name="function-bar"
|
name="function-bar"
|
||||||
id="4"
|
id="4"
|
||||||
|
:checked="checkID === 4"
|
||||||
@change="handleTabChange"
|
@change="handleTabChange"
|
||||||
/>
|
/>
|
||||||
<Zap class="icon" />
|
<Zap class="icon" />
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<Zap class="w-5 h-5" />
|
<Zap class="w-5 h-5" />
|
||||||
逻辑信号分析
|
逻辑信号分析
|
||||||
</h2>
|
</h2>
|
||||||
<LogicalWaveFormDisplay />
|
<LogicalWaveFormDisplay :data="generateTestLogicData()" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -38,7 +38,12 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Zap, Settings, Layers } from "lucide-vue-next";
|
import { Zap, Settings, Layers } from "lucide-vue-next";
|
||||||
import { useEquipments } from "@/stores/equipments";
|
import { useEquipments } from "@/stores/equipments";
|
||||||
import LogicalWaveFormDisplay from "@/components/LogicAnalyzer/LogicalWaveFormDisplay.vue";
|
import {
|
||||||
|
LogicalWaveFormDisplay,
|
||||||
|
generateTestLogicData,
|
||||||
|
TriggerSettings,
|
||||||
|
ChannelConfig
|
||||||
|
} from "@/components/LogicAnalyzer";
|
||||||
|
|
||||||
// 使用全局设备配置
|
// 使用全局设备配置
|
||||||
const equipments = useEquipments();
|
const equipments = useEquipments();
|
||||||
|
|
Loading…
Reference in New Issue