diff --git a/docs/设计方案.md b/docs/设计方案.md
index ff36640..3882108 100644
--- a/docs/设计方案.md
+++ b/docs/设计方案.md
@@ -55,7 +55,7 @@
**还原真实行为**:在后台线程里直接修改控件,会抛出和 VS 中按 F5 调试时一样的"线程间操作无效"异常;单选按钮、复选框、下拉框的事件顺序与真实 WinForms 一致。
-**教材要求的运行能力**:多窗体(`Show`、`Hide`、`Close`,项目 1 的登录程序就用到);控制台输出面板(`Console.WriteLine`);虚拟 COM 口(`SerialPort.GetPortNames()` 返回 COM1–COM5,保留"选串口、点打开"的操作步骤)。
+**教材要求的运行能力**:多窗体(`Show`、`Hide`、`Close`,项目 1 的登录程序就用到);控制台输出面板(`Console.WriteLine`);虚拟 COM 口(`SerialPort.GetPortNames()` 返回 COM2–COM5,对应实训台的四台设备,保留"选串口、点打开"的操作步骤)。
**导入与导出 VS 工程**:设计器能读懂 VS 生成的 `Designer.cs`,教材配套的 19 个参考工程可直接导入,作为初始模板和参考答案;学生作品也能导出为 VS 工程,在机房真机上运行。
diff --git a/runtime/Sim.WinForms/Ports/SerialPort.cs b/runtime/Sim.WinForms/Ports/SerialPort.cs
index bd03cb0..b5d321a 100644
--- a/runtime/Sim.WinForms/Ports/SerialPort.cs
+++ b/runtime/Sim.WinForms/Ports/SerialPort.cs
@@ -8,8 +8,8 @@ namespace System.IO.Ports;
///
public class SerialPort : Component
{
- /// COM1 是电脑自带串口,COM2–COM5 对应实训台上的 4017、4150、ZigBee 协调器和 LED 屏
- private static readonly string[] VirtualPorts = { "COM1", "COM2", "COM3", "COM4", "COM5" };
+ /// COM2–COM5 对应实训台上的 4017、4150、ZigBee 协调器和 LED 屏,不提供电脑自带的 COM1
+ private static readonly string[] VirtualPorts = { "COM2", "COM3", "COM4", "COM5" };
public SerialPort() { }
@@ -19,7 +19,7 @@ public class SerialPort : Component
public static string[] GetPortNames() => (string[])VirtualPorts.Clone();
- public string PortName { get; set; } = "COM1";
+ public string PortName { get; set; } = "COM2";
public int BaudRate { get; set; } = 9600;
public int DataBits { get; set; } = 8;
public int ReadTimeout { get; set; } = -1;
diff --git a/samples/p5-t3-推送灾情/task.md b/samples/p5-t3-推送灾情/task.md
index 589b2c6..c8339f3 100644
--- a/samples/p5-t3-推送灾情/task.md
+++ b/samples/p5-t3-推送灾情/task.md
@@ -37,7 +37,7 @@
1. 点 **+ 新建类**,建 `RecordClass`,写两个静态成员:`string[,] record = new string[50, 2]`(每条记录存时间和内容)、`int recoudeindex = 0`(下一条记录存在第几行)。
2. Form1 的代码开头加上 `using System.IO.Ports;`、`using DeviceCtrlLibrary;`、`using DeviceCtrlLibrary.Model;`;全局变量:`ADAM4150ResModel ResModel;`、`bool IsOpen = false;`、`string LED_MSG = "";`。
-3. **Form1_Load**:清空两个下拉框,用 for 循环加串口号;遇到 `COM1`(电脑自带的串口)就 `continue` 跳过。
+3. **Form1_Load**:清空两个下拉框,用 for 循环加串口号;遇到 `COM1`(电脑自带的串口)就 `continue` 跳过。平台上没有 COM1,这段照书保留,在机房真机上才用得到。
4. **btn_Open 的 Click**:打开时调用 `adam4150Helper.Open(com_environmentPort.Text)` 和 `ledHelper.OpenLedCom(com_ledPort.Text)`,按钮改成"关闭";关闭时关闭 ADAM-4150 串口,按钮改回"开启"。
5. **btn_get 的 Click**:
- 用 `adam4150Helper.SetData()` 读出数据存进 `ResModel`,清空 `LED_MSG`,显示当前时间,火焰、烟雾两个框先写"正常";
diff --git a/server/test/classroom.ts b/server/test/classroom.ts
index 548121f..2adbbfe 100644
--- a/server/test/classroom.ts
+++ b/server/test/classroom.ts
@@ -214,7 +214,7 @@ try {
async (s) => {
const window = s.window!
window.action('com_lamp', { action: 'dropDown' })
- window.action('com_lamp', { action: 'select', index: 3 })
+ window.action('com_lamp', { action: 'select', index: 2 })
window.action('btn_open', { action: 'click' })
await Bun.sleep(300)
// 设备消息由服务器记进实训台状态,再以 board 消息推给浏览器
diff --git a/server/test/load.ts b/server/test/load.ts
index bb71552..952f50c 100644
--- a/server/test/load.ts
+++ b/server/test/load.ts
@@ -80,7 +80,7 @@ const latencies = await Promise.all(
const form = instance.form!
const id = (name: string) => findId(form, name)!
send(instance, { type: 'action', id: id('com_Port'), action: 'dropDown' })
- send(instance, { type: 'action', id: id('com_Port'), action: 'select', index: 4 })
+ send(instance, { type: 'action', id: id('com_Port'), action: 'select', index: 3 })
send(instance, { type: 'action', id: id('btn_Open'), action: 'click' })
send(instance, { type: 'action', id: id('txt_SendText'), action: 'text', value: '压力测试' })
await Bun.sleep(200)
diff --git a/server/test/runner.test.ts b/server/test/runner.test.ts
index 6f02c04..d7d7fb5 100644
--- a/server/test/runner.test.ts
+++ b/server/test/runner.test.ts
@@ -196,7 +196,7 @@ test('项目2 任务2:温度读数来自实训台,滑块变化后再次获
const { runner, id } = await startSample('p2-t2-模拟量采集')
const port = id('com_port')
// 串口列表在 Load 里就已填好
- runner.send({ type: 'action', id: port, action: 'select', index: 1 })
+ runner.send({ type: 'action', id: port, action: 'select', index: 0 })
runner.send({ type: 'action', id: id('btn_Open'), action: 'click' })
const textOf = (name: string) =>
runner
@@ -224,7 +224,7 @@ test('项目3 任务1:单选按钮切换,事件顺序与 WinForms 一致,
await runner.waitFor('patch', (m) => m.changes.some((c) => c.id === lamp && c.prop === 'IsOn' && c.value === true))
expect(runner.messages.some((m) => m.type === 'device')).toBe(false)
- runner.send({ type: 'action', id: id('com_port'), action: 'select', index: 3 })
+ runner.send({ type: 'action', id: id('com_port'), action: 'select', index: 2 })
runner.send({ type: 'action', id: id('btn_open'), action: 'click' })
// 选"关":先触发"开"的 CheckedChanged(又开一次),再触发"关"的 CheckedChanged,最终关灯
@@ -241,8 +241,8 @@ test('项目3 任务1:单选按钮切换,事件顺序与 WinForms 一致,
test('项目3 任务2:勾选人体开关后,有人经过时 5 秒内路灯打开', async () => {
const { runner, id } = await startSample('p3-t2-人体红外ZigBee联动')
- runner.send({ type: 'action', id: id('com_lamp'), action: 'select', index: 3 })
- runner.send({ type: 'action', id: id('com_environment'), action: 'select', index: 2 })
+ runner.send({ type: 'action', id: id('com_lamp'), action: 'select', index: 2 })
+ runner.send({ type: 'action', id: id('com_environment'), action: 'select', index: 1 })
runner.send({ type: 'action', id: id('btn_open'), action: 'click' })
// 复选框的 Click 里读取 Checked:此时 Checked 必须已经是 true
@@ -316,7 +316,7 @@ test('项目1 任务2:计算器(装箱拆箱)和多态', async () => {
/** 选 COM3、点"开启",等定时器读到实训台上的传感器 */
async function openAdam4150(runner: Runner, id: (name: string) => string) {
- runner.send({ type: 'action', id: id('com_port'), action: 'select', index: 2 })
+ runner.send({ type: 'action', id: id('com_port'), action: 'select', index: 1 })
runner.send({ type: 'action', id: id('btn_open'), action: 'click' })
}
diff --git a/web/src/pages/HelpPage.vue b/web/src/pages/HelpPage.vue
index 5ccd62b..615f89d 100644
--- a/web/src/pages/HelpPage.vue
+++ b/web/src/pages/HelpPage.vue
@@ -105,7 +105,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey))
5. 虚拟实训台
- - 运行区下面是虚拟实训台,代替书上的实物设备。程序打开的串口:COM2 → ADAM-4017+,COM3 → ADAM-4150,COM4 → ZigBee 协调器,COM5 → LED 屏。
+ - 运行区下面是虚拟实训台,代替书上的实物设备。程序打开的串口:COM2 → ADAM-4017+,COM3 → ADAM-4150,COM4 → ZigBee 协调器,COM5 → LED 屏。程序里的串口下拉框展开时,每个串口后面也会用灰字标出接的设备。
- 拖动滑块(或点一下滑块再按方向键)调温度、湿度、光照;点人体红外、烟雾、火焰、红外对射切换有无。程序控制的灯、警示灯、LED 屏会跟着变化。
-
两盏照明灯:照明灯 1 接 ZigBee 继电器节点 1(项目 3 的路灯),照明灯 2 接 ADAM-4150 的 DO1(项目 7 的教室、寝室、会议室);警示灯接
diff --git a/web/src/winforms/ControlView.vue b/web/src/winforms/ControlView.vue
index 4b516f0..e338a0e 100644
--- a/web/src/winforms/ControlView.vue
+++ b/web/src/winforms/ControlView.vue
@@ -58,6 +58,8 @@ const items = computed(() => (p.value.Items as string[] | undefined) ?? [])
const editable = computed(() => p.value.DropDownStyle !== 'DropDownList')
const combo = ref()
const listStyle = ref({})
+/** 串口列表里每个口后面用灰字标出实训台上接的设备,只是提示,下拉框的值还是 COM2 这样的串口名 */
+const portDevices: Record = { COM2: 'ADAM-4017+', COM3: 'ADAM-4150', COM4: 'ZigBee 协调器', COM5: 'LED 屏' }
function openDropDown() {
if (!enabled.value) return
@@ -73,7 +75,7 @@ function openDropDown() {
listStyle.value = {
left: `${rect.left}px`,
top: `${rect.bottom}px`,
- width: `${el.offsetWidth}px`,
+ minWidth: `${el.offsetWidth}px`,
transform: `scale(${rect.width / el.offsetWidth || 1})`,
font: css.font,
color: css.color,
@@ -177,7 +179,9 @@ const itemHeight = computed(() => `${(p.value.ItemHeight as number | undefined)
▾
- - {{ item }}
+ -
+ {{ item }}{{ portDevices[item] }}
+
-
diff --git a/web/src/winforms/winforms.css b/web/src/winforms/winforms.css
index 0db59aa..06181fa 100644
--- a/web/src/winforms/winforms.css
+++ b/web/src/winforms/winforms.css
@@ -196,6 +196,13 @@
.wf-combobox-list li {
padding: 1px 3px;
+ white-space: nowrap;
+}
+
+/* 串口后面的设备名提示:比列表文字淡,悬停变蓝底白字时也看得清 */
+.wf-combobox-hint {
+ margin-left: 1.5em;
+ opacity: 0.55;
}
.wf-combobox-list li:hover {