Merge branch 'master' of ssh://git.swordlost.top:222/SikongJueluo/FPGA_WebLab into dpp
This commit is contained in:
commit
48ae3b5975
|
@ -39,9 +39,11 @@ run-web:
|
|||
npm run build
|
||||
npm run preview
|
||||
|
||||
dev: dev-server
|
||||
|
||||
# 测试服务器
|
||||
dev-server: _show-dir
|
||||
cd server && dotnet run
|
||||
cd server && dotnet run --watch
|
||||
|
||||
# 运行网页客户端
|
||||
dev-web:
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
"@tailwindcss/postcss": "^4.0.12",
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@types/node": "^22.13.4",
|
||||
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
|
@ -1647,19 +1646,6 @@
|
|||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-basic-ssl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.0.0.tgz",
|
||||
"integrity": "sha512-gc9Tjg8bUxBVSTzeWT3Njc0Cl3PakHFKdNfABnZWiUgbxqmHDEn7uECv3fHVylxoYgNzAcmU7ZrILz+BwSo3sA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz",
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
"@tailwindcss/postcss": "^4.0.12",
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
"@types/node": "^22.13.4",
|
||||
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
|
|
|
@ -192,26 +192,6 @@ public class JtagController : ControllerBase
|
|||
return "This is Jtag Controller";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行一个Jtag命令
|
||||
/// </summary>
|
||||
/// <param name="address"> 设备地址 </param>
|
||||
/// <param name="port"> 设备端口 </param>
|
||||
/// <param name="hexDevAddr"> 16进制设备目的地址(Jtag) </param>
|
||||
/// <param name="hexCmd"> 16进制命令 </param>
|
||||
[HttpPost("RunCommand")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async ValueTask<IResult> RunCommand(string address, int port, string hexDevAddr, string hexCmd)
|
||||
{
|
||||
var jtagCtrl = new JtagClient.Jtag(address, port);
|
||||
var ret = await jtagCtrl.WriteFIFO(Convert.ToUInt32(hexDevAddr, 16), Convert.ToUInt32(hexCmd, 16));
|
||||
|
||||
if (ret.IsSuccessful) { return TypedResults.Ok(ret.Value); }
|
||||
else { return TypedResults.InternalServerError(ret.Error); }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取Jtag ID Code
|
||||
/// </summary>
|
||||
|
@ -349,6 +329,11 @@ public class JtagController : ControllerBase
|
|||
return TypedResults.InternalServerError(retBuffer.Error);
|
||||
revBuffer = retBuffer.Value;
|
||||
|
||||
for (int i = 0; i < revBuffer.Length; i++)
|
||||
{
|
||||
revBuffer[i] = Common.Number.ReverseBits(revBuffer[i]);
|
||||
}
|
||||
|
||||
await memoryStream.WriteAsync(revBuffer, 0, bytesRead);
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
|
|
|
@ -432,199 +432,44 @@ public class Jtag
|
|||
return Convert.ToUInt32(Common.Number.BytesToUInt64(retPackOpts.Data).Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向指定的 JTAG 设备地址写入数据到 FIFO
|
||||
/// </summary>
|
||||
/// <param name="devAddr">目标设备地址</param>
|
||||
/// <param name="data">要写入的数据</param>
|
||||
/// <param name="delayMilliseconds">写入后的延迟时间(毫秒)</param>
|
||||
/// <returns>包含接收数据包的异步结果</returns>
|
||||
public async ValueTask<Result<RecvDataPackage>> WriteFIFO(UInt32 devAddr, UInt32 data, UInt32 delayMilliseconds = 0)
|
||||
async ValueTask<Result<bool>> WriteFIFO
|
||||
(UInt32 devAddr, UInt32 data, UInt32 result, UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 delayMilliseconds = 0)
|
||||
{
|
||||
var ret = false;
|
||||
var opts = new SendAddrPackOptions();
|
||||
|
||||
|
||||
opts.BurstType = BurstType.FixedBurst;
|
||||
opts.BurstLength = 0;
|
||||
opts.CommandID = 0;
|
||||
opts.Address = devAddr;
|
||||
|
||||
// Write Jtag State Register
|
||||
opts.IsWrite = true;
|
||||
ret = await UDPClientPool.SendAddrPackAsync(ep, new SendAddrPackage(opts));
|
||||
if (!ret) return new(new Exception("Send 1st address package failed!"));
|
||||
// Send Data Package
|
||||
ret = await UDPClientPool.SendDataPackAsync(ep,
|
||||
new SendDataPackage(Common.Number.NumberToBytes(data, 4).Value));
|
||||
if (!ret) return new(new Exception("Send data package failed!"));
|
||||
|
||||
// Check Msg Bus
|
||||
if (!MsgBus.IsRunning)
|
||||
return new(new Exception("Message bus not working!"));
|
||||
// Wait for Write Ack
|
||||
var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync(address, port);
|
||||
if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error);
|
||||
else if (!udpWriteAck.Value.IsSuccessful)
|
||||
return new(new Exception("Send address package failed"));
|
||||
|
||||
// Delay some time before read register
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(delayMilliseconds));
|
||||
|
||||
// Read Jtag State Register
|
||||
opts.IsWrite = false;
|
||||
opts.Address = JtagAddr.STATE;
|
||||
ret = await UDPClientPool.SendAddrPackAsync(ep, new SendAddrPackage(opts));
|
||||
if (!ret) return new(new Exception("Send 2rd Address Package Failed!"));
|
||||
// Wait for Read Data
|
||||
var udpDataResp = await MsgBus.UDPServer.WaitForDataAsync(address, port);
|
||||
if (!udpDataResp.IsSuccessful) return new(udpDataResp.Error);
|
||||
else if (!udpDataResp.Value.IsSuccessful)
|
||||
return new(new Exception("Send address package failed"));
|
||||
|
||||
return udpDataResp.Value;
|
||||
}
|
||||
|
||||
async ValueTask<Result<RecvDataPackage>> WriteFIFO(UInt32 devAddr, byte[] dataArray, UInt32 delayMilliseconds = 0)
|
||||
{
|
||||
var ret = false;
|
||||
var opts = new SendAddrPackOptions();
|
||||
|
||||
|
||||
opts.BurstType = BurstType.FixedBurst;
|
||||
opts.CommandID = 0;
|
||||
opts.Address = devAddr;
|
||||
|
||||
// Check Msg Bus
|
||||
if (!MsgBus.IsRunning)
|
||||
return new(new Exception("Message bus not working!"));
|
||||
|
||||
var writeTimes = dataArray.Length / (256 * (32 / 8)) + 1;
|
||||
for (var i = 0; i < writeTimes; i++)
|
||||
{
|
||||
// Sperate Data Array
|
||||
var isLastData = i == writeTimes - 1;
|
||||
var sendDataArray =
|
||||
isLastData ?
|
||||
dataArray[(i * (256 * (32 / 8)))..] :
|
||||
dataArray[(i * (256 * (32 / 8)))..((i + 1) * (256 * (32 / 8)))];
|
||||
|
||||
// Write Jtag State Register
|
||||
opts.IsWrite = true;
|
||||
opts.BurstLength = ((byte)(sendDataArray.Length / 4 - 1));
|
||||
ret = await UDPClientPool.SendAddrPackAsync(ep, new SendAddrPackage(opts));
|
||||
if (!ret) return new(new Exception("Send 1st address package failed!"));
|
||||
|
||||
// Send Data Package
|
||||
ret = await UDPClientPool.SendDataPackAsync(ep, new SendDataPackage(sendDataArray));
|
||||
if (!ret) return new(new Exception("Send data package failed!"));
|
||||
|
||||
// Wait for Write Ack
|
||||
var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync(address, port);
|
||||
if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error);
|
||||
else if (!udpWriteAck.Value.IsSuccessful)
|
||||
return new(new Exception("Send address package failed"));
|
||||
var ret = await UDPClientPool.WriteAddr(this.ep, devAddr, data, this.timeout);
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
if (!ret.Value) return new(new Exception("Write FIFO failed"));
|
||||
}
|
||||
|
||||
// Delay some time before read register
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(delayMilliseconds));
|
||||
|
||||
// Read Jtag State Register
|
||||
opts.IsWrite = false;
|
||||
opts.BurstLength = 0;
|
||||
opts.Address = JtagAddr.STATE;
|
||||
ret = await UDPClientPool.SendAddrPackAsync(ep, new SendAddrPackage(opts));
|
||||
if (!ret) return new(new Exception("Send 2rd Address Package Failed!"));
|
||||
// Wait for Read Data
|
||||
var udpDataResp = await MsgBus.UDPServer.WaitForDataAsync(address, port);
|
||||
if (!udpDataResp.IsSuccessful) return new(udpDataResp.Error);
|
||||
else if (!udpDataResp.Value.IsSuccessful)
|
||||
return new(new Exception("Send address package failed"));
|
||||
|
||||
return udpDataResp.Value;
|
||||
}
|
||||
|
||||
async ValueTask<Result<bool>> WriteFIFO
|
||||
(UInt32 devAddr, UInt32 data, UInt32 result, UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 delayMilliseconds = 0)
|
||||
{
|
||||
var ret = false;
|
||||
var retPack = await WriteFIFO(devAddr, data, delayMilliseconds);
|
||||
if (!retPack.IsSuccessful) return new(retPack.Error);
|
||||
|
||||
if (retPack.Value.Options.Data is null)
|
||||
return new(new Exception($"Data is Null, package: {retPack.Value.Options.ToString()}"));
|
||||
|
||||
var retPackLen = retPack.Value.Options.Data.Length;
|
||||
if (retPackLen != 4)
|
||||
return new(new Exception($"RecvDataPackage BodyData Length not Equal to 4: Total {retPackLen} bytes"));
|
||||
|
||||
if (Common.Number.BitsCheck(
|
||||
Common.Number.BytesToUInt64(retPack.Value.Options.Data).Value, result, resultMask))
|
||||
ret = true;
|
||||
|
||||
return ret;
|
||||
{
|
||||
var ret = await UDPClientPool.ReadAddrWithWait(this.ep, JtagAddr.STATE, result, resultMask, this.timeout);
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
return ret.Value;
|
||||
}
|
||||
}
|
||||
|
||||
async ValueTask<Result<bool>> WriteFIFO
|
||||
(UInt32 devAddr, byte[] data, UInt32 result, UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 delayMilliseconds = 0)
|
||||
{
|
||||
var ret = false;
|
||||
var retPack = await WriteFIFO(devAddr, data, delayMilliseconds);
|
||||
|
||||
if (retPack.Value.Options.Data is null)
|
||||
return new(new Exception($"Data is Null, package: {retPack.Value.Options.ToString()}"));
|
||||
|
||||
var retPackLen = retPack.Value.Options.Data.Length;
|
||||
if (retPackLen != 4)
|
||||
return new(new Exception($"RecvDataPackage BodyData Length not Equal to 4: Total {retPackLen} bytes"));
|
||||
|
||||
if (Common.Number.BitsCheck(
|
||||
Common.Number.BytesToUInt64(retPack.Value.Options.Data).Value, result, resultMask))
|
||||
ret = true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
async ValueTask<Result<bool>> WaitForWriteFIFO
|
||||
(UInt32 devAddr, byte[] data, UInt32 result,
|
||||
UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 timeout = 10_000, UInt32 cycle = 500)
|
||||
{
|
||||
{
|
||||
var wrRet = await WriteFIFO(devAddr, data, result, resultMask);
|
||||
|
||||
if (!wrRet.IsSuccessful) return new(wrRet.Error);
|
||||
if (wrRet.Value) return true;
|
||||
var ret = await UDPClientPool.WriteAddr(this.ep, devAddr, data, this.timeout);
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
if (!ret.Value) return new(new Exception("Write FIFO failed"));
|
||||
}
|
||||
|
||||
// Wait some time
|
||||
var ret = false;
|
||||
var startTime = DateTime.Now;
|
||||
var isTimeout = false;
|
||||
var timeleft = TimeSpan.FromMilliseconds(timeout);
|
||||
while (!isTimeout)
|
||||
// Delay some time before read register
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(delayMilliseconds));
|
||||
|
||||
{
|
||||
// Check whether timeout
|
||||
var elapsed = DateTime.Now - startTime;
|
||||
isTimeout = elapsed >= TimeSpan.FromMilliseconds(timeout);
|
||||
if (isTimeout) break;
|
||||
timeleft = TimeSpan.FromMilliseconds(timeout) - elapsed;
|
||||
|
||||
// Check FIFO
|
||||
var retPack = await ReadFIFO(JtagAddr.STATE);
|
||||
if (Common.Number.BitsCheck(retPack.Value, result, resultMask))
|
||||
{
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Wait
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(cycle));
|
||||
var ret = await UDPClientPool.ReadAddrWithWait(this.ep, JtagAddr.STATE, result, resultMask, this.timeout);
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
return ret.Value;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 清除所有 JTAG 寄存器
|
||||
/// </summary>
|
||||
|
@ -718,17 +563,16 @@ public class Jtag
|
|||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
else if (!ret.Value) return new(new Exception("Write CMD_JTAG_LOAD_DR_CAREI Failed"));
|
||||
}
|
||||
|
||||
{
|
||||
var ret = await WaitForWriteFIFO(
|
||||
var ret = await WriteFIFO(
|
||||
JtagAddr.WRITE_DATA,
|
||||
bytesArray, 0x01_00_00_00,
|
||||
JtagState.CMD_EXEC_FINISH,
|
||||
timeout, cycle);
|
||||
JtagState.CMD_EXEC_FINISH);
|
||||
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
else if (!ret.Value) return new(new Exception("Write Data Failed"));
|
||||
return ret.Value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async ValueTask<Result<UInt32>> LoadDRCareOutput(UInt32 UInt32Num)
|
||||
|
@ -753,7 +597,7 @@ public class Jtag
|
|||
var ret = await WriteFIFO(
|
||||
JtagAddr.WRITE_CMD,
|
||||
Common.Number.MultiBitsToNumber(JtagCmd.CMD_JTAG_LOAD_DR_CAREO, JtagCmd.LEN_CMD_JTAG, 32 * UInt32Num, 28).Value,
|
||||
0x01_00_00_00, JtagState.CMD_EXEC_FINISH);
|
||||
JtagState.CMD_EXEC_FINISH, JtagState.CMD_EXEC_FINISH);
|
||||
|
||||
if (ret.Value)
|
||||
{
|
||||
|
@ -955,11 +799,10 @@ public class Jtag
|
|||
|
||||
ret = await ExecRDCmd(JtagCmd.JTAG_DR_SAMPLE);
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_JRST Failed"));
|
||||
else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_SAMPLE Failed"));
|
||||
|
||||
var retData = await LoadDRCareOutputArray(((uint)(portNum % 32 == 0 ? portNum / 32 : portNum / 32 + 1)));
|
||||
if (!retData.IsSuccessful)
|
||||
return new(new Exception("Read Status Reg Failed"));
|
||||
if (!retData.IsSuccessful) return new(retData.Error);
|
||||
|
||||
ret = await CloseTest();
|
||||
if (!ret.IsSuccessful) return new(ret.Error);
|
||||
|
|
11
src/App.vue
11
src/App.vue
|
@ -1,7 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import Navbar from "./components/Navbar.vue";
|
||||
import Dialog from "./components/Dialog.vue";
|
||||
import { ref, provide, onMounted } from "vue";
|
||||
import { ref, provide, computed, onMounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// 主题切换状态管理
|
||||
const isDarkMode = ref(
|
||||
|
@ -42,6 +45,10 @@ provide("theme", {
|
|||
isDarkMode,
|
||||
toggleTheme,
|
||||
});
|
||||
|
||||
const currentRoutePath = computed(() => {
|
||||
return router.currentRoute.value.path;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -54,7 +61,7 @@ provide("theme", {
|
|||
<main>
|
||||
<RouterView />
|
||||
</main>
|
||||
<footer class="footer footer-center p-4 bg-base-300 text-base-content">
|
||||
<footer v-if="currentRoutePath != '/project'" class="footer footer-center p-4 bg-base-300 text-base-content">
|
||||
<div>
|
||||
<p>Copyright © 2023 - All right reserved by OurEDA</p>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg t="1747137150839" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5910" width="200" height="200"><path d="M224 831.936V192.096L223.808 192H576v159.936c0 35.328 28.736 64.064 64.064 64.064h159.712c0.032 0.512 0.224 1.184 0.224 1.664L800.256 832 224 831.936zM757.664 352L640 351.936V224.128L757.664 352z m76.064-11.872l-163.872-178.08C651.712 142.336 619.264 128 592.672 128H223.808A64.032 64.032 0 0 0 160 192.096v639.84A64 64 0 0 0 223.744 896h576.512A64 64 0 0 0 864 831.872V417.664c0-25.856-12.736-58.464-30.272-77.536z" fill="#3E3A39" p-id="5911"></path><path d="M640 512h-256a32 32 0 0 0 0 64h256a32 32 0 0 0 0-64M640 672h-256a32 32 0 0 0 0 64h256a32 32 0 0 0 0-64" fill="#3E3A39" p-id="5912"></path></svg>
|
After Width: | Height: | Size: 759 B |
|
@ -0,0 +1 @@
|
|||
<svg t="1747136937808" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4865" width="200" height="200"><path d="M894.481158 505.727133c0 49.589418-9.711176 97.705276-28.867468 143.007041-18.501376 43.74634-44.98454 83.031065-78.712713 116.759237-33.728172 33.728172-73.012897 60.211337-116.759237 78.712713-45.311998 19.156292-93.417623 28.877701-143.007041 28.877701s-97.695043-9.721409-142.996808-28.877701c-43.756573-18.501376-83.031065-44.98454-116.76947-78.712713-33.728172-33.728172-60.211337-73.012897-78.712713-116.759237-19.156292-45.301765-28.867468-93.417623-28.867468-143.007041 0-49.579185 9.711176-97.695043 28.867468-142.996808 18.501376-43.74634 44.98454-83.031065 78.712713-116.759237 33.738405-33.728172 73.012897-60.211337 116.76947-78.712713 45.301765-19.166525 93.40739-28.877701 142.996808-28.877701 52.925397 0 104.008842 11.010775 151.827941 32.745798 46.192042 20.977777 86.909395 50.79692 121.016191 88.608084 4.389984 4.860704 8.646937 9.854439 12.781094 14.97097l0-136.263453c0-11.307533 9.168824-20.466124 20.466124-20.466124 11.307533 0 20.466124 9.15859 20.466124 20.466124l0 183.64253c0 5.433756-2.148943 10.632151-5.986341 14.46955-3.847631 3.837398-9.046027 5.996574-14.479783 5.996574l-183.64253-0.020466c-11.307533 0-20.466124-9.168824-20.466124-20.466124 0-11.307533 9.168824-20.466124 20.466124-20.466124l132.293025 0.020466c-3.960195-4.952802-8.063653-9.782807-12.289907-14.479783-30.320563-33.605376-66.514903-60.098773-107.549481-78.753645-42.467207-19.289322-87.850837-29.072129-134.902456-29.072129-87.195921 0-169.172981 33.9533-230.816946 95.597265-61.654198 61.654198-95.597265 143.621025-95.597265 230.816946s33.943067 169.172981 95.597265 230.816946c61.643965 61.654198 143.621025 95.607498 230.816946 95.607498s169.172981-33.9533 230.816946-95.607498c61.654198-61.643965 95.597265-143.621025 95.597265-230.816946 0-11.2973 9.168824-20.466124 20.466124-20.466124C885.322567 485.261009 894.481158 494.429833 894.481158 505.727133z" p-id="4866"></path></svg>
|
After Width: | Height: | Size: 2.0 KiB |
|
@ -3,35 +3,39 @@
|
|||
interface Props {
|
||||
title: string;
|
||||
isExpanded?: boolean;
|
||||
status?: 'default' | 'success' | 'error';
|
||||
status?: "default" | "success" | "error";
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
isExpanded: false,
|
||||
status: 'default'
|
||||
status: "default",
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:isExpanded', value: boolean): void
|
||||
(e: "update:isExpanded", value: boolean): void;
|
||||
}>();
|
||||
|
||||
// 切换展开/收起状态
|
||||
const toggleExpand = () => {
|
||||
emit('update:isExpanded', !props.isExpanded);
|
||||
emit("update:isExpanded", !props.isExpanded);
|
||||
};
|
||||
|
||||
// 动画处理函数
|
||||
const enter = (element: Element, done: () => void) => {
|
||||
if (element instanceof HTMLElement) {
|
||||
const height = element.scrollHeight;
|
||||
element.style.height = '0px';
|
||||
element.style.height = "0px";
|
||||
// 触发重绘
|
||||
element.offsetHeight;
|
||||
element.style.height = height + 'px';
|
||||
|
||||
element.addEventListener('transitionend', () => {
|
||||
done();
|
||||
}, { once: true });
|
||||
element.style.height = height + "px";
|
||||
|
||||
element.addEventListener(
|
||||
"transitionend",
|
||||
() => {
|
||||
done();
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
|
@ -39,21 +43,25 @@ const enter = (element: Element, done: () => void) => {
|
|||
|
||||
const afterEnter = (element: Element) => {
|
||||
if (element instanceof HTMLElement) {
|
||||
element.style.height = 'auto';
|
||||
element.style.height = "auto";
|
||||
}
|
||||
};
|
||||
|
||||
const leave = (element: Element, done: () => void) => {
|
||||
if (element instanceof HTMLElement) {
|
||||
const height = element.scrollHeight;
|
||||
element.style.height = height + 'px';
|
||||
element.style.height = height + "px";
|
||||
// 触发重绘
|
||||
element.offsetHeight;
|
||||
element.style.height = '0px';
|
||||
|
||||
element.addEventListener('transitionend', () => {
|
||||
done();
|
||||
}, { once: true });
|
||||
element.style.height = "0px";
|
||||
|
||||
element.addEventListener(
|
||||
"transitionend",
|
||||
() => {
|
||||
done();
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
|
@ -61,17 +69,12 @@ const leave = (element: Element, done: () => void) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="section" :class="[`status-${status}`]">
|
||||
<div class="section-header" @click="toggleExpand">
|
||||
<div class="section m-4 shadow-xl" :class="[`status-${status}`]">
|
||||
<div class="section-header bg-primary text-primary-content" @click="toggleExpand">
|
||||
<h2>{{ title }}</h2>
|
||||
<span class="expand-icon" :class="{ 'is-expanded': isExpanded }">▶</span>
|
||||
</div>
|
||||
<transition
|
||||
name="collapse"
|
||||
@enter="enter"
|
||||
@after-enter="afterEnter"
|
||||
@leave="leave"
|
||||
>
|
||||
<transition name="collapse" @enter="enter" @after-enter="afterEnter" @leave="leave">
|
||||
<div v-show="isExpanded" class="section-content">
|
||||
<div class="section-inner">
|
||||
<slot></slot>
|
||||
|
@ -88,7 +91,6 @@ const leave = (element: Element, done: () => void) => {
|
|||
border-radius: var(--radius-md, 0.375rem);
|
||||
overflow: hidden;
|
||||
background-color: hsl(var(--b1));
|
||||
box-shadow: var(--shadow-sm, 0 1px 2px 0 rgba(0, 0, 0, 0.05));
|
||||
transition: all var(--transition-normal, 0.3s);
|
||||
}
|
||||
|
||||
|
@ -119,26 +121,58 @@ const leave = (element: Element, done: () => void) => {
|
|||
}
|
||||
|
||||
@keyframes borderPulseSuccess {
|
||||
0% { border-color: hsl(var(--b3)); box-shadow: 0px 0px 0px transparent;}
|
||||
50% { border-color: hsl(var(--su)); box-shadow: 0px 0px 5px hsl(var(--su));}
|
||||
100% { border-color: hsl(var(--su)); box-shadow: 0px 0px 3px hsl(var(--su));}
|
||||
0% {
|
||||
border-color: hsl(var(--b3));
|
||||
box-shadow: 0px 0px 0px transparent;
|
||||
}
|
||||
|
||||
50% {
|
||||
border-color: hsl(var(--su));
|
||||
box-shadow: 0px 0px 5px hsl(var(--su));
|
||||
}
|
||||
|
||||
100% {
|
||||
border-color: hsl(var(--su));
|
||||
box-shadow: 0px 0px 3px hsl(var(--su));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes borderPulseError {
|
||||
0% { border-color: hsl(var(--b3)); box-shadow: 0px 0px 0px transparent;}
|
||||
50% { border-color: hsl(var(--er)); box-shadow: 0px 0px 5px hsl(var(--er));}
|
||||
100% { border-color: hsl(var(--er)); box-shadow: 0px 0px 3px hsl(var(--er));}
|
||||
0% {
|
||||
border-color: hsl(var(--b3));
|
||||
box-shadow: 0px 0px 0px transparent;
|
||||
}
|
||||
|
||||
50% {
|
||||
border-color: hsl(var(--er));
|
||||
box-shadow: 0px 0px 5px hsl(var(--er));
|
||||
}
|
||||
|
||||
100% {
|
||||
border-color: hsl(var(--er));
|
||||
box-shadow: 0px 0px 3px hsl(var(--er));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes borderPulseInfo {
|
||||
0% { border-color: hsl(var(--b3)); box-shadow: 0px 0px 0px transparent;}
|
||||
50% { border-color: hsl(var(--in)); box-shadow: 0px 0px 5px hsl(var(--in));}
|
||||
100% { border-color: hsl(var(--in)); box-shadow: 0px 0px 3px hsl(var(--in));}
|
||||
0% {
|
||||
border-color: hsl(var(--b3));
|
||||
box-shadow: 0px 0px 0px transparent;
|
||||
}
|
||||
|
||||
50% {
|
||||
border-color: hsl(var(--in));
|
||||
box-shadow: 0px 0px 5px hsl(var(--in));
|
||||
}
|
||||
|
||||
100% {
|
||||
border-color: hsl(var(--in));
|
||||
box-shadow: 0px 0px 3px hsl(var(--in));
|
||||
}
|
||||
}
|
||||
|
||||
.section-header {
|
||||
padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 0.75rem);
|
||||
background-color: hsl(var(--b1));
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
@ -148,10 +182,6 @@ const leave = (element: Element, done: () => void) => {
|
|||
transition: all var(--transition-normal, 0.3s);
|
||||
}
|
||||
|
||||
.section-header:hover {
|
||||
background-color: hsl(var(--b2));
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.1em;
|
||||
|
@ -188,10 +218,12 @@ const leave = (element: Element, done: () => void) => {
|
|||
}
|
||||
|
||||
.content-wrapper {
|
||||
overflow: visible; /* 允许内容溢出 */
|
||||
overflow: visible;
|
||||
/* 允许内容溢出 */
|
||||
}
|
||||
|
||||
.card-body {
|
||||
overflow: visible; /* 允许内容溢出 */
|
||||
overflow: visible;
|
||||
/* 允许内容溢出 */
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -49,17 +49,6 @@
|
|||
测试功能
|
||||
</router-link>
|
||||
</li>
|
||||
<li class="my-1 hover:translate-x-1 transition-all duration-300">
|
||||
<router-link to="/test/jtag" class="text-base font-medium">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 opacity-70" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2">
|
||||
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
|
||||
<line x1="8" y1="21" x2="16" y2="21"></line>
|
||||
<line x1="12" y1="17" x2="12" y2="21"></line>
|
||||
</svg>
|
||||
JTAG测试
|
||||
</router-link>
|
||||
</li>
|
||||
<li class="my-1 hover:translate-x-1 transition-all duration-300">
|
||||
<a href="http://localhost:5000/swagger" target="_self" rel="noopener noreferrer"
|
||||
class="text-base font-medium">
|
||||
|
|
|
@ -4,51 +4,50 @@
|
|||
<div v-else>
|
||||
<div class="mb-4 pb-4 border-b border-base-300">
|
||||
<h4 class="font-semibold text-lg mb-1">{{ componentData?.type }}</h4>
|
||||
<p class="text-xs text-base-content opacity-70">ID: {{ componentData?.id }}</p>
|
||||
<p class="text-xs text-base-content opacity-70">类型: {{ componentData?.type }}</p>
|
||||
<p class="text-xs text-base-content opacity-70">
|
||||
ID: {{ componentData?.id }}
|
||||
</p>
|
||||
<p class="text-xs text-base-content opacity-70">
|
||||
类型: {{ componentData?.type }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 通用属性部分 -->
|
||||
<CollapsibleSection
|
||||
title="通用属性"
|
||||
v-model:isExpanded="generalPropsExpanded"
|
||||
status="default"
|
||||
>
|
||||
<CollapsibleSection title="通用属性" v-model:isExpanded="generalPropsExpanded" status="default">
|
||||
<div class="space-y-4">
|
||||
<div v-for="prop in getGeneralProps()" :key="prop.name" class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">{{ prop.label || prop.name }}</span>
|
||||
</label>
|
||||
<!-- 根据 prop 类型选择输入控件 -->
|
||||
<input
|
||||
v-if="prop.type === 'number'"
|
||||
type="number"
|
||||
:placeholder="prop.label || prop.name"
|
||||
class="input input-bordered input-sm w-full"
|
||||
:value="getPropValue(componentData, prop.name)"
|
||||
:disabled="prop.isReadOnly"
|
||||
:min="prop.min"
|
||||
:max="prop.max"
|
||||
:step="prop.step"
|
||||
@input="updateDirectProp(componentData.id, prop.name, parseFloat(($event.target as HTMLInputElement).value) || prop.default)"
|
||||
/>
|
||||
<input
|
||||
v-else-if="prop.type === 'string'"
|
||||
type="text"
|
||||
:placeholder="prop.label || prop.name"
|
||||
class="input input-bordered input-sm w-full"
|
||||
:value="getPropValue(componentData, prop.name)"
|
||||
:disabled="prop.isReadOnly"
|
||||
@input="updateDirectProp(componentData.id, prop.name, ($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
<input v-if="prop.type === 'number'" type="number" :placeholder="prop.label || prop.name"
|
||||
class="input input-bordered input-sm w-full" :value="getPropValue(componentData, prop.name)"
|
||||
:disabled="prop.isReadOnly" :min="prop.min" :max="prop.max" :step="prop.step" @input="
|
||||
updateDirectProp(
|
||||
componentData.id,
|
||||
prop.name,
|
||||
parseFloat(($event.target as HTMLInputElement).value) ||
|
||||
prop.default,
|
||||
)
|
||||
" />
|
||||
<input v-else-if="prop.type === 'string'" type="text" :placeholder="prop.label || prop.name"
|
||||
class="input input-bordered input-sm w-full" :value="getPropValue(componentData, prop.name)"
|
||||
:disabled="prop.isReadOnly" @input="
|
||||
updateDirectProp(
|
||||
componentData.id,
|
||||
prop.name,
|
||||
($event.target as HTMLInputElement).value,
|
||||
)
|
||||
" />
|
||||
<div v-else-if="prop.type === 'boolean'" class="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-sm mr-2"
|
||||
:checked="getPropValue(componentData, prop.name)"
|
||||
:disabled="prop.isReadOnly"
|
||||
@change="updateDirectProp(componentData.id, prop.name, ($event.target as HTMLInputElement).checked)"
|
||||
/>
|
||||
<input type="checkbox" class="checkbox checkbox-sm mr-2" :checked="getPropValue(componentData, prop.name)"
|
||||
:disabled="prop.isReadOnly" @change="
|
||||
updateDirectProp(
|
||||
componentData.id,
|
||||
prop.name,
|
||||
($event.target as HTMLInputElement).checked,
|
||||
)
|
||||
" />
|
||||
<span>{{ prop.label || prop.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -56,74 +55,69 @@
|
|||
</CollapsibleSection>
|
||||
|
||||
<!-- 组件特有属性部分 -->
|
||||
<CollapsibleSection
|
||||
title="组件特有属性"
|
||||
v-model:isExpanded="componentPropsExpanded"
|
||||
status="default"
|
||||
class="mt-4"
|
||||
>
|
||||
<CollapsibleSection title="组件特有属性" v-model:isExpanded="componentPropsExpanded" status="default" class="mt-4">
|
||||
<div v-if="componentConfig && componentConfig.props" class="space-y-4">
|
||||
<div v-for="prop in getComponentProps()" :key="prop.name" class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">{{ prop.label || prop.name }}</span>
|
||||
</label>
|
||||
<!-- 根据 prop 类型选择输入控件 -->
|
||||
<input
|
||||
v-if="prop.type === 'string'"
|
||||
type="text"
|
||||
:placeholder="prop.label || prop.name"
|
||||
class="input input-bordered input-sm w-full"
|
||||
:value="componentData?.attrs?.[prop.name]"
|
||||
:disabled="prop.isReadOnly"
|
||||
@input="updateProp(componentData.id, prop.name, ($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
<input
|
||||
v-else-if="prop.type === 'number'"
|
||||
type="number"
|
||||
:placeholder="prop.label || prop.name"
|
||||
class="input input-bordered input-sm w-full"
|
||||
:value="componentData?.attrs?.[prop.name]"
|
||||
:disabled="prop.isReadOnly"
|
||||
:min="prop.min"
|
||||
:max="prop.max"
|
||||
:step="prop.step"
|
||||
@input="updateProp(componentData.id, prop.name, parseFloat(($event.target as HTMLInputElement).value) || prop.default)"
|
||||
/>
|
||||
<input v-if="prop.type === 'string'" type="text" :placeholder="prop.label || prop.name"
|
||||
class="input input-bordered input-sm w-full" :value="componentData?.attrs?.[prop.name]"
|
||||
:disabled="prop.isReadOnly" @input="
|
||||
updateProp(
|
||||
componentData.id,
|
||||
prop.name,
|
||||
($event.target as HTMLInputElement).value,
|
||||
)
|
||||
" />
|
||||
<input v-else-if="prop.type === 'number'" type="number" :placeholder="prop.label || prop.name"
|
||||
class="input input-bordered input-sm w-full" :value="componentData?.attrs?.[prop.name]"
|
||||
:disabled="prop.isReadOnly" :min="prop.min" :max="prop.max" :step="prop.step" @input="
|
||||
updateProp(
|
||||
componentData.id,
|
||||
prop.name,
|
||||
parseFloat(($event.target as HTMLInputElement).value) ||
|
||||
prop.default,
|
||||
)
|
||||
" />
|
||||
<div v-else-if="prop.type === 'boolean'" class="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-sm mr-2"
|
||||
:checked="componentData?.attrs?.[prop.name]"
|
||||
:disabled="prop.isReadOnly"
|
||||
@change="updateProp(componentData.id, prop.name, ($event.target as HTMLInputElement).checked)"
|
||||
/>
|
||||
<input type="checkbox" class="checkbox checkbox-sm mr-2" :checked="componentData?.attrs?.[prop.name]"
|
||||
:disabled="prop.isReadOnly" @change="
|
||||
updateProp(
|
||||
componentData.id,
|
||||
prop.name,
|
||||
($event.target as HTMLInputElement).checked,
|
||||
)
|
||||
" />
|
||||
<span>{{ prop.label || prop.name }}</span>
|
||||
</div>
|
||||
<select
|
||||
v-else-if="prop.type === 'select' && prop.options"
|
||||
class="select select-bordered select-sm w-full"
|
||||
:value="componentData?.attrs?.[prop.name]"
|
||||
:disabled="prop.isReadOnly"
|
||||
@change="(event) => {
|
||||
const selectElement = event.target as HTMLSelectElement;
|
||||
const value = selectElement.value;
|
||||
if (componentData) {
|
||||
updateProp(componentData.id, prop.name, value);
|
||||
</div>
|
||||
<select v-else-if="prop.type === 'select' && prop.options" class="select select-bordered select-sm w-full"
|
||||
:value="componentData?.attrs?.[prop.name]" :disabled="prop.isReadOnly" @change="
|
||||
(event) => {
|
||||
const selectElement = event.target as HTMLSelectElement;
|
||||
const value = selectElement.value;
|
||||
if (componentData) {
|
||||
updateProp(componentData.id, prop.name, value);
|
||||
}
|
||||
}
|
||||
}"
|
||||
>
|
||||
">
|
||||
<option v-for="option in prop.options" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<p v-else class="text-xs text-warning">不支持的属性类型: {{ prop.type }}</p>
|
||||
<p v-else class="text-xs text-warning">
|
||||
不支持的属性类型: {{ prop.type }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="componentData && !componentConfig" class="text-base-content opacity-70 text-sm">
|
||||
正在加载组件配置...
|
||||
</div>
|
||||
<div v-else-if="componentData && componentConfig && getComponentProps().length === 0" class="text-base-content opacity-70 text-sm">
|
||||
<div v-else-if="
|
||||
componentData && componentConfig && getComponentProps().length === 0
|
||||
" class="text-base-content opacity-70 text-sm">
|
||||
此组件没有特有属性可配置。
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
|
@ -132,13 +126,13 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import CollapsibleSection from './CollapsibleSection.vue';
|
||||
import { type DiagramPart } from '@/components/diagramManager';
|
||||
import {
|
||||
type PropertyConfig,
|
||||
getPropValue
|
||||
} from '@/components/equipments/componentConfig';
|
||||
import { ref } from "vue";
|
||||
import CollapsibleSection from "./CollapsibleSection.vue";
|
||||
import { type DiagramPart } from "@/components/diagramManager";
|
||||
import {
|
||||
type PropertyConfig,
|
||||
getPropValue,
|
||||
} from "@/components/equipments/componentConfig";
|
||||
|
||||
// 定义属性
|
||||
const props = defineProps<{
|
||||
|
@ -148,8 +142,13 @@ const props = defineProps<{
|
|||
|
||||
// 定义事件
|
||||
const emit = defineEmits<{
|
||||
(e: 'updateProp', componentId: string, propName: string, value: any): void;
|
||||
(e: 'updateDirectProp', componentId: string, propName: string, value: any): void;
|
||||
(e: "updateProp", componentId: string, propName: string, value: any): void;
|
||||
(
|
||||
e: "updateDirectProp",
|
||||
componentId: string,
|
||||
propName: string,
|
||||
value: any,
|
||||
): void;
|
||||
}>();
|
||||
|
||||
// 控制折叠面板状态
|
||||
|
@ -158,23 +157,32 @@ const componentPropsExpanded = ref(true);
|
|||
|
||||
// 更新组件属性方法
|
||||
function updateProp(componentId: string, propName: string, value: any) {
|
||||
emit('updateProp', componentId, propName, value);
|
||||
emit("updateProp", componentId, propName, value);
|
||||
}
|
||||
|
||||
// 更新组件的直接属性
|
||||
function updateDirectProp(componentId: string, propName: string, value: any) {
|
||||
emit('updateDirectProp', componentId, propName, value);
|
||||
emit("updateDirectProp", componentId, propName, value);
|
||||
}
|
||||
|
||||
// 获取通用属性(直接属性)
|
||||
function getGeneralProps(): PropertyConfig[] {
|
||||
return props.componentConfig?.props.filter(p => p.isDirectProp && p.name !== 'pins') || [];
|
||||
return (
|
||||
props.componentConfig?.props.filter(
|
||||
(p) => p.isDirectProp && p.name !== "pins",
|
||||
) || []
|
||||
);
|
||||
}
|
||||
|
||||
// 获取组件特有属性(非直接属性)
|
||||
function getComponentProps(): PropertyConfig[] {
|
||||
return props.componentConfig?.props.filter(p => !p.isDirectProp && p.name !== 'pins') || [];
|
||||
return (
|
||||
props.componentConfig?.props.filter(
|
||||
(p) => !p.isDirectProp && p.name !== "pins",
|
||||
) || []
|
||||
);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -1,68 +1,64 @@
|
|||
<template> <div class="property-panel">
|
||||
<CollapsibleSection
|
||||
title="基本属性"
|
||||
v-model:isExpanded="propertySectionExpanded"
|
||||
status="default"
|
||||
>
|
||||
<PropertyEditor
|
||||
:componentData="componentData"
|
||||
:componentConfig="componentConfig"
|
||||
@updateProp="(componentId, propName, value) => $emit('updateProp', componentId, propName, value)"
|
||||
@updateDirectProp="(componentId, propName, value) => $emit('updateDirectProp', componentId, propName, value)"
|
||||
/>
|
||||
<template>
|
||||
<div class="property-panel">
|
||||
<CollapsibleSection title="基本属性" v-model:isExpanded="propertySectionExpanded" status="default">
|
||||
<PropertyEditor :componentData="componentData" :componentConfig="componentConfig" @updateProp="
|
||||
(componentId, propName, value) =>
|
||||
$emit('updateProp', componentId, propName, value)
|
||||
" @updateDirectProp="
|
||||
(componentId, propName, value) =>
|
||||
$emit('updateDirectProp', componentId, propName, value)
|
||||
" />
|
||||
</CollapsibleSection>
|
||||
|
||||
|
||||
<!-- 信号发生器(DDS)特殊属性编辑器 -->
|
||||
<div v-if="isDDSComponent">
|
||||
<DDSPropertyEditor
|
||||
v-model="ddsProperties"
|
||||
@update:modelValue="updateDDSProperties"
|
||||
/>
|
||||
<DDSPropertyEditor v-model="ddsProperties" @update:modelValue="updateDDSProperties" />
|
||||
</div>
|
||||
|
||||
<!-- 如果选中的组件有pins属性,则显示引脚配置区域 -->
|
||||
<CollapsibleSection
|
||||
v-if="hasPinsProperty"
|
||||
title="引脚配置"
|
||||
v-model:isExpanded="pinsSectionExpanded"
|
||||
status="default"
|
||||
>
|
||||
<CollapsibleSection v-if="hasPinsProperty" title="引脚配置" v-model:isExpanded="pinsSectionExpanded" status="default">
|
||||
<div class="space-y-4 p-2">
|
||||
<!-- 显示现有的pins -->
|
||||
<div v-for="(pin, index) in componentPins" :key="index" class="pin-item p-2 border rounded-md bg-base-200">
|
||||
<div class="font-medium mb-2">引脚 #{{ index + 1 }}</div>
|
||||
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text text-xs">ID</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
v-model="componentPins[index].pinId"
|
||||
class="input input-bordered input-sm w-full"
|
||||
placeholder="引脚ID"
|
||||
@change="updatePins"
|
||||
/>
|
||||
<input type="text" v-model="componentPins[index].pinId" class="input input-bordered input-sm w-full"
|
||||
placeholder="引脚ID" @change="updatePins" />
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text text-xs">约束条件</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
v-model="componentPins[index].constraint"
|
||||
class="input input-bordered input-sm w-full"
|
||||
placeholder="约束条件"
|
||||
@change="updatePins"
|
||||
/>
|
||||
<input type="text" v-model="componentPins[index].constraint" class="input input-bordered input-sm w-full"
|
||||
placeholder="约束条件" @change="updatePins" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
|
||||
<CollapsibleSection title="组件功能" v-model:isExpanded="componentCapsExpanded" status="default" class="mt-4">
|
||||
<div v-if="componentData && componentData.type">
|
||||
<component
|
||||
v-if="capabilityComponent"
|
||||
:is="capabilityComponent"
|
||||
v-bind="componentData.attrs"
|
||||
/>
|
||||
<div v-else class="text-gray-400">
|
||||
该组件没有提供特殊功能
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-gray-400">
|
||||
选择元件以查看其功能
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
|
||||
<!-- 未来可以在这里添加更多的分区 -->
|
||||
<!-- 例如:
|
||||
<CollapsibleSection
|
||||
|
@ -77,120 +73,258 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type DiagramPart } from '@/components/diagramManager';
|
||||
import { type PropertyConfig } from '@/components/equipments/componentConfig';
|
||||
import CollapsibleSection from './CollapsibleSection.vue';
|
||||
import PropertyEditor from './PropertyEditor.vue';
|
||||
import DDSPropertyEditor from './equipments/DDSPropertyEditor.vue';
|
||||
import { ref, computed, watch } from 'vue';
|
||||
// 导入所需的类型和组件
|
||||
import { type DiagramPart } from "@/components/diagramManager"; // 图表部件类型定义
|
||||
import { type PropertyConfig } from "@/components/equipments/componentConfig"; // 属性配置类型定义
|
||||
import CollapsibleSection from "./CollapsibleSection.vue"; // 可折叠区域组件
|
||||
import PropertyEditor from "./PropertyEditor.vue"; // 属性编辑器组件
|
||||
import DDSPropertyEditor from "./equipments/DDSPropertyEditor.vue"; // DDS专用属性编辑器组件
|
||||
import { ref, computed, watch, shallowRef, markRaw, h, createApp } from "vue"; // Vue核心API
|
||||
import { isNull, isUndefined } from "lodash";
|
||||
import type { JSX } from "vue/jsx-runtime";
|
||||
|
||||
// 定义Pin接口
|
||||
// 引脚接口定义
|
||||
interface Pin {
|
||||
pinId: string;
|
||||
constraint: string;
|
||||
x: number;
|
||||
y: number;
|
||||
pinId: string; // 引脚ID
|
||||
constraint: string; // 引脚约束条件
|
||||
x: number; // 引脚X坐标位置
|
||||
y: number; // 引脚Y坐标位置
|
||||
}
|
||||
|
||||
// 定义属性
|
||||
// 定义组件的输入属性
|
||||
const props = defineProps<{
|
||||
componentData: DiagramPart | null;
|
||||
componentConfig: { props: PropertyConfig[] } | null;
|
||||
componentData: DiagramPart | null; // 当前选中的组件数据
|
||||
componentConfig: { props: PropertyConfig[] } | null; // 组件的配置信息
|
||||
}>();
|
||||
|
||||
// 控制各个分区的展开状态
|
||||
const propertySectionExpanded = ref(true);
|
||||
const pinsSectionExpanded = ref(false);
|
||||
const wireSectionExpanded = ref(false);
|
||||
// 控制各个属性分区的展开状态
|
||||
const propertySectionExpanded = ref(true); // 基本属性区域默认展开
|
||||
const pinsSectionExpanded = ref(false); // 引脚配置区域默认折叠
|
||||
const componentCapsExpanded = ref(true); // 组件功能区域默认展开
|
||||
const wireSectionExpanded = ref(false); // 连线管理区域默认折叠
|
||||
|
||||
// DDS特殊属性
|
||||
// DDS组件特殊属性的本地状态
|
||||
const ddsProperties = ref({
|
||||
frequency: 1000,
|
||||
phase: 0,
|
||||
waveform: 'sine',
|
||||
customWaveformPoints: []
|
||||
frequency: 1000, // 频率,默认1000Hz
|
||||
phase: 0, // 相位,默认0度
|
||||
waveform: "sine", // 波形类型,默认正弦波
|
||||
customWaveformPoints: [], // 自定义波形点数据,默认空数组
|
||||
});
|
||||
|
||||
// 本地维护一个pins数组副本
|
||||
// 本地维护一个pins数组副本,用于编辑操作
|
||||
const componentPins = ref<Pin[]>([]);
|
||||
|
||||
// 监听组件变化,更新本地pins数据
|
||||
watch(() => props.componentData?.attrs?.pins, (newPins) => {
|
||||
if (newPins) {
|
||||
componentPins.value = JSON.parse(JSON.stringify(newPins));
|
||||
} else {
|
||||
componentPins.value = [];
|
||||
}
|
||||
}, { deep: true, immediate: true });
|
||||
// 监听组件pins属性变化,更新本地pins数据
|
||||
watch(
|
||||
() => props.componentData?.attrs?.pins,
|
||||
(newPins) => {
|
||||
if (newPins) {
|
||||
// 深拷贝以避免直接修改原始数据
|
||||
componentPins.value = JSON.parse(JSON.stringify(newPins));
|
||||
} else {
|
||||
componentPins.value = [];
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }, // 深度监听并立即执行
|
||||
);
|
||||
|
||||
// 监听DDS组件数据变化,更新特殊属性
|
||||
watch(() => props.componentData?.attrs, (newAttrs) => {
|
||||
if (newAttrs && isDDSComponent.value) {
|
||||
ddsProperties.value = {
|
||||
frequency: newAttrs.frequency || 1000,
|
||||
phase: newAttrs.phase || 0,
|
||||
waveform: newAttrs.waveform || 'sine',
|
||||
customWaveformPoints: newAttrs.customWaveformPoints || []
|
||||
};
|
||||
}
|
||||
}, { deep: true, immediate: true });
|
||||
watch(
|
||||
() => props.componentData?.attrs,
|
||||
(newAttrs) => {
|
||||
if (newAttrs && isDDSComponent.value) {
|
||||
// 从组件属性中提取DDS特有属性
|
||||
ddsProperties.value = {
|
||||
frequency: newAttrs.frequency || 1000,
|
||||
phase: newAttrs.phase || 0,
|
||||
waveform: newAttrs.waveform || "sine",
|
||||
customWaveformPoints: newAttrs.customWaveformPoints || [],
|
||||
};
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }, // 深度监听并立即执行
|
||||
);
|
||||
|
||||
// 计算属性:检查组件是否有pins属性
|
||||
const hasPinsProperty = computed(() => {
|
||||
if (!props.componentData || !props.componentData.attrs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查配置中是否有pins属性
|
||||
|
||||
// 方法1:检查配置中是否有pins属性
|
||||
if (props.componentConfig && props.componentConfig.props) {
|
||||
return props.componentConfig.props.some(prop => prop.name === 'pins' && prop.isArrayType);
|
||||
return props.componentConfig.props.some(
|
||||
(prop) => prop.name === "pins" && prop.isArrayType,
|
||||
);
|
||||
}
|
||||
|
||||
// 或直接检查attrs中是否有pins属性
|
||||
return 'pins' in props.componentData.attrs;
|
||||
|
||||
// 方法2:直接检查attrs中是否有pins属性
|
||||
return "pins" in props.componentData.attrs;
|
||||
});
|
||||
|
||||
// 计算属性:检查组件是否为DDS组件
|
||||
const isDDSComponent = computed(() => {
|
||||
return props.componentData?.type === 'DDS';
|
||||
return props.componentData?.type === "DDS";
|
||||
});
|
||||
|
||||
// 定义事件
|
||||
// 定义向父组件发送的事件
|
||||
const emit = defineEmits<{
|
||||
(e: 'updateProp', componentId: string, propName: string, value: any): void;
|
||||
(e: 'updateDirectProp', componentId: string, propName: string, value: any): void;
|
||||
// 更新嵌套属性(如数组或对象中的属性)
|
||||
(e: "updateProp", componentId: string, propName: string, value: any): void;
|
||||
// 更新直接属性
|
||||
(
|
||||
e: "updateDirectProp",
|
||||
componentId: string,
|
||||
propName: string,
|
||||
value: any,
|
||||
): void;
|
||||
}>();
|
||||
|
||||
// 更新pins属性
|
||||
// 更新pins属性的函数
|
||||
function updatePins() {
|
||||
if (props.componentData && props.componentData.id) {
|
||||
emit('updateProp', props.componentData.id, 'pins', componentPins.value);
|
||||
// 将编辑后的pins数据发送给父组件
|
||||
emit("updateProp", props.componentData.id, "pins", componentPins.value);
|
||||
}
|
||||
}
|
||||
|
||||
// 监听DDS组件数据变化,更新特殊属性
|
||||
watch(() => props.componentData?.attrs, (newAttrs) => {
|
||||
if (newAttrs && isDDSComponent.value) {
|
||||
ddsProperties.value = {
|
||||
frequency: newAttrs.frequency || 1000,
|
||||
phase: newAttrs.phase || 0,
|
||||
waveform: newAttrs.waveform || 'sine',
|
||||
customWaveformPoints: newAttrs.customWaveformPoints || []
|
||||
};
|
||||
}
|
||||
}, { deep: true, immediate: true });
|
||||
|
||||
// 更新DDS属性
|
||||
// 更新DDS特殊属性的函数
|
||||
function updateDDSProperties(newProperties: any) {
|
||||
// 更新本地状态
|
||||
ddsProperties.value = newProperties;
|
||||
if (props.componentData && props.componentData.id) {
|
||||
// 将各个属性单独更新,而不是作为一个整体
|
||||
emit('updateProp', props.componentData.id, 'frequency', newProperties.frequency);
|
||||
emit('updateProp', props.componentData.id, 'phase', newProperties.phase);
|
||||
emit('updateProp', props.componentData.id, 'waveform', newProperties.waveform);
|
||||
emit('updateProp', props.componentData.id, 'customWaveformPoints', newProperties.customWaveformPoints);
|
||||
// 将各个属性单独更新,而不是作为一个整体更新
|
||||
emit(
|
||||
"updateProp",
|
||||
props.componentData.id,
|
||||
"frequency",
|
||||
newProperties.frequency,
|
||||
);
|
||||
emit("updateProp", props.componentData.id, "phase", newProperties.phase);
|
||||
emit(
|
||||
"updateProp",
|
||||
props.componentData.id,
|
||||
"waveform",
|
||||
newProperties.waveform,
|
||||
);
|
||||
emit(
|
||||
"updateProp",
|
||||
props.componentData.id,
|
||||
"customWaveformPoints",
|
||||
newProperties.customWaveformPoints,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 存储当前选中组件的能力组件
|
||||
const capabilityComponent = shallowRef(null);
|
||||
|
||||
// 获取组件实例上暴露的方法
|
||||
async function getExposedCapabilities(componentType: string) {
|
||||
try {
|
||||
// 动态导入组件
|
||||
const module = await import(`./equipments/${componentType}.vue`);
|
||||
const Component = module.default;
|
||||
|
||||
// 创建一个临时div作为挂载点
|
||||
const tempDiv = document.createElement('div');
|
||||
|
||||
// 创建临时应用实例并挂载组件
|
||||
let exposedMethods: any = null;
|
||||
const app = createApp({
|
||||
render() {
|
||||
return h(Component, {
|
||||
ref: (el: any) => {
|
||||
if (el) {
|
||||
// 获取组件实例暴露的方法
|
||||
exposedMethods = el;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 挂载应用
|
||||
const vm = app.mount(tempDiv);
|
||||
|
||||
// 确保实例已创建并获取到暴露的方法
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
// 检查是否有getCapabilities方法
|
||||
if (exposedMethods && typeof exposedMethods.getCapabilities === 'function') {
|
||||
// 获取能力组件定义
|
||||
const CapabilityComponent = exposedMethods.getCapabilities();
|
||||
|
||||
// 卸载应用,清理DOM
|
||||
app.unmount();
|
||||
tempDiv.remove();
|
||||
|
||||
return CapabilityComponent;
|
||||
}
|
||||
|
||||
// 卸载应用,清理DOM
|
||||
app.unmount();
|
||||
tempDiv.remove();
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error(`获取${componentType}能力页面失败:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 监听选中组件变化,动态加载能力组件
|
||||
watch(
|
||||
() => props.componentData,
|
||||
async (newComponentData) => {
|
||||
if (newComponentData && newComponentData.type) {
|
||||
try {
|
||||
// 首先尝试从实例中获取暴露的方法
|
||||
const capsComponent = await getExposedCapabilities(newComponentData.type);
|
||||
|
||||
if (capsComponent) {
|
||||
capabilityComponent.value = markRaw(capsComponent);
|
||||
console.log(`已从实例加载${newComponentData.type}组件的能力页面`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果实例方法获取失败,回退到模块导出方法
|
||||
const module = await import(`./equipments/${newComponentData.type}.vue`);
|
||||
|
||||
if (
|
||||
(module.default && typeof module.default.getCapabilities === "function") ||
|
||||
typeof module.getCapabilities === "function"
|
||||
) {
|
||||
const getCapsFn =
|
||||
typeof module.getCapabilities === "function"
|
||||
? module.getCapabilities
|
||||
: module.default.getCapabilities;
|
||||
|
||||
const moduleCapComponent = getCapsFn();
|
||||
if (moduleCapComponent) {
|
||||
capabilityComponent.value = markRaw(moduleCapComponent);
|
||||
console.log(`已从模块加载${newComponentData.type}组件的能力页面`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
capabilityComponent.value = null;
|
||||
console.log(`组件${newComponentData.type}没有提供getCapabilities方法`);
|
||||
} catch (error) {
|
||||
console.error(`加载组件${newComponentData.type}能力页面失败:`, error);
|
||||
capabilityComponent.value = null;
|
||||
}
|
||||
} else {
|
||||
capabilityComponent.value = null;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 修改hasComponentCaps计算属性
|
||||
const hasComponentCaps = computed(() => {
|
||||
return capabilityComponent.value !== null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<!-- Input File -->
|
||||
<fieldset class="fieldset w-full">
|
||||
<legend class="fieldset-legend text-sm">选择或拖拽上传文件</legend>
|
||||
<input type="file" class="file-input w-full" @change="handleFileChange" />
|
||||
<input type="file" class="file-input w-full" :value="fileInput" @change="handleFileChange" />
|
||||
<label class="fieldset-label">文件最大容量: {{ maxMemory }}MB</label>
|
||||
</fieldset>
|
||||
|
||||
|
@ -20,17 +20,38 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { useEquipments } from "@/stores/equipments";
|
||||
import { useDialogStore } from "@/stores/dialog";
|
||||
import { isNull, isUndefined } from "lodash";
|
||||
|
||||
interface Props {
|
||||
uploadEvent?: Function;
|
||||
downloadEvent?: Function;
|
||||
maxMemory?: number;
|
||||
defaultFile?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
maxMemory: 4,
|
||||
});
|
||||
|
||||
const emits = defineEmits<{
|
||||
finishedUpload: [file: File];
|
||||
}>();
|
||||
|
||||
const dialog = useDialogStore();
|
||||
const eqps = useEquipments();
|
||||
|
||||
const buttonText = computed(() => {
|
||||
return isUndefined(props.downloadEvent) ? "上传" : "上传并下载";
|
||||
});
|
||||
|
||||
var bitstream: File | null = null;
|
||||
// var bitstream: File | null = null;
|
||||
const bitstream = defineModel<File | undefined>();
|
||||
const fileInput = computed(() => {
|
||||
return !isUndefined(bitstream.value) ? bitstream.value.name : "";
|
||||
});
|
||||
|
||||
function handleFileChange(event: Event): void {
|
||||
const target = event.target as HTMLInputElement;
|
||||
|
@ -40,15 +61,10 @@ function handleFileChange(event: Event): void {
|
|||
return;
|
||||
}
|
||||
|
||||
bitstream = file;
|
||||
bitstream.value = file;
|
||||
}
|
||||
|
||||
function checkFile(file: File | null): boolean {
|
||||
if (isNull(file)) {
|
||||
dialog.error(`未选择文件`);
|
||||
return false;
|
||||
}
|
||||
|
||||
function checkFile(file: File): boolean {
|
||||
const maxBytes = props.maxMemory! * 1024 * 1024; // 将最大容量从 MB 转换为字节
|
||||
if (file.size > maxBytes) {
|
||||
dialog.error(`文件大小超过最大限制: ${props.maxMemory}MB`);
|
||||
|
@ -58,18 +74,25 @@ function checkFile(file: File | null): boolean {
|
|||
}
|
||||
|
||||
async function handleClick(event: Event): Promise<void> {
|
||||
if (!checkFile(bitstream)) return;
|
||||
if (isNull(bitstream.value) || isUndefined(bitstream.value)) {
|
||||
dialog.error(`未选择文件`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkFile(bitstream.value)) return;
|
||||
if (isUndefined(props.uploadEvent)) {
|
||||
dialog.error("无法上传");
|
||||
return;
|
||||
}
|
||||
|
||||
// Upload
|
||||
// Upload - 修改这里,传递bitstream.value而不是bitstream
|
||||
try {
|
||||
const ret = await props.uploadEvent(event, bitstream);
|
||||
const ret = await props.uploadEvent(event, bitstream.value);
|
||||
if (isUndefined(props.downloadEvent)) {
|
||||
if (ret) dialog.info("上传成功");
|
||||
else dialog.error("上传失败");
|
||||
if (ret) {
|
||||
dialog.info("上传成功");
|
||||
emits("finishedUpload", bitstream.value);
|
||||
} else dialog.error("上传失败");
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -89,15 +112,6 @@ async function handleClick(event: Event): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
uploadEvent?: Function;
|
||||
downloadEvent?: Function;
|
||||
maxMemory?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
maxMemory: 4,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import type { JSX } from "vue/jsx-runtime";
|
||||
|
||||
// 定义 diagram.json 的类型结构
|
||||
export interface DiagramData {
|
||||
version: number;
|
||||
|
@ -15,6 +17,7 @@ export interface DiagramPart {
|
|||
x: number;
|
||||
y: number;
|
||||
attrs: Record<string, any>;
|
||||
capsPage?: JSX.Element;
|
||||
rotate: number;
|
||||
group: string;
|
||||
positionlock: boolean;
|
||||
|
|
|
@ -1,45 +1,65 @@
|
|||
<template> <div class="eth-component" :style="{ width: width + 'px', height: height + 'px' }">
|
||||
<img
|
||||
src="../equipments/svg/eth.svg"
|
||||
:width="width"
|
||||
:height="height"
|
||||
alt="以太网接口"
|
||||
class="svg-image"
|
||||
draggable="false"
|
||||
/>
|
||||
<template>
|
||||
<div class="eth-component" :style="{ width: width + 'px', height: height + 'px' }">
|
||||
<img src="../equipments/svg/eth.svg" :width="width" :height="height" alt="以太网接口" class="svg-image"
|
||||
draggable="false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { computed } from "vue";
|
||||
|
||||
interface Props {
|
||||
size?: number;
|
||||
devIPAddress?: string;
|
||||
devPort?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: 1
|
||||
});
|
||||
const props = withDefaults(defineProps<Props>(), getDefaultProps());
|
||||
|
||||
// 计算实际宽高
|
||||
const width = computed(() => 100 * props.size);
|
||||
const height = computed(() => 60 * props.size);
|
||||
|
||||
// 向外暴露方法
|
||||
defineExpose({
|
||||
getInfo: () => ({
|
||||
size: props.size,
|
||||
type: "ETH",
|
||||
}),
|
||||
// 主板没有引脚,但为了接口一致性,提供一个空的getPinPosition方法
|
||||
getPinPosition: () => null,
|
||||
});
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
// 添加一个静态方法来获取默认props
|
||||
export function getDefaultProps() {
|
||||
return {
|
||||
size: 1,
|
||||
devIPAddress: "127.0.0.1",
|
||||
devPort: "1234",
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.eth-component {
|
||||
display: block;
|
||||
user-select: none;
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* IE/Edge */
|
||||
-webkit-user-select: none;
|
||||
/* Safari */
|
||||
-moz-user-select: none;
|
||||
/* Firefox */
|
||||
-ms-user-select: none;
|
||||
/* IE/Edge */
|
||||
}
|
||||
|
||||
.svg-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
pointer-events: none; /* 禁止鼠标交互 */
|
||||
pointer-events: none;
|
||||
/* 禁止鼠标交互 */
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
|
|
|
@ -1,54 +1,190 @@
|
|||
<template>
|
||||
<div class="motherboard-container" :style="{ width: width + 'px', height: height + 'px', position: 'relative' }">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
:width="width"
|
||||
:height="height"
|
||||
:viewBox="`0 0 800 600`"
|
||||
class="motherboard-svg"
|
||||
>
|
||||
<image
|
||||
href="../equipments/svg/motherboard.svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
/>
|
||||
<div class="motherboard-container" :style="{
|
||||
width: width + 'px',
|
||||
height: height + 'px',
|
||||
position: 'relative',
|
||||
}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" :width="width" :height="height" :viewBox="`0 0 800 600`"
|
||||
class="motherboard-svg">
|
||||
<image href="../equipments/svg/motherboard.svg" width="100%" height="100%" preserveAspectRatio="xMidYMid meet" />
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
<script setup lang="tsx">
|
||||
import { JtagClient, type FileParameter } from "@/APIClient";
|
||||
import z from "zod";
|
||||
import { useEquipments } from "@/stores/equipments";
|
||||
import UploadCard from "@/components/UploadCard.vue";
|
||||
import { useDialogStore } from "@/stores/dialog";
|
||||
import { toNumber, toString } from "lodash";
|
||||
import { computed, ref, defineComponent, watch } from "vue";
|
||||
|
||||
// 主板特有属性
|
||||
interface MotherBoardProps {
|
||||
size?: number;
|
||||
jtagAddr?: string;
|
||||
jtagPort?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<MotherBoardProps>(), {
|
||||
size: 1
|
||||
});
|
||||
const props = withDefaults(defineProps<MotherBoardProps>(), getDefaultProps());
|
||||
|
||||
// 计算实际宽高
|
||||
const width = computed(() => 800 * props.size);
|
||||
const height = computed(() => 600 * props.size);
|
||||
const eqps = useEquipments();
|
||||
|
||||
watch([props.jtagAddr, props.jtagPort], () => {
|
||||
eqps.jtagIPAddr = props.jtagAddr;
|
||||
eqps.jtagPort = props.jtagPort;
|
||||
});
|
||||
|
||||
// 向外暴露方法
|
||||
defineExpose({
|
||||
getInfo: () => ({
|
||||
size: props.size,
|
||||
type: 'motherboard'
|
||||
type: "motherboard",
|
||||
}),
|
||||
// 主板没有引脚,但为了接口一致性,提供一个空的getPinPosition方法
|
||||
getPinPosition: () => null
|
||||
getPinPosition: () => null,
|
||||
getCapabilities: () => {
|
||||
// 返回组件定义而不是直接返回JSX
|
||||
return defineComponent({
|
||||
name: "MotherBoardCapabilities",
|
||||
props: {
|
||||
jtagAddr: {
|
||||
type: String,
|
||||
default: props.jtagAddr,
|
||||
},
|
||||
jtagPort: {
|
||||
type: String,
|
||||
default: props.jtagPort,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const jtagController = new JtagClient();
|
||||
const dialog = useDialogStore();
|
||||
|
||||
// 使用传入的属性或默认值
|
||||
const jtagIDCode = ref("");
|
||||
const boardAddress = computed(() => props.jtagAddr);
|
||||
const boardPort = computed(() => toNumber(props.jtagPort));
|
||||
|
||||
async function uploadBitstream(event: Event, bitstream: File) {
|
||||
if (!boardAddress.value || !boardPort.value) {
|
||||
dialog.error("开发板地址或端口空缺");
|
||||
return;
|
||||
}
|
||||
|
||||
const fileParam = {
|
||||
data: bitstream,
|
||||
fileName: bitstream.name,
|
||||
};
|
||||
|
||||
try {
|
||||
const resp = await jtagController.uploadBitstream(
|
||||
boardAddress.value,
|
||||
fileParam,
|
||||
);
|
||||
return resp;
|
||||
} catch (e) {
|
||||
dialog.error("上传错误");
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadBitstream() {
|
||||
if (!boardAddress.value || !boardPort.value) {
|
||||
dialog.error("开发板地址或端口空缺");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await jtagController.downloadBitstream(
|
||||
boardAddress.value,
|
||||
boardPort.value,
|
||||
);
|
||||
return resp;
|
||||
} catch (e) {
|
||||
dialog.error("上传错误");
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
watch([boardAddress, boardPort], () => {
|
||||
if (
|
||||
z.string().ip().safeParse(boardAddress).success &&
|
||||
z.number().positive().safeParse(boardPort).success
|
||||
)
|
||||
getIDCode();
|
||||
});
|
||||
|
||||
async function getIDCode() {
|
||||
if (!boardAddress.value || !boardPort.value) {
|
||||
dialog.error("开发板地址或端口空缺");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await jtagController.getDeviceIDCode(
|
||||
boardAddress.value,
|
||||
boardPort.value,
|
||||
);
|
||||
jtagIDCode.value = toString(resp);
|
||||
} catch (e) {
|
||||
dialog.error("获取IDCode错误");
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
return () => (
|
||||
<div>
|
||||
<h1 class="font-bold text-center text-2xl">Jtag</h1>
|
||||
<div class="flex">
|
||||
<p class="grow">IDCode: {jtagIDCode.value}</p>
|
||||
<button class="btn btn-circle w-8 h-8" onClick={getIDCode}>
|
||||
<svg
|
||||
class="icon opacity-70"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="4865"
|
||||
width="200"
|
||||
height="200"
|
||||
>
|
||||
<path
|
||||
d="M894.481158 505.727133c0 49.589418-9.711176 97.705276-28.867468 143.007041-18.501376 43.74634-44.98454 83.031065-78.712713 116.759237-33.728172 33.728172-73.012897 60.211337-116.759237 78.712713-45.311998 19.156292-93.417623 28.877701-143.007041 28.877701s-97.695043-9.721409-142.996808-28.877701c-43.756573-18.501376-83.031065-44.98454-116.76947-78.712713-33.728172-33.728172-60.211337-73.012897-78.712713-116.759237-19.156292-45.301765-28.867468-93.417623-28.867468-143.007041 0-49.579185 9.711176-97.695043 28.867468-142.996808 18.501376-43.74634 44.98454-83.031065 78.712713-116.759237 33.738405-33.728172 73.012897-60.211337 116.76947-78.712713 45.301765-19.166525 93.40739-28.877701 142.996808-28.877701 52.925397 0 104.008842 11.010775 151.827941 32.745798 46.192042 20.977777 86.909395 50.79692 121.016191 88.608084 4.389984 4.860704 8.646937 9.854439 12.781094 14.97097l0-136.263453c0-11.307533 9.168824-20.466124 20.466124-20.466124 11.307533 0 20.466124 9.15859 20.466124 20.466124l0 183.64253c0 5.433756-2.148943 10.632151-5.986341 14.46955-3.847631 3.837398-9.046027 5.996574-14.479783 5.996574l-183.64253-0.020466c-11.307533 0-20.466124-9.168824-20.466124-20.466124 0-11.307533 9.168824-20.466124 20.466124-20.466124l132.293025 0.020466c-3.960195-4.952802-8.063653-9.782807-12.289907-14.479783-30.320563-33.605376-66.514903-60.098773-107.549481-78.753645-42.467207-19.289322-87.850837-29.072129-134.902456-29.072129-87.195921 0-169.172981 33.9533-230.816946 95.597265-61.654198 61.654198-95.597265 143.621025-95.597265 230.816946s33.943067 169.172981 95.597265 230.816946c61.643965 61.654198 143.621025 95.607498 230.816946 95.607498s169.172981-33.9533 230.816946-95.607498c61.654198-61.643965 95.597265-143.621025 95.597265-230.816946 0-11.2973 9.168824-20.466124 20.466124-20.466124C885.322567 485.261009 894.481158 494.429833 894.481158 505.727133z"
|
||||
p-id="4866"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<UploadCard
|
||||
class="bg-base-200"
|
||||
upload-event={uploadBitstream}
|
||||
download-event={downloadBitstream}
|
||||
defaultFile={eqps.jtagBitstream}
|
||||
onFinishedUpload={(file: File) => {
|
||||
eqps.jtagBitstream = file;
|
||||
}}
|
||||
>
|
||||
{" "}
|
||||
</UploadCard>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
<script lang="tsx">
|
||||
// 添加一个静态方法来获取默认props
|
||||
export function getDefaultProps() {
|
||||
return {
|
||||
size: 1
|
||||
size: 1,
|
||||
jtagAddr: "127.0.0.1",
|
||||
jtagPort: "1234",
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
@ -58,14 +194,18 @@ export function getDefaultProps() {
|
|||
display: block;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* IE/Edge */
|
||||
-webkit-user-select: none;
|
||||
/* Safari */
|
||||
-moz-user-select: none;
|
||||
/* Firefox */
|
||||
-ms-user-select: none;
|
||||
/* IE/Edge */
|
||||
}
|
||||
|
||||
.motherboard-svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none; /* 禁止鼠标交互 */
|
||||
pointer-events: none;
|
||||
/* 禁止鼠标交互 */
|
||||
}
|
||||
</style>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,7 +2,6 @@ import { createWebHistory, createRouter } from "vue-router";
|
|||
import LoginView from "../views/LoginView.vue";
|
||||
import UserView from "../views/UserView.vue";
|
||||
import TestView from "../views/TestView.vue";
|
||||
import JtagTest from "../views/JtagTest.vue";
|
||||
import ProjectView from "../views/ProjectView.vue";
|
||||
import HomeView from "@/views/HomeView.vue";
|
||||
import AdminView from "@/views/AdminView.vue";
|
||||
|
@ -12,7 +11,6 @@ const routes = [
|
|||
{ path: "/login", name: "Login", component: LoginView },
|
||||
{ path: "/user", name: "User", component: UserView },
|
||||
{ path: "/test", name: "Test", component: TestView },
|
||||
{ path: "/test/jtag", name: "JtagTest", component: JtagTest },
|
||||
{ path: "/project", name: "Project", component: ProjectView },
|
||||
{ path: "/admin", name: "Admin", component: AdminView },
|
||||
];
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { ref, reactive } from 'vue';
|
||||
|
||||
// 约束电平状态类型
|
||||
export type ConstraintLevel = 'high' | 'low' | 'undefined';
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { isUndefined } from 'lodash';
|
||||
|
||||
export const useEquipments = defineStore('equipments', () => {
|
||||
const jtagIPAddr = ref("127.0.0.1")
|
||||
const jtagPort = ref("1234")
|
||||
const jtagBitstream = ref<File | undefined>()
|
||||
const remoteUpdateIPAddr = ref("127.0.0.1")
|
||||
const remoteUpdatePort = ref("1234")
|
||||
const remoteUpdateBitstream = ref<File | undefined>()
|
||||
|
||||
return {
|
||||
jtagIPAddr,
|
||||
jtagPort,
|
||||
jtagBitstream,
|
||||
remoteUpdateIPAddr,
|
||||
remoteUpdatePort,
|
||||
remoteUpdateBitstream,
|
||||
}
|
||||
})
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
<template>
|
||||
<div class="h-screen flex flex-col overflow-hidden">
|
||||
<!-- 主要内容 -->
|
||||
<div class="flex-1 flex justify-center">
|
||||
<div class="h-full w-32"></div>
|
||||
|
||||
<div class="h-full w-[70%] shadow-2xl flex items-start p-4">
|
||||
<button class="btn btn-primary h-10 w-30">获取ID Code</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
|
||||
const eventSource = ref<EventSource>();
|
||||
|
||||
const initSource = () => {
|
||||
eventSource.value = new EventSource("http://localhost:500/api/log/example");
|
||||
|
||||
eventSource.value.onmessage = (event) => {
|
||||
console.log("收到消息内容是:", event.data);
|
||||
};
|
||||
|
||||
eventSource.value.onerror = (error) => {
|
||||
console.error("SSE 连接出错:", error);
|
||||
eventSource.value!.close();
|
||||
};
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initSource();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (eventSource) {
|
||||
eventSource.value?.close();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
@import "../assets/main.css";
|
||||
</style>
|
|
@ -1,80 +1,65 @@
|
|||
<template>
|
||||
<div class="h-screen flex flex-col overflow-hidden">
|
||||
<div class="flex flex-1 overflow-hidden relative">
|
||||
<!-- 左侧图形化区域 -->
|
||||
<!-- 左侧图形化区域 -->
|
||||
<div class="relative bg-base-200 overflow-hidden h-full" :style="{ width: leftPanelWidth + '%' }">
|
||||
<DiagramCanvas
|
||||
ref="diagramCanvas"
|
||||
:componentModules="componentModules"
|
||||
@component-selected="handleComponentSelected"
|
||||
@component-moved="handleComponentMoved"
|
||||
@component-delete="handleComponentDelete"
|
||||
@wire-created="handleWireCreated"
|
||||
@wire-deleted="handleWireDeleted"
|
||||
@diagram-updated="handleDiagramUpdated"
|
||||
@open-components="openComponentsMenu"
|
||||
@load-component-module="handleLoadComponentModule"
|
||||
/>
|
||||
<DiagramCanvas ref="diagramCanvas" :componentModules="componentModules"
|
||||
@component-selected="handleComponentSelected" @component-moved="handleComponentMoved"
|
||||
@component-delete="handleComponentDelete" @wire-created="handleWireCreated" @wire-deleted="handleWireDeleted"
|
||||
@diagram-updated="handleDiagramUpdated" @open-components="openComponentsMenu"
|
||||
@load-component-module="handleLoadComponentModule" />
|
||||
</div>
|
||||
|
||||
<!-- 拖拽分割线 -->
|
||||
<div
|
||||
class="resizer cursor-col-resize bg-base-300 hover:bg-primary hover:opacity-70 active:bg-primary active:opacity-90 transition-colors"
|
||||
@mousedown="startResize"
|
||||
></div>
|
||||
|
||||
class="resizer bg-base-100 hover:bg-primary hover:opacity-70 active:bg-primary active:opacity-90 transition-colors"
|
||||
@mousedown="startResize"></div>
|
||||
|
||||
<!-- 右侧编辑区域 -->
|
||||
<div class="bg-base-100 h-full overflow-hidden flex flex-col" :style="{ width: (100 - leftPanelWidth) + '%' }">
|
||||
<div class="overflow-y-auto p-4 flex-1">
|
||||
<PropertyPanel
|
||||
:componentData="selectedComponentData"
|
||||
:componentConfig="selectedComponentConfig"
|
||||
@updateProp="updateComponentProp"
|
||||
@updateDirectProp="updateComponentDirectProp"
|
||||
/>
|
||||
<div class="bg-base-200 h-full overflow-hidden flex flex-col" :style="{ width: 100 - leftPanelWidth + '%' }">
|
||||
<div class="overflow-y-auto flex-1">
|
||||
<PropertyPanel :componentData="selectedComponentData" :componentConfig="selectedComponentConfig"
|
||||
@updateProp="updateComponentProp" @updateDirectProp="updateComponentDirectProp" />
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- 元器件选择组件 -->
|
||||
<ComponentSelector
|
||||
:open="showComponentsMenu"
|
||||
@update:open="showComponentsMenu = $event"
|
||||
@add-component="handleAddComponent"
|
||||
@add-template="handleAddTemplate"
|
||||
@close="showComponentsMenu = false"
|
||||
/>
|
||||
</div>
|
||||
<!-- 元器件选择组件 -->
|
||||
<ComponentSelector :open="showComponentsMenu" @update:open="showComponentsMenu = $event"
|
||||
@add-component="handleAddComponent" @add-template="handleAddTemplate" @close="showComponentsMenu = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 引入wokwi-elements和组件
|
||||
// import "@wokwi/elements"; // 不再需要全局引入 wokwi
|
||||
import { ref, reactive, computed, onMounted, onUnmounted, defineAsyncComponent, shallowRef } from 'vue'; // 引入 defineAsyncComponent 和 shallowRef
|
||||
import DiagramCanvas from '@/components/DiagramCanvas.vue';
|
||||
import ComponentSelector from '@/components/ComponentSelector.vue';
|
||||
import PropertyPanel from '@/components/PropertyPanel.vue';
|
||||
import type { DiagramData, DiagramPart, ConnectionArray } from '@/components/diagramManager';
|
||||
import { validateDiagramData } from '@/components/diagramManager';
|
||||
import {
|
||||
type PropertyConfig,
|
||||
generatePropertyConfigs,
|
||||
generatePropsFromDefault,
|
||||
generatePropsFromAttrs,
|
||||
getPropValue
|
||||
} from '@/components/equipments/componentConfig'; // 引入组件配置工具
|
||||
import { ref, computed, onMounted, onUnmounted, shallowRef } from "vue"; // 引入 defineAsyncComponent 和 shallowRef
|
||||
import DiagramCanvas from "@/components/DiagramCanvas.vue";
|
||||
import ComponentSelector from "@/components/ComponentSelector.vue";
|
||||
import PropertyPanel from "@/components/PropertyPanel.vue";
|
||||
import type { DiagramData, DiagramPart } from "@/components/diagramManager";
|
||||
import {
|
||||
type PropertyConfig,
|
||||
generatePropertyConfigs,
|
||||
generatePropsFromDefault,
|
||||
generatePropsFromAttrs,
|
||||
} from "@/components/equipments/componentConfig"; // 引入组件配置工具
|
||||
|
||||
// --- 元器件管理 ---
|
||||
const showComponentsMenu = ref(false);
|
||||
const diagramData = ref<DiagramData>({
|
||||
version: 1,
|
||||
author: 'admin',
|
||||
editor: 'me',
|
||||
author: "admin",
|
||||
editor: "me",
|
||||
parts: [],
|
||||
connections: []
|
||||
connections: [],
|
||||
});
|
||||
|
||||
const selectedComponentId = ref<string | null>(null);
|
||||
const selectedComponentData = computed(() => {
|
||||
return diagramData.value.parts.find(p => p.id === selectedComponentId.value) || null;
|
||||
return (
|
||||
diagramData.value.parts.find((p) => p.id === selectedComponentId.value) ||
|
||||
null
|
||||
);
|
||||
});
|
||||
const diagramCanvas = ref(null);
|
||||
|
||||
|
@ -88,7 +73,9 @@ interface ComponentModule {
|
|||
}
|
||||
|
||||
const componentModules = shallowRef<Record<string, ComponentModule>>({});
|
||||
const selectedComponentConfig = shallowRef<{ props: PropertyConfig[] } | null>(null); // 存储选中组件的配置
|
||||
const selectedComponentConfig = shallowRef<{ props: PropertyConfig[] } | null>(
|
||||
null,
|
||||
); // 存储选中组件的配置
|
||||
|
||||
// 动态加载组件定义
|
||||
async function loadComponentModule(type: string) {
|
||||
|
@ -96,13 +83,13 @@ async function loadComponentModule(type: string) {
|
|||
try {
|
||||
// 假设组件都在 src/components/equipments/ 目录下,且文件名与 type 相同
|
||||
const module = await import(`../components/equipments/${type}.vue`);
|
||||
|
||||
|
||||
// 使用 markRaw 包装模块,避免不必要的响应式处理
|
||||
componentModules.value = {
|
||||
...componentModules.value,
|
||||
[type]: module
|
||||
[type]: module,
|
||||
};
|
||||
|
||||
|
||||
console.log(`Loaded module for ${type}:`, module);
|
||||
} catch (error) {
|
||||
console.error(`Failed to load component module ${type}:`, error);
|
||||
|
@ -114,7 +101,7 @@ async function loadComponentModule(type: string) {
|
|||
|
||||
// 处理组件模块加载请求
|
||||
async function handleLoadComponentModule(type: string) {
|
||||
console.log('Handling load component module request for:', type);
|
||||
console.log("Handling load component module request for:", type);
|
||||
await loadComponentModule(type);
|
||||
}
|
||||
|
||||
|
@ -125,35 +112,37 @@ const isResizing = ref(false);
|
|||
// 分割面板拖拽相关函数
|
||||
function startResize(e: MouseEvent) {
|
||||
isResizing.value = true;
|
||||
document.addEventListener('mousemove', onResize);
|
||||
document.addEventListener('mouseup', stopResize);
|
||||
document.addEventListener("mousemove", onResize);
|
||||
document.addEventListener("mouseup", stopResize);
|
||||
e.preventDefault(); // 防止文本选择
|
||||
}
|
||||
|
||||
function onResize(e: MouseEvent) {
|
||||
if (!isResizing.value) return;
|
||||
|
||||
|
||||
// 获取容器宽度和鼠标位置
|
||||
const container = document.querySelector('.flex-1.overflow-hidden') as HTMLElement;
|
||||
const container = document.querySelector(
|
||||
".flex-1.overflow-hidden",
|
||||
) as HTMLElement;
|
||||
if (!container) return;
|
||||
|
||||
|
||||
const containerWidth = container.clientWidth;
|
||||
const mouseX = e.clientX;
|
||||
|
||||
|
||||
// 计算左侧面板应占的百分比
|
||||
let newWidth = (mouseX / containerWidth) * 100;
|
||||
|
||||
|
||||
// 限制最小宽度和最大宽度
|
||||
newWidth = Math.max(20, Math.min(newWidth, 80));
|
||||
|
||||
|
||||
// 更新宽度
|
||||
leftPanelWidth.value = newWidth;
|
||||
}
|
||||
|
||||
function stopResize() {
|
||||
isResizing.value = false;
|
||||
document.removeEventListener('mousemove', onResize);
|
||||
document.removeEventListener('mouseup', stopResize);
|
||||
document.removeEventListener("mousemove", onResize);
|
||||
document.removeEventListener("mouseup", stopResize);
|
||||
}
|
||||
|
||||
// --- 元器件操作 ---
|
||||
|
@ -162,42 +151,65 @@ function openComponentsMenu() {
|
|||
}
|
||||
|
||||
// 处理 ComponentSelector 组件添加元器件事件
|
||||
async function handleAddComponent(componentData: { type: string; name: string; props: Record<string, any> }) {
|
||||
async function handleAddComponent(componentData: {
|
||||
type: string;
|
||||
name: string;
|
||||
props: Record<string, any>;
|
||||
}) {
|
||||
// 加载组件模块以便后续使用
|
||||
await loadComponentModule(componentData.type);
|
||||
const componentModule = await loadComponentModule(componentData.type);
|
||||
|
||||
// 获取画布容器和位置信息
|
||||
const canvasInstance = diagramCanvas.value as any;
|
||||
|
||||
|
||||
// 获取当前画布的位置信息
|
||||
let position = { x: 100, y: 100 };
|
||||
let scale = 1;
|
||||
|
||||
|
||||
try {
|
||||
if (canvasInstance && canvasInstance.getCanvasPosition && canvasInstance.getScale) {
|
||||
if (
|
||||
canvasInstance &&
|
||||
canvasInstance.getCanvasPosition &&
|
||||
canvasInstance.getScale
|
||||
) {
|
||||
position = canvasInstance.getCanvasPosition();
|
||||
scale = canvasInstance.getScale();
|
||||
|
||||
|
||||
// 获取画布容器
|
||||
const canvasContainer = canvasInstance.$el as HTMLElement;
|
||||
if (canvasContainer) {
|
||||
// 计算可视区域中心点在画布坐标系中的位置
|
||||
const viewportWidth = canvasContainer.clientWidth;
|
||||
const viewportHeight = canvasContainer.clientHeight;
|
||||
|
||||
|
||||
// 计算画布中心点的坐标
|
||||
position.x = (viewportWidth / 2 - position.x) / scale;
|
||||
position.y = (viewportHeight / 2 - position.y) / scale;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取画布位置时出错:', error);
|
||||
console.error("获取画布位置时出错:", error);
|
||||
}
|
||||
|
||||
|
||||
// 添加一些随机偏移,避免元器件重叠
|
||||
const offsetX = Math.floor(Math.random() * 100) - 50;
|
||||
const offsetY = Math.floor(Math.random() * 100) - 50;
|
||||
|
||||
|
||||
// 获取组件的能力页面
|
||||
let capsPage = null;
|
||||
if (
|
||||
componentModule &&
|
||||
componentModule.default &&
|
||||
typeof componentModule.default.getCapabilities === "function"
|
||||
) {
|
||||
try {
|
||||
capsPage = componentModule.default.getCapabilities();
|
||||
console.log(`获取到${componentData.type}组件的能力页面`);
|
||||
} catch (error) {
|
||||
console.error(`获取${componentData.type}组件能力页面失败:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建新组件,使用diagramManager接口定义
|
||||
const newComponent: DiagramPart = {
|
||||
id: `component-${Date.now()}`,
|
||||
|
@ -205,17 +217,22 @@ async function handleAddComponent(componentData: { type: string; name: string; p
|
|||
x: Math.round(position.x + offsetX),
|
||||
y: Math.round(position.y + offsetY),
|
||||
attrs: componentData.props,
|
||||
capsPage: capsPage, // 设置组件的能力页面
|
||||
rotate: 0,
|
||||
group: '',
|
||||
group: "",
|
||||
positionlock: false,
|
||||
hidepins: true,
|
||||
isOn: true,
|
||||
index: 0
|
||||
index: 0,
|
||||
};
|
||||
|
||||
console.log('添加新组件:', newComponent);
|
||||
|
||||
console.log("添加新组件:", newComponent);
|
||||
// 通过画布实例添加组件
|
||||
if (canvasInstance && canvasInstance.getDiagramData && canvasInstance.updateDiagramDataDirectly) {
|
||||
if (
|
||||
canvasInstance &&
|
||||
canvasInstance.getDiagramData &&
|
||||
canvasInstance.updateDiagramDataDirectly
|
||||
) {
|
||||
const currentData = canvasInstance.getDiagramData();
|
||||
currentData.parts.push(newComponent);
|
||||
canvasInstance.updateDiagramDataDirectly(currentData);
|
||||
|
@ -223,81 +240,114 @@ async function handleAddComponent(componentData: { type: string; name: string; p
|
|||
}
|
||||
|
||||
// 处理模板添加事件
|
||||
async function handleAddTemplate(templateData: { id: string; name: string; template: any }) {
|
||||
console.log('添加模板:', templateData);
|
||||
console.log('=== 模板组件数量:', templateData.template?.parts?.length || 0);
|
||||
// 获取画布实例
|
||||
async function handleAddTemplate(templateData: {
|
||||
id: string;
|
||||
name: string;
|
||||
template: any;
|
||||
}) {
|
||||
console.log("添加模板:", templateData);
|
||||
console.log("=== 模板组件数量:", templateData.template?.parts?.length || 0);
|
||||
// 获取画布实例
|
||||
const canvasInstance = diagramCanvas.value as any;
|
||||
if (!canvasInstance || !canvasInstance.getDiagramData || !canvasInstance.updateDiagramDataDirectly) {
|
||||
console.error('没有可用的画布实例添加模板');
|
||||
if (
|
||||
!canvasInstance ||
|
||||
!canvasInstance.getDiagramData ||
|
||||
!canvasInstance.updateDiagramDataDirectly
|
||||
) {
|
||||
console.error("没有可用的画布实例添加模板");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 获取当前图表数据
|
||||
const currentData = canvasInstance.getDiagramData();
|
||||
console.log('=== 当前图表组件数量:', currentData.parts.length);
|
||||
|
||||
console.log("=== 当前图表组件数量:", currentData.parts.length);
|
||||
|
||||
// 生成唯一ID前缀,以确保添加的组件ID不重复
|
||||
const idPrefix = `template-${Date.now()}-`;
|
||||
// 处理模板组件并添加到图表
|
||||
// 处理模板组件并添加到图表
|
||||
if (templateData.template && templateData.template.parts) {
|
||||
// 获取当前视口中心位置
|
||||
let viewportCenter = { x: 300, y: 200 }; // 默认值
|
||||
try {
|
||||
if (canvasInstance && canvasInstance.getCanvasPosition && canvasInstance.getScale) {
|
||||
if (
|
||||
canvasInstance &&
|
||||
canvasInstance.getCanvasPosition &&
|
||||
canvasInstance.getScale
|
||||
) {
|
||||
const position = canvasInstance.getCanvasPosition();
|
||||
const scale = canvasInstance.getScale();
|
||||
|
||||
|
||||
// 获取画布容器
|
||||
const canvasContainer = canvasInstance.$el as HTMLElement;
|
||||
if (canvasContainer) {
|
||||
// 计算可视区域中心点在画布坐标系中的位置
|
||||
const viewportWidth = canvasContainer.clientWidth;
|
||||
const viewportHeight = canvasContainer.clientHeight;
|
||||
|
||||
|
||||
// 计算视口中心点的坐标 (与handleAddComponent函数中的方法相同)
|
||||
viewportCenter.x = (viewportWidth / 2 - position.x) / scale;
|
||||
viewportCenter.y = (viewportHeight / 2 - position.y) / scale;
|
||||
console.log(`=== 计算的视口中心: x=${viewportCenter.x}, y=${viewportCenter.y}, scale=${scale}`);
|
||||
console.log(
|
||||
`=== 计算的视口中心: x=${viewportCenter.x}, y=${viewportCenter.y}, scale=${scale}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取视口中心位置时出错:', error);
|
||||
console.error("获取视口中心位置时出错:", error);
|
||||
}
|
||||
|
||||
console.log('=== 使用视口中心添加模板组件:', viewportCenter);
|
||||
|
||||
|
||||
console.log("=== 使用视口中心添加模板组件:", viewportCenter);
|
||||
|
||||
// 找到模板中的主要组件(假设是第一个组件)
|
||||
const mainPart = templateData.template.parts[0];
|
||||
|
||||
|
||||
// 创建带有新位置的组件
|
||||
const newParts = templateData.template.parts.map((part: any) => {
|
||||
// 创建组件副本并分配新ID
|
||||
const newPart = JSON.parse(JSON.stringify(part));
|
||||
newPart.id = `${idPrefix}${part.id}`;
|
||||
|
||||
// 计算相对于主要组件的偏移量,保持模板内部组件的相对位置关系
|
||||
if (typeof newPart.x === 'number' && typeof newPart.y === 'number') {
|
||||
const oldX = newPart.x;
|
||||
const oldY = newPart.y;
|
||||
|
||||
// 计算相对位置(相对于主要组件)
|
||||
const relativeX = part.x - mainPart.x;
|
||||
const relativeY = part.y - mainPart.y;
|
||||
|
||||
// 应用到视口中心位置
|
||||
newPart.x = viewportCenter.x + relativeX;
|
||||
newPart.y = viewportCenter.y + relativeY;
|
||||
|
||||
console.log(`=== 组件[${newPart.id}]位置调整: (${oldX},${oldY}) -> (${newPart.x},${newPart.y})`);
|
||||
}
|
||||
|
||||
return newPart;
|
||||
});
|
||||
|
||||
const newParts = await Promise.all(
|
||||
templateData.template.parts.map(async (part: any) => {
|
||||
// 创建组件副本并分配新ID
|
||||
const newPart = JSON.parse(JSON.stringify(part));
|
||||
newPart.id = `${idPrefix}${part.id}`;
|
||||
|
||||
// 尝试加载组件模块并获取能力页面
|
||||
try {
|
||||
const componentModule = await loadComponentModule(part.type);
|
||||
if (
|
||||
componentModule &&
|
||||
componentModule.default &&
|
||||
typeof componentModule.default.getCapabilities === "function"
|
||||
) {
|
||||
newPart.capsPage = componentModule.default.getCapabilities();
|
||||
console.log(`加载模板组件${part.type}组件的能力页面成功`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`加载模板组件${part.type}的能力页面失败:`, error);
|
||||
}
|
||||
|
||||
// 计算相对于主要组件的偏移量,保持模板内部组件的相对位置关系
|
||||
if (typeof newPart.x === "number" && typeof newPart.y === "number") {
|
||||
const oldX = newPart.x;
|
||||
const oldY = newPart.y;
|
||||
|
||||
// 计算相对位置(相对于主要组件)
|
||||
const relativeX = part.x - mainPart.x;
|
||||
const relativeY = part.y - mainPart.y;
|
||||
|
||||
// 应用到视口中心位置
|
||||
newPart.x = viewportCenter.x + relativeX;
|
||||
newPart.y = viewportCenter.y + relativeY;
|
||||
|
||||
console.log(
|
||||
`=== 组件[${newPart.id}]位置调整: (${oldX},${oldY}) -> (${newPart.x},${newPart.y})`,
|
||||
);
|
||||
}
|
||||
|
||||
return newPart;
|
||||
}),
|
||||
);
|
||||
|
||||
// 向图表添加新组件
|
||||
currentData.parts.push(...newParts);
|
||||
|
||||
|
||||
// 处理连接关系
|
||||
if (templateData.template.connections) {
|
||||
// 创建一个映射表用于转换旧组件ID到新组件ID
|
||||
|
@ -305,45 +355,47 @@ async function handleAddTemplate(templateData: { id: string; name: string; templ
|
|||
templateData.template.parts.forEach((part: any) => {
|
||||
idMap[part.id] = `${idPrefix}${part.id}`;
|
||||
});
|
||||
|
||||
|
||||
// 添加连接,更新组件ID引用
|
||||
const newConnections = templateData.template.connections.map((conn: any) => {
|
||||
// 处理连接数据 (格式为 [from, to, type, path])
|
||||
if (Array.isArray(conn)) {
|
||||
const [from, to, type, path] = conn;
|
||||
|
||||
// 从连接字符串中提取组件ID和引脚ID
|
||||
const fromParts = from.split(':');
|
||||
const toParts = to.split(':');
|
||||
|
||||
if (fromParts.length === 2 && toParts.length === 2) {
|
||||
const fromComponentId = fromParts[0];
|
||||
const fromPinId = fromParts[1];
|
||||
const toComponentId = toParts[0];
|
||||
const toPinId = toParts[1];
|
||||
|
||||
// 创建新的连接字符串,使用新的组件ID
|
||||
const newFrom = `${idMap[fromComponentId] || fromComponentId}:${fromPinId}`;
|
||||
const newTo = `${idMap[toComponentId] || toComponentId}:${toPinId}`;
|
||||
|
||||
return [newFrom, newTo, type, path];
|
||||
const newConnections = templateData.template.connections.map(
|
||||
(conn: any) => {
|
||||
// 处理连接数据 (格式为 [from, to, type, path])
|
||||
if (Array.isArray(conn)) {
|
||||
const [from, to, type, path] = conn;
|
||||
|
||||
// 从连接字符串中提取组件ID和引脚ID
|
||||
const fromParts = from.split(":");
|
||||
const toParts = to.split(":");
|
||||
|
||||
if (fromParts.length === 2 && toParts.length === 2) {
|
||||
const fromComponentId = fromParts[0];
|
||||
const fromPinId = fromParts[1];
|
||||
const toComponentId = toParts[0];
|
||||
const toPinId = toParts[1];
|
||||
|
||||
// 创建新的连接字符串,使用新的组件ID
|
||||
const newFrom = `${idMap[fromComponentId] || fromComponentId}:${fromPinId}`;
|
||||
const newTo = `${idMap[toComponentId] || toComponentId}:${toPinId}`;
|
||||
|
||||
return [newFrom, newTo, type, path];
|
||||
}
|
||||
}
|
||||
}
|
||||
return conn; // 如果格式不匹配,保持原样
|
||||
});
|
||||
|
||||
return conn; // 如果格式不匹配,保持原样
|
||||
},
|
||||
);
|
||||
|
||||
// 添加到当前连接列表
|
||||
currentData.connections.push(...newConnections);
|
||||
}
|
||||
// 更新图表数据
|
||||
// 更新图表数据
|
||||
canvasInstance.updateDiagramDataDirectly(currentData);
|
||||
console.log('=== 更新图表数据完成,新组件数量:', currentData.parts.length);
|
||||
|
||||
console.log("=== 更新图表数据完成,新组件数量:", currentData.parts.length);
|
||||
|
||||
// 显示成功消息
|
||||
showToast(`已添加 ${templateData.name} 模板`, 'success');
|
||||
showToast(`已添加 ${templateData.name} 模板`, "success");
|
||||
} else {
|
||||
console.error('模板格式错误,缺少parts数组');
|
||||
showToast('模板格式错误', 'error');
|
||||
console.error("模板格式错误,缺少parts数组");
|
||||
showToast("模板格式错误", "error");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -355,36 +407,44 @@ async function handleComponentSelected(componentData: DiagramPart | null) {
|
|||
if (componentData) {
|
||||
// 先加载组件模块
|
||||
const moduleRef = await loadComponentModule(componentData.type);
|
||||
|
||||
|
||||
if (moduleRef) {
|
||||
try {
|
||||
// 使用组件配置工具创建配置项
|
||||
const propConfigs: PropertyConfig[] = [];
|
||||
|
||||
|
||||
// 1. 自动获取组件属性信息 - 利用DiagramPart接口结构
|
||||
const directPropConfigs = generatePropertyConfigs(componentData);
|
||||
propConfigs.push(...directPropConfigs);
|
||||
|
||||
|
||||
// 2. 尝试使用组件导出的getDefaultProps方法获取配置
|
||||
if (typeof moduleRef.getDefaultProps === 'function') {
|
||||
if (typeof moduleRef.getDefaultProps === "function") {
|
||||
// 从getDefaultProps方法构建配置
|
||||
const defaultProps = moduleRef.getDefaultProps();
|
||||
const defaultPropConfigs = generatePropsFromDefault(defaultProps);
|
||||
propConfigs.push(...defaultPropConfigs);
|
||||
|
||||
|
||||
selectedComponentConfig.value = { props: propConfigs };
|
||||
console.log(`Built config for ${componentData.type} from getDefaultProps:`, selectedComponentConfig.value);
|
||||
console.log(
|
||||
`Built config for ${componentData.type} from getDefaultProps:`,
|
||||
selectedComponentConfig.value,
|
||||
);
|
||||
} else {
|
||||
console.warn(`Component ${componentData.type} does not export getDefaultProps method.`);
|
||||
console.warn(
|
||||
`Component ${componentData.type} does not export getDefaultProps method.`,
|
||||
);
|
||||
// 创建一个空配置,只显示组件提供的属性
|
||||
const attrs = componentData.attrs || {};
|
||||
const attrPropConfigs = generatePropsFromAttrs(attrs);
|
||||
propConfigs.push(...attrPropConfigs);
|
||||
|
||||
|
||||
selectedComponentConfig.value = { props: propConfigs };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error building config for ${componentData.type}:`, error);
|
||||
console.error(
|
||||
`Error building config for ${componentData.type}:`,
|
||||
error,
|
||||
);
|
||||
selectedComponentConfig.value = { props: [] };
|
||||
}
|
||||
} else {
|
||||
|
@ -397,12 +457,12 @@ async function handleComponentSelected(componentData: DiagramPart | null) {
|
|||
// 处理图表数据更新事件
|
||||
function handleDiagramUpdated(data: DiagramData) {
|
||||
diagramData.value = data;
|
||||
console.log('Diagram data updated:', data);
|
||||
console.log("Diagram data updated:", data);
|
||||
}
|
||||
|
||||
// 处理组件移动事件
|
||||
function handleComponentMoved(moveData: { id: string; x: number; y: number }) {
|
||||
const part = diagramData.value.parts.find(p => p.id === moveData.id);
|
||||
const part = diagramData.value.parts.find((p) => p.id === moveData.id);
|
||||
if (part) {
|
||||
part.x = moveData.x;
|
||||
part.y = moveData.y;
|
||||
|
@ -412,63 +472,79 @@ function handleComponentMoved(moveData: { id: string; x: number; y: number }) {
|
|||
// 处理组件删除事件
|
||||
function handleComponentDelete(componentId: string) {
|
||||
// 查找要删除的组件
|
||||
const component = diagramData.value.parts.find(p => p.id === componentId);
|
||||
const component = diagramData.value.parts.find((p) => p.id === componentId);
|
||||
if (!component) return;
|
||||
|
||||
|
||||
// 收集需要删除的组件ID列表(包括当前组件和同组组件)
|
||||
const componentsToDelete: string[] = [componentId];
|
||||
|
||||
|
||||
// 如果组件属于一个组,则找出所有同组的组件
|
||||
if (component.group && component.group !== '') {
|
||||
if (component.group && component.group !== "") {
|
||||
const groupMembers = diagramData.value.parts.filter(
|
||||
p => p.group === component.group && p.id !== componentId
|
||||
(p) => p.group === component.group && p.id !== componentId,
|
||||
);
|
||||
|
||||
|
||||
// 将同组组件ID添加到删除列表
|
||||
componentsToDelete.push(...groupMembers.map(p => p.id));
|
||||
console.log(`删除组件 ${componentId} 及其组 ${component.group} 中的 ${groupMembers.length} 个组件`);
|
||||
componentsToDelete.push(...groupMembers.map((p) => p.id));
|
||||
console.log(
|
||||
`删除组件 ${componentId} 及其组 ${component.group} 中的 ${groupMembers.length} 个组件`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// 删除所有标记的组件
|
||||
diagramData.value.parts = diagramData.value.parts.filter(
|
||||
p => !componentsToDelete.includes(p.id)
|
||||
(p) => !componentsToDelete.includes(p.id),
|
||||
);
|
||||
|
||||
|
||||
// 同时删除与这些组件相关的所有连接
|
||||
diagramData.value.connections = diagramData.value.connections.filter(
|
||||
connection => {
|
||||
(connection) => {
|
||||
for (const id of componentsToDelete) {
|
||||
if (connection[0].startsWith(`${id}:`) || connection[1].startsWith(`${id}:`)) {
|
||||
if (
|
||||
connection[0].startsWith(`${id}:`) ||
|
||||
connection[1].startsWith(`${id}:`)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
// 如果删除的是当前选中的组件,清除选中状态
|
||||
if (selectedComponentId.value && componentsToDelete.includes(selectedComponentId.value)) {
|
||||
if (
|
||||
selectedComponentId.value &&
|
||||
componentsToDelete.includes(selectedComponentId.value)
|
||||
) {
|
||||
selectedComponentId.value = null;
|
||||
selectedComponentConfig.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新组件属性的方法
|
||||
function updateComponentProp(componentId: string, propName: string, value: any) {
|
||||
function updateComponentProp(
|
||||
componentId: string,
|
||||
propName: string,
|
||||
value: any,
|
||||
) {
|
||||
const canvasInstance = diagramCanvas.value as any;
|
||||
if (!canvasInstance || !canvasInstance.getDiagramData || !canvasInstance.updateDiagramDataDirectly) {
|
||||
console.error('没有可用的画布实例进行属性更新');
|
||||
if (
|
||||
!canvasInstance ||
|
||||
!canvasInstance.getDiagramData ||
|
||||
!canvasInstance.updateDiagramDataDirectly
|
||||
) {
|
||||
console.error("没有可用的画布实例进行属性更新");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 检查值是否为对象,如果是对象并有value属性,则使用该属性值
|
||||
if (value !== null && typeof value === 'object' && 'value' in value) {
|
||||
if (value !== null && typeof value === "object" && "value" in value) {
|
||||
value = value.value;
|
||||
}
|
||||
|
||||
|
||||
const currentData = canvasInstance.getDiagramData();
|
||||
const part = currentData.parts.find((p: DiagramPart) => p.id === componentId);
|
||||
|
||||
|
||||
if (part) {
|
||||
// 检查是否为基本属性
|
||||
if (propName in part) {
|
||||
|
@ -480,29 +556,45 @@ function updateComponentProp(componentId: string, propName: string, value: any)
|
|||
}
|
||||
part.attrs[propName] = value;
|
||||
}
|
||||
|
||||
|
||||
canvasInstance.updateDiagramDataDirectly(currentData);
|
||||
console.log(`更新组件${componentId}的属性${propName}为:`, value, typeof value);
|
||||
console.log(
|
||||
`更新组件${componentId}的属性${propName}为:`,
|
||||
value,
|
||||
typeof value,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新组件的直接属性
|
||||
function updateComponentDirectProp(componentId: string, propName: string, value: any) {
|
||||
function updateComponentDirectProp(
|
||||
componentId: string,
|
||||
propName: string,
|
||||
value: any,
|
||||
) {
|
||||
const canvasInstance = diagramCanvas.value as any;
|
||||
if (!canvasInstance || !canvasInstance.getDiagramData || !canvasInstance.updateDiagramDataDirectly) {
|
||||
console.error('没有可用的画布实例进行属性更新');
|
||||
if (
|
||||
!canvasInstance ||
|
||||
!canvasInstance.getDiagramData ||
|
||||
!canvasInstance.updateDiagramDataDirectly
|
||||
) {
|
||||
console.error("没有可用的画布实例进行属性更新");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const currentData = canvasInstance.getDiagramData();
|
||||
const part = currentData.parts.find((p: DiagramPart) => p.id === componentId);
|
||||
|
||||
|
||||
if (part) {
|
||||
// @ts-ignore: 动态属性赋值
|
||||
part[propName] = value;
|
||||
|
||||
|
||||
canvasInstance.updateDiagramDataDirectly(currentData);
|
||||
console.log(`更新组件${componentId}的直接属性${propName}为:`, value, typeof value);
|
||||
console.log(
|
||||
`更新组件${componentId}的直接属性${propName}为:`,
|
||||
value,
|
||||
typeof value,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -510,12 +602,12 @@ function updateComponentDirectProp(componentId: string, propName: string, value:
|
|||
|
||||
// 处理连线创建事件
|
||||
function handleWireCreated(wireData: any) {
|
||||
console.log('Wire created:', wireData);
|
||||
console.log("Wire created:", wireData);
|
||||
}
|
||||
|
||||
// 处理连线删除事件
|
||||
function handleWireDeleted(wireId: string) {
|
||||
console.log('Wire deleted:', wireId);
|
||||
console.log("Wire deleted:", wireId);
|
||||
}
|
||||
|
||||
// 导出当前diagram数据
|
||||
|
@ -529,10 +621,15 @@ function exportDiagram() {
|
|||
|
||||
// --- 消息提示 ---
|
||||
const showNotification = ref(false);
|
||||
const notificationMessage = ref('');
|
||||
const notificationType = ref<'success' | 'error' | 'info'>('info');
|
||||
const notificationMessage = ref("");
|
||||
const notificationType = ref<"success" | "error" | "info">("info");
|
||||
|
||||
function showToast(message: string, type: 'success' | 'error' | 'info' = 'info', duration = 3000) { const canvasInstance = diagramCanvas.value as any;
|
||||
function showToast(
|
||||
message: string,
|
||||
type: "success" | "error" | "info" = "info",
|
||||
duration = 3000,
|
||||
) {
|
||||
const canvasInstance = diagramCanvas.value as any;
|
||||
if (canvasInstance && canvasInstance.showToast) {
|
||||
canvasInstance.showToast(message, type, duration);
|
||||
} else {
|
||||
|
@ -540,7 +637,7 @@ function showToast(message: string, type: 'success' | 'error' | 'info' = 'info',
|
|||
notificationMessage.value = message;
|
||||
notificationType.value = type;
|
||||
showNotification.value = true;
|
||||
|
||||
|
||||
// 设置自动消失
|
||||
setTimeout(() => {
|
||||
showNotification.value = false;
|
||||
|
@ -555,33 +652,33 @@ function showToast(message: string, type: 'success' | 'error' | 'info' = 'info',
|
|||
// --- 生命周期钩子 ---
|
||||
onMounted(async () => {
|
||||
// 初始化画布设置
|
||||
console.log('ProjectView mounted, diagram canvas ref:', diagramCanvas.value);
|
||||
|
||||
console.log("ProjectView mounted, diagram canvas ref:", diagramCanvas.value);
|
||||
|
||||
// 获取初始图表数据
|
||||
const canvasInstance = diagramCanvas.value as any;
|
||||
if (canvasInstance && canvasInstance.getDiagramData) {
|
||||
diagramData.value = canvasInstance.getDiagramData();
|
||||
|
||||
|
||||
// 预加载所有使用的组件模块,以确保它们在渲染时可用
|
||||
const componentTypes = new Set<string>();
|
||||
diagramData.value.parts.forEach(part => {
|
||||
diagramData.value.parts.forEach((part) => {
|
||||
componentTypes.add(part.type);
|
||||
});
|
||||
|
||||
console.log('Preloading component modules:', Array.from(componentTypes));
|
||||
|
||||
|
||||
console.log("Preloading component modules:", Array.from(componentTypes));
|
||||
|
||||
// 并行加载所有组件模块
|
||||
await Promise.all(
|
||||
Array.from(componentTypes).map(type => loadComponentModule(type))
|
||||
Array.from(componentTypes).map((type) => loadComponentModule(type)),
|
||||
);
|
||||
|
||||
console.log('All component modules loaded');
|
||||
|
||||
console.log("All component modules loaded");
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('mousemove', onResize);
|
||||
document.removeEventListener('mouseup', stopResize);
|
||||
document.removeEventListener("mousemove", onResize);
|
||||
document.removeEventListener("mouseup", stopResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -593,13 +690,13 @@ onUnmounted(() => {
|
|||
.resizer {
|
||||
width: 6px;
|
||||
height: 100%;
|
||||
background-color: hsl(var(--b3));
|
||||
cursor: col-resize;
|
||||
transition: background-color 0.3s;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.resizer:hover, .resizer:active {
|
||||
.resizer:hover,
|
||||
.resizer:active {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
|
@ -618,6 +715,7 @@ onUnmounted(() => {
|
|||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
|
@ -625,7 +723,8 @@ onUnmounted(() => {
|
|||
}
|
||||
|
||||
/* 确保滚动行为仅在需要时出现 */
|
||||
html, body {
|
||||
html,
|
||||
body {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="w-full">
|
||||
<div class="collapse bg-primary border-base-300 border">
|
||||
<div class="collapse bg-primary">
|
||||
<input type="checkbox" />
|
||||
<div class="collapse-title font-semibold text-lg text-white">
|
||||
自定义开发板参数
|
||||
|
@ -73,7 +73,7 @@ async function uploadBitstream(event: Event, bitstream: File) {
|
|||
boardAddress.value,
|
||||
fileParam,
|
||||
);
|
||||
return resp;
|
||||
return resp
|
||||
} catch (e) {
|
||||
dialog.error("上传错误");
|
||||
}
|
||||
|
|
|
@ -7,5 +7,8 @@
|
|||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
],
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import vueJsx from '@vitejs/plugin-vue-jsx'
|
|||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
import tailwindcss from '@tailwindcss/postcss'
|
||||
import autoprefixer from 'autoprefixer'
|
||||
import basicSsl from '@vitejs/plugin-basic-ssl'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
|
@ -21,7 +20,6 @@ export default defineConfig({
|
|||
}),
|
||||
vueJsx(),
|
||||
vueDevTools(),
|
||||
// basicSsl()
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
|
|
Loading…
Reference in New Issue