From 8feda289de3ea74f07819ea44681642ded83e3ba Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Thu, 22 Oct 2026 11:23:37 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=9A=E7=AA=97=E4=BD=93=EF=BC=9A=E6=96=B0?= =?UTF-8?q?=E5=BB=BA=20/=20=E5=88=A0=E9=99=A4=E7=AA=97=E4=BD=93=EF=BC=8C?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=201=20=E7=99=BB=E5=BD=95=E7=A4=BA=E4=BE=8B?= =?UTF-8?q?=EF=BC=9B=E6=A0=87=E8=AF=86=E7=AC=A6=E6=94=AF=E6=8C=81=E4=B8=AD?= =?UTF-8?q?=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新建窗体:生成与 VS 新建窗体相同的 X.cs 和 X.Designer.cs,沿用工程命名空间; 删除窗体(启动窗体不能删) - 标签页按窗体分组,启动窗体排在最前并默认打开 - 运行时点哪个窗口哪个到最前;消息框始终在所有窗口之上 - 示例:项目 1 任务 1 登录与增加用户,两个窗体的 Designer.cs 由设计器引擎生成; 修正书上未声明的 change 变量、关闭后重复 Show 已释放窗体的问题 - 设计器的正则支持中文控件名、类名、命名空间 - 修正:切换窗体时属性窗口的名称没有刷新 - 测试:多窗体端到端测试;新窗体模板测试 Co-Authored-By: Claude Opus 5.5 --- README.md | 7 +- docs/设计方案.md | 6 +- samples/p1-t1-登录/AddUser.Designer.cs | 143 +++++++++++++++++++++++++ samples/p1-t1-登录/AddUser.cs | 48 +++++++++ samples/p1-t1-登录/Login.Designer.cs | 133 +++++++++++++++++++++++ samples/p1-t1-登录/Login.cs | 57 ++++++++++ samples/p1-t1-登录/Program.cs | 22 ++++ samples/p1-t1-登录/PublicData.cs | 16 +++ samples/p1-t1-登录/meta.json | 4 + server/test/runner.test.ts | 27 +++++ web/src/App.vue | 125 ++++++++++++++++++--- web/src/designer/PropertyGrid.vue | 3 +- web/src/designer/code.ts | 2 +- web/src/designer/document.ts | 20 ++-- web/src/designer/edit.ts | 8 +- web/src/designer/templates.test.ts | 29 +++++ web/src/designer/templates.ts | 106 ++++++++++++++++++ web/src/winforms/FormWindow.vue | 14 ++- web/src/winforms/winforms.css | 5 +- 19 files changed, 739 insertions(+), 36 deletions(-) create mode 100644 samples/p1-t1-登录/AddUser.Designer.cs create mode 100644 samples/p1-t1-登录/AddUser.cs create mode 100644 samples/p1-t1-登录/Login.Designer.cs create mode 100644 samples/p1-t1-登录/Login.cs create mode 100644 samples/p1-t1-登录/Program.cs create mode 100644 samples/p1-t1-登录/PublicData.cs create mode 100644 samples/p1-t1-登录/meta.json create mode 100644 web/src/designer/templates.test.ts create mode 100644 web/src/designer/templates.ts diff --git a/README.md b/README.md index ae0ad0d..1d5b151 100644 --- a/README.md +++ b/README.md @@ -61,11 +61,12 @@ bun test/load.ts 100 # 压力测试:同时运行 100 个学生程序 - **虚拟实训台**:按教材实训台布局图排列(红外对射、警示灯、LED 屏、照明灯、人体红外、温湿度、光照、烟雾、火焰、ADAM 模块)。每个浏览器会话一台,状态由服务器统一保存:传感器读数由网页上的滑块和开关设定,经服务器推给学生程序;灯、继电器、LED 屏由学生程序控制。 - 新控件:CheckBox、RadioButton(事件顺序与 WinForms 一致),教材控件库的 Lamp、Alarm。 -- 示例工程(按书上代码整理):项目 2 任务 1–3、项目 3 任务 1–2。网址带 `?sample=示例编号` 可直接打开指定示例。 +- 示例工程(按书上代码整理):项目 1 任务 1、项目 2 任务 1–3、项目 3 任务 1–2。网址带 `?sample=示例编号` 可直接打开指定示例。 - 代码编辑器(Monaco):C# 高亮、编译错误波浪线、点错误跳到对应行、F5 运行 / Shift+F5 停止、草稿自动保存在浏览器本地。 -- 窗体设计器(仿 VS):工具箱、拖放与改大小、属性窗口、事件窗口、双击生成事件处理方法;读写 VS 格式的 Designer.cs。 +- 窗体设计器(仿 VS,界面中文):工具箱、拖放与改大小、属性窗口、事件窗口、双击生成事件处理方法、撤销 / 重做;读写 VS 格式的 Designer.cs。 +- 多窗体:新建、删除窗体;运行时多个窗口可切换前后。 尚未完成: - 学生代码目前直接在本机进程里运行,**还没有沙箱**,只能在本机开发时使用;部署前要改为在 Docker 容器中运行。 -- 多窗体(新建窗体)、设计器的撤销 / 重做、账号和班级、自动评测。 +- 导入教材参考工程、账号和班级、自动评测。 diff --git a/docs/设计方案.md b/docs/设计方案.md index ffc6ae3..f15cc30 100644 --- a/docs/设计方案.md +++ b/docs/设计方案.md @@ -200,7 +200,7 @@ IOT/ | 项目 | 教材内容 | 平台需要支持 | 状态 | | --- | --- | --- | --- | -| 1 C# 入门基础 | 登录与新增用户(两个窗体)、计算器、继承与多态演示 | 多窗体、Label、TextBox(PasswordChar)、Button、ComboBox、MessageBox、控制台输出 | 控件已有,示例待整理 | +| 1 C# 入门基础 | 登录与新增用户(两个窗体)、计算器、继承与多态演示 | 多窗体、Label、TextBox(PasswordChar)、Button、ComboBox、MessageBox、控制台输出 | 任务 1 已完成,任务 2 示例待整理 | | 2 环境监测 | LED 屏推送文字;4017 采集温湿度和光照;采集后推送 LED | LED 控件、ledHelper、adam4017Helper、GroupBox、虚拟 COM 口 | 已完成 | | 3 智慧路灯 | ZigBee 开关路灯;人体红外每 5 秒检测并联动路灯 | Lamp 控件、zigBeeHelper、adam4150Helper、CheckBox、RadioButton、Timer | 已完成 | | 4 入侵监测 | 红外对射检测;网络摄像机画面与云台;报警灯 | PictureBox、摄像头仿真、Alarm 控件、图片保存 | | @@ -227,7 +227,7 @@ IOT/ ```mermaid flowchart TB P0["阶段 0 · 技术验证(已完成 2026-10-22)
教材项目 2 任务 1 原样运行;实测编译、延迟、内存"] - P1["阶段 1 · MVP(进行中,约 3 个月)
虚拟实训台 ✔、项目 2–3 示例 ✔、代码编辑器 ✔、窗体设计器 ✔
多窗体、导入教材参考工程"] + P1["阶段 1 · MVP(进行中,约 3 个月)
虚拟实训台 ✔、项目 1–3 示例 ✔、代码编辑器 ✔、窗体设计器 ✔、多窗体 ✔
导入教材参考工程"] P2["阶段 2 · 教学闭环(约 2 个月)
班级码登录、提交作业、自动评测、老师看板、成绩导出;一个班试用"] P3["阶段 3 · 扩展(下学期)
教材项目 4–8:摄像头、线程、SQLite 模拟数据库、图表、虚拟时钟"] P4["阶段 4 · 进阶(之后)
校园街景场景、故障注入题库、导出 VS 工程"] @@ -256,6 +256,8 @@ flowchart TB - 2026-10-22:虚拟实训台(服务器统一保存状态,网页调节环境);CheckBox、RadioButton、Lamp、Alarm;教材项目 2、3 共 5 个任务的示例全部跑通。 - 2026-10-22:代码编辑器换成 Monaco:C# 语法高亮;编译错误以波浪线标在代码里,点错误信息跳到对应行;F5 运行、Shift+F5 停止(在整个页面生效,并阻止浏览器 F5 刷新);改过的代码自动保存在浏览器本地,可一键恢复原始代码。前端打包后 JS + CSS 压缩约 1.2 MB。 - 2026-10-22:窗体设计器:工具箱拖放或双击添加控件,选中、拖动、改大小、拖进拖出分组框,Delete 删除、方向键微调;属性窗口和事件窗口;双击控件或事件生成事件处理方法并跳到代码;Timer 等组件显示在组件栏。Designer.cs 在浏览器端直接解析和生成:未改动的语句保留原文,教材 25 个参考窗体和全部示例读入再写出逐字节不变;布局语句(SuspendLayout / ResumeLayout / PerformLayout)的增删规则与 VS 一致。 +- 2026-10-22:设计器撤销 / 重做(撤销"生成事件处理方法"时连同插入的方法一起撤掉,不覆盖学生已写的代码);工具箱、属性、事件窗口全部显示中文,英文名放在悬停提示里;支持中文的控件名和类名。 +- 2026-10-22:多窗体:新建窗体(与 VS 新建窗体相同的两个文件,沿用工程命名空间)、删除窗体(启动窗体不能删);标签页按窗体分组;运行时点哪个窗口哪个到最前。新增示例:项目 1 任务 1 登录与增加用户(两个窗体的 Designer.cs 由设计器引擎生成)。 ## 技术风险与待确认事项 diff --git a/samples/p1-t1-登录/AddUser.Designer.cs b/samples/p1-t1-登录/AddUser.Designer.cs new file mode 100644 index 0000000..a2195df --- /dev/null +++ b/samples/p1-t1-登录/AddUser.Designer.cs @@ -0,0 +1,143 @@ +namespace 登录 +{ + partial class AddUser + { + /// + /// 必需的设计器变量。 + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 清理所有正在使用的资源。 + /// + /// 如果应释放托管资源,为 true;否则为 false。 + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows 窗体设计器生成的代码 + + /// + /// 设计器支持所需的方法 - 不要 + /// 使用代码编辑器修改此方法的内容。 + /// + private void InitializeComponent() + { + this.LblUsername = new System.Windows.Forms.Label(); + this.txtNewUser1 = new System.Windows.Forms.TextBox(); + this.LblPwd = new System.Windows.Forms.Label(); + this.txtPass1 = new System.Windows.Forms.TextBox(); + this.LblCheckPwd = new System.Windows.Forms.Label(); + this.txtPass2 = new System.Windows.Forms.TextBox(); + this.btnOk = new System.Windows.Forms.Button(); + this.btnCancel = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // LblUsername + // + this.LblUsername.AutoSize = true; + this.LblUsername.Location = new System.Drawing.Point(25, 27); + this.LblUsername.Name = "LblUsername"; + this.LblUsername.Size = new System.Drawing.Size(41, 12); + this.LblUsername.TabIndex = 0; + this.LblUsername.Text = "用户名"; + // + // txtNewUser1 + // + this.txtNewUser1.Location = new System.Drawing.Point(90, 24); + this.txtNewUser1.Name = "txtNewUser1"; + this.txtNewUser1.Size = new System.Drawing.Size(110, 21); + this.txtNewUser1.TabIndex = 1; + // + // LblPwd + // + this.LblPwd.AutoSize = true; + this.LblPwd.Location = new System.Drawing.Point(25, 57); + this.LblPwd.Name = "LblPwd"; + this.LblPwd.Size = new System.Drawing.Size(29, 12); + this.LblPwd.TabIndex = 2; + this.LblPwd.Text = "密码"; + // + // txtPass1 + // + this.txtPass1.Location = new System.Drawing.Point(90, 54); + this.txtPass1.Name = "txtPass1"; + this.txtPass1.PasswordChar = '*'; + this.txtPass1.Size = new System.Drawing.Size(110, 21); + this.txtPass1.TabIndex = 3; + // + // LblCheckPwd + // + this.LblCheckPwd.AutoSize = true; + this.LblCheckPwd.Location = new System.Drawing.Point(25, 87); + this.LblCheckPwd.Name = "LblCheckPwd"; + this.LblCheckPwd.Size = new System.Drawing.Size(53, 12); + this.LblCheckPwd.TabIndex = 4; + this.LblCheckPwd.Text = "确认密码"; + // + // txtPass2 + // + this.txtPass2.Location = new System.Drawing.Point(90, 84); + this.txtPass2.Name = "txtPass2"; + this.txtPass2.PasswordChar = '*'; + this.txtPass2.Size = new System.Drawing.Size(110, 21); + this.txtPass2.TabIndex = 5; + // + // btnOk + // + this.btnOk.Location = new System.Drawing.Point(35, 120); + this.btnOk.Name = "btnOk"; + this.btnOk.Size = new System.Drawing.Size(75, 23); + this.btnOk.TabIndex = 6; + this.btnOk.Text = "确定"; + this.btnOk.UseVisualStyleBackColor = true; + this.btnOk.Click += new System.EventHandler(this.btnOk_Click); + // + // btnCancel + // + this.btnCancel.Location = new System.Drawing.Point(125, 120); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(75, 23); + this.btnCancel.TabIndex = 7; + this.btnCancel.Text = "取消"; + this.btnCancel.UseVisualStyleBackColor = true; + this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); + // + // AddUser + // + this.AcceptButton = this.btnOk; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(220, 160); + this.Controls.Add(this.btnCancel); + this.Controls.Add(this.btnOk); + this.Controls.Add(this.txtPass2); + this.Controls.Add(this.LblCheckPwd); + this.Controls.Add(this.txtPass1); + this.Controls.Add(this.LblPwd); + this.Controls.Add(this.txtNewUser1); + this.Controls.Add(this.LblUsername); + this.Name = "AddUser"; + this.Text = "增加用户"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label LblUsername; + private System.Windows.Forms.TextBox txtNewUser1; + private System.Windows.Forms.Label LblPwd; + private System.Windows.Forms.TextBox txtPass1; + private System.Windows.Forms.Label LblCheckPwd; + private System.Windows.Forms.TextBox txtPass2; + private System.Windows.Forms.Button btnOk; + private System.Windows.Forms.Button btnCancel; + } +} diff --git a/samples/p1-t1-登录/AddUser.cs b/samples/p1-t1-登录/AddUser.cs new file mode 100644 index 0000000..ec38e35 --- /dev/null +++ b/samples/p1-t1-登录/AddUser.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace 登录 +{ + public partial class AddUser : Form + { + public AddUser() + { + InitializeComponent(); + } + + private void btnOk_Click(object sender, EventArgs e) + { + if (PublicData.intNum < 4)//确保还有足够的存储空间 + { + if (txtPass1.Text.Equals(txtPass2.Text) && txtPass2.Text != "")//确保两次密码相同且非空 + { + //存储新增用户 + PublicData.SysUserName[PublicData.intNum] = txtNewUser1.Text; + PublicData.SysPwd[PublicData.intNum] = txtPass1.Text; + PublicData.intNum++;//增加实际存储数量 + this.Close();//关闭当前窗体 + } + else + { + MessageBox.Show("用户名或密码错误,请重新输入", "新增用户"); + } + } + else + { + MessageBox.Show("用户名已满不能增加", "新增用户"); + } + } + + private void btnCancel_Click(object sender, EventArgs e) + { + this.Close(); + } + } +} diff --git a/samples/p1-t1-登录/Login.Designer.cs b/samples/p1-t1-登录/Login.Designer.cs new file mode 100644 index 0000000..ed270f4 --- /dev/null +++ b/samples/p1-t1-登录/Login.Designer.cs @@ -0,0 +1,133 @@ +namespace 登录 +{ + partial class Login + { + /// + /// 必需的设计器变量。 + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 清理所有正在使用的资源。 + /// + /// 如果应释放托管资源,为 true;否则为 false。 + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows 窗体设计器生成的代码 + + /// + /// 设计器支持所需的方法 - 不要 + /// 使用代码编辑器修改此方法的内容。 + /// + private void InitializeComponent() + { + this.LblUsername = new System.Windows.Forms.Label(); + this.txtUserName = new System.Windows.Forms.TextBox(); + this.LblPwd = new System.Windows.Forms.Label(); + this.txtPassword = new System.Windows.Forms.TextBox(); + this.btnOk = new System.Windows.Forms.Button(); + this.btnCancel = new System.Windows.Forms.Button(); + this.btnAdd = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // LblUsername + // + this.LblUsername.AutoSize = true; + this.LblUsername.Location = new System.Drawing.Point(30, 30); + this.LblUsername.Name = "LblUsername"; + this.LblUsername.Size = new System.Drawing.Size(53, 12); + this.LblUsername.TabIndex = 0; + this.LblUsername.Text = "用户名称"; + // + // txtUserName + // + this.txtUserName.Location = new System.Drawing.Point(100, 27); + this.txtUserName.Name = "txtUserName"; + this.txtUserName.Size = new System.Drawing.Size(160, 21); + this.txtUserName.TabIndex = 1; + // + // LblPwd + // + this.LblPwd.AutoSize = true; + this.LblPwd.Location = new System.Drawing.Point(30, 66); + this.LblPwd.Name = "LblPwd"; + this.LblPwd.Size = new System.Drawing.Size(29, 12); + this.LblPwd.TabIndex = 2; + this.LblPwd.Text = "密码"; + // + // txtPassword + // + this.txtPassword.Location = new System.Drawing.Point(100, 63); + this.txtPassword.Name = "txtPassword"; + this.txtPassword.PasswordChar = '*'; + this.txtPassword.Size = new System.Drawing.Size(160, 21); + this.txtPassword.TabIndex = 3; + // + // btnOk + // + this.btnOk.Location = new System.Drawing.Point(30, 105); + this.btnOk.Name = "btnOk"; + this.btnOk.Size = new System.Drawing.Size(75, 23); + this.btnOk.TabIndex = 4; + this.btnOk.Text = "登录"; + this.btnOk.UseVisualStyleBackColor = true; + this.btnOk.Click += new System.EventHandler(this.btnOk_Click); + // + // btnCancel + // + this.btnCancel.Location = new System.Drawing.Point(115, 105); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(75, 23); + this.btnCancel.TabIndex = 5; + this.btnCancel.Text = "取消"; + this.btnCancel.UseVisualStyleBackColor = true; + this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); + // + // btnAdd + // + this.btnAdd.Location = new System.Drawing.Point(200, 105); + this.btnAdd.Name = "btnAdd"; + this.btnAdd.Size = new System.Drawing.Size(75, 23); + this.btnAdd.TabIndex = 6; + this.btnAdd.Text = "增加用户"; + this.btnAdd.UseVisualStyleBackColor = true; + this.btnAdd.Click += new System.EventHandler(this.btnAdd_Click); + // + // Login + // + this.AcceptButton = this.btnOk; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(300, 150); + this.Controls.Add(this.btnAdd); + this.Controls.Add(this.btnCancel); + this.Controls.Add(this.btnOk); + this.Controls.Add(this.txtPassword); + this.Controls.Add(this.LblPwd); + this.Controls.Add(this.txtUserName); + this.Controls.Add(this.LblUsername); + this.Name = "Login"; + this.Text = "登录"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label LblUsername; + private System.Windows.Forms.TextBox txtUserName; + private System.Windows.Forms.Label LblPwd; + private System.Windows.Forms.TextBox txtPassword; + private System.Windows.Forms.Button btnOk; + private System.Windows.Forms.Button btnCancel; + private System.Windows.Forms.Button btnAdd; + } +} diff --git a/samples/p1-t1-登录/Login.cs b/samples/p1-t1-登录/Login.cs new file mode 100644 index 0000000..551d2b2 --- /dev/null +++ b/samples/p1-t1-登录/Login.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace 登录 +{ + public partial class Login : Form + { + public Login() + { + InitializeComponent(); + } + + public bool IsLogin = false;//定义登录标识 + + private void btnOk_Click(object sender, EventArgs e) + { + for (int i = 0; i < PublicData.SysUserName.Length; i++) + { + string usr = PublicData.SysUserName[i]; + string pwd = PublicData.SysPwd[i]; + if (txtUserName.Text == usr && txtPassword.Text == pwd && txtUserName.Text != "")//遍历用户名密码是否存在 + { + IsLogin = true;//变更登录标识 + MessageBox.Show("登录成功,欢迎进入!", "登录"); + PublicData.intUser = i;//存储当前用户在数组中的索引 + break;//结束循环 + } + else + IsLogin = false; + } + if (IsLogin == false) + { + MessageBox.Show("无效的密码或用户名,请重试", "登录"); + txtPassword.Focus(); + } + } + + private void btnCancel_Click(object sender, EventArgs e) + { + IsLogin = false; + this.Close();//关闭当前窗体 + } + + private void btnAdd_Click(object sender, EventArgs e) + { + AddUser addfrm = new AddUser();//实例化添加用户窗体 + addfrm.Show();//显示增加用户窗体 + } + } +} diff --git a/samples/p1-t1-登录/Program.cs b/samples/p1-t1-登录/Program.cs new file mode 100644 index 0000000..6c7f567 --- /dev/null +++ b/samples/p1-t1-登录/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace 登录 +{ + static class Program + { + /// + /// 应用程序的主入口点。 + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Login()); + } + } +} diff --git a/samples/p1-t1-登录/PublicData.cs b/samples/p1-t1-登录/PublicData.cs new file mode 100644 index 0000000..47d6660 --- /dev/null +++ b/samples/p1-t1-登录/PublicData.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace 登录 +{ + public class PublicData + { + public static string[] SysUserName = new string[4];//存储系统用户名 + public static string[] SysPwd = new string[4];//存储系统用户密码 + public static int intNum { get; set; }//当前数据存储的用户数量 + public static int intUser { get; set; }//记录当前用户索引号 + } +} diff --git a/samples/p1-t1-登录/meta.json b/samples/p1-t1-登录/meta.json new file mode 100644 index 0000000..e4ec82d --- /dev/null +++ b/samples/p1-t1-登录/meta.json @@ -0,0 +1,4 @@ +{ + "title": "项目1 任务1 C#引入(登录与增加用户)", + "source": "教材书 p2–7,按书上代码整理。修正:删去未声明的变量 change;增加用户窗体关闭后会被释放,改为每次点击时新建" +} diff --git a/server/test/runner.test.ts b/server/test/runner.test.ts index e03bdfc..fdb307d 100644 --- a/server/test/runner.test.ts +++ b/server/test/runner.test.ts @@ -237,3 +237,30 @@ test('项目3 任务2:勾选人体开关后,有人经过时 5 秒内路灯 const on = await runner.next('device', (m) => m.device === 'zigbee', 7000) expect(on.data).toEqual([1, 1]) }, 30000) + +test('项目1 任务1:多窗体——打开增加用户窗体、添加用户、关闭后登录成功,可以再次打开', async () => { + const { runner, form: login, id } = await startSample('p1-t1-登录') + expect(login.props.Text).toBe('登录') + + runner.send({ type: 'action', id: id('btnAdd'), action: 'click' }) + const { form: addUser } = await runner.waitFor('formShown', (m) => m.form.props.Text === '增加用户') + const addId = (name: string) => findByName(addUser, name).id + runner.send({ type: 'action', id: addId('txtNewUser1'), action: 'text', value: 'admin' }) + runner.send({ type: 'action', id: addId('txtPass1'), action: 'text', value: '123' }) + runner.send({ type: 'action', id: addId('txtPass2'), action: 'text', value: '123' }) + runner.send({ type: 'action', id: addId('btnOk'), action: 'click' }) + await runner.waitFor('formClosed', (m) => m.formId === addUser.id) + + runner.send({ type: 'action', id: id('txtUserName'), action: 'text', value: 'admin' }) + runner.send({ type: 'action', id: id('txtPassword'), action: 'text', value: '123' }) + runner.send({ type: 'action', id: id('btnOk'), action: 'click' }) + const box = await runner.waitFor('messageBox') + expect(box.text).toBe('登录成功,欢迎进入!') + runner.send({ type: 'reply', requestId: box.requestId, result: 'OK' }) + + // 再次打开增加用户窗体:每次新建实例,不会因为上一个已释放而出错 + runner.send({ type: 'action', id: id('btnAdd'), action: 'click' }) + const second = await runner.next('formShown', (m) => m.form.props.Text === '增加用户') + expect(second.form.id).not.toBe(addUser.id) + expect(runner.messages.some((m) => m.type === 'exception')).toBe(false) +}, 30000) diff --git a/web/src/App.vue b/web/src/App.vue index cfe9f0e..8d95fd2 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -1,10 +1,11 @@ +