试用前走查的修改:报错看得见、学生找得到作业、运行区和设计器的若干体验问题
- 运行区:编译错误、运行异常、控制台输出和保存的图片移到窗体下面,不再被实训台挤成 0 高;缩放和放大按钮移到桌面上方,不压窗体的关闭按钮;放大后也能停止程序;点停止后清掉窗体 - 运行异常:常见 .NET 异常配中文说明,显示出错的文件、行号和方法,点击跳到代码 - 运行中的下拉列表挂到页面最外层,能伸出窗体和分组框(小窗体也能选到 COM4、COM5) - 学生登录后进"我的作业";任务面板从工具栏下面开始,不挡导航;记住上次做的实验,从数据库页面返回、恢复数据时默认用它 - 代码页和设计页左栏都占 3/5,文件标签放不下时换行;实验选择框加宽;关闭粘性滚动 - 设计器:双击生成的事件方法带缩进;双击工具箱放进选中控件所在的容器并错开位置;新控件比容器宽时按比例缩小;无边框窗体不画标题栏 - 中文界面:代码编辑器(查找替换等)、属性窗口的颜色选项 - SQL:DATEADD、DATEDIFF、DATEPART 的日期单位(day、hour)解析成 IdentifierLiteral 时报"不支持",补上 - 项目 8 的预置环境数据改成建库那天之前的 7 天;p6-t3 评分项改为"按日期查询环境数据" - 没有 AI 建议时不显示"全部采纳 AI 建议"和相关说明;导出 CSV 只给纯数字的文字加制表符 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
da623fa648
commit
f6024dc113
29 files changed
+520
-238
No files matched your search
@@ -802,9 +802,10 @@ internal sealed class Translator
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>DATEADD(day, ...) 的第一个参数是关键字,解析成列名</summary>
|
||||
/// <summary>DATEADD(day, ...) 的第一个参数是关键字:ScriptDom 解析成 IdentifierLiteral(旧版本是列名)</summary>
|
||||
private static string DatePartName(ScalarExpression expression) => expression switch
|
||||
{
|
||||
IdentifierLiteral i => i.Value,
|
||||
ColumnReferenceExpression c => c.MultiPartIdentifier.Identifiers[^1].Value,
|
||||
StringLiteral s => s.Value,
|
||||
_ => throw SqlException.NotSupported("这种 datepart 写法"),
|
||||
|
||||
@@ -134,12 +134,54 @@ public static class SimHost
|
||||
public static void ReportException(Exception ex)
|
||||
{
|
||||
while (ex is TargetInvocationException { InnerException: not null } wrapper) ex = wrapper.InnerException!;
|
||||
Send(new JsonObject
|
||||
var type = ex.GetType().FullName;
|
||||
// 仿真库自己抛的异常信息已经是中文;.NET 自带的是英文,常见的几种配上中文说明,原文放在括号里
|
||||
var message = HasChinese(ex.Message) ? $"{ex.Message}({type})"
|
||||
: ExplainException(ex) is { } explained ? $"{explained}({type}: {ex.Message})"
|
||||
: $"{type}: {ex.Message}";
|
||||
var frame = StudentFrame(ex);
|
||||
var result = new JsonObject
|
||||
{
|
||||
["type"] = "exception",
|
||||
["message"] = $"{ex.GetType().FullName}: {ex.Message}",
|
||||
["detail"] = StudentStackTrace(ex),
|
||||
});
|
||||
["message"] = message,
|
||||
["detail"] = frame is { } f ? $"出错位置:{f.File} 第 {f.Line} 行({f.Method} 方法)" : StudentStackTrace(ex),
|
||||
};
|
||||
if (frame is { } at)
|
||||
{
|
||||
result["file"] = at.File;
|
||||
result["line"] = at.Line;
|
||||
}
|
||||
Send(result);
|
||||
}
|
||||
|
||||
private static bool HasChinese(string text) => text.Any(c => c >= '\u4e00' && c <= '\u9fff');
|
||||
|
||||
private static string? ExplainException(Exception ex) => ex switch
|
||||
{
|
||||
FormatException => "文字转换成数字或日期时格式不对,比如文本框是空的,或者里面不是数字",
|
||||
NullReferenceException => "用到的对象是 null:变量还没有赋值,或者对象、窗体还没有 new 出来",
|
||||
IndexOutOfRangeException => "数组的下标超出了范围:下标从 0 开始,最大是长度减 1",
|
||||
ArgumentOutOfRangeException => "下标或参数超出了范围,比如列表、字符串的下标太大,或者下拉框还没有选中",
|
||||
DivideByZeroException => "整数除以了 0",
|
||||
OverflowException => "数字太大或太小,超出了这个类型能表示的范围",
|
||||
InvalidCastException => "类型转换失败,这个对象不能转换成要求的类型",
|
||||
KeyNotFoundException => "字典里没有这个键",
|
||||
FileNotFoundException => "找不到这个文件",
|
||||
DirectoryNotFoundException => "找不到这个文件夹",
|
||||
_ => null,
|
||||
};
|
||||
|
||||
/// <summary>堆栈里离出错点最近的一行学生代码:文件名、行号、方法名</summary>
|
||||
private static (string File, int Line, string Method)? StudentFrame(Exception ex)
|
||||
{
|
||||
foreach (var frame in new System.Diagnostics.StackTrace(ex, true).GetFrames())
|
||||
{
|
||||
var file = frame.GetFileName();
|
||||
var line = frame.GetFileLineNumber();
|
||||
if (file == null || line <= 0 || file.Contains("/runtime/Sim.")) continue;
|
||||
return (Path.GetFileName(file), line, frame.GetMethod()?.Name ?? "");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>只保留学生代码的堆栈行,去掉仿真库内部的调用</summary>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 评分规则:书上评价表的功能项。每项单独启动一次程序(使用 database.sql 建的初始数据库),按步骤操作,核对结果。
|
||||
# 初始数据有 3 行:2024-01-16 09:00、2024-01-18 14:30、2024-01-23 08:15
|
||||
# 格式说明见 server/src/grading/checks.ts
|
||||
- title: 能实现按日期查询灾情数据
|
||||
- title: 能实现按日期查询环境数据
|
||||
steps:
|
||||
- pick: { control: StartTime, value: '2024-01-15' }
|
||||
- pick: { control: EndTime, value: '2024-01-20' }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
-- 实验的初始数据库 WisdomCampus(教材"综合实训"附带的库):环境数据表 EnvironmentData、报警图片表 SecurityData、用户表 Users。
|
||||
-- EnvironmentData 预置了 7 天的历史数据,"数据分析"的图表一打开就有内容;SecurityData 是空的,拍照或有人入侵时才有记录。
|
||||
-- EnvironmentData 预置了今天之前 7 天的历史数据(按建库那天往前推),"数据分析"的图表一打开就有最近一周的内容;SecurityData 是空的,拍照或有人入侵时才有记录。
|
||||
-- 学生可以在数据库页面"恢复实验初始数据";评分时每个检查项用一份新的。
|
||||
CREATE DATABASE WisdomCampus
|
||||
GO
|
||||
@@ -10,45 +10,45 @@ CREATE TABLE EnvironmentData (Id int IDENTITY(1,1) PRIMARY KEY, Fire bit NOT NUL
|
||||
CREATE TABLE SecurityData (Id int IDENTITY(1,1) PRIMARY KEY, EvenTime datetime NOT NULL, EvenImage image NOT NULL)
|
||||
GO
|
||||
INSERT INTO Users (UserName, Password) VALUES ('admin', '123')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.9, 58, 258, '2026-10-14 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.1, 53, 475, '2026-10-14 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.8, 51, 761, '2026-10-14 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 22.5, 49, 733, '2026-10-14 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 54, 528, '2026-10-14 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.1, 57, 283, '2026-10-14 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.9, 59, 261, '2026-10-15 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.6, 53, 499, '2026-10-15 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.8, 52, 764, '2026-10-15 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 23.5, 47, 778, '2026-10-15 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.8, 52, 503, '2026-10-15 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.6, 56, 218, '2026-10-15 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 13.9, 63, 258, '2026-10-16 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.6, 60, 477, '2026-10-16 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.8, 51, 741, '2026-10-16 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 20.5, 54, 796, '2026-10-16 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 55, 488, '2026-10-16 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.6, 59, 214, '2026-10-16 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 11.9, 67, 234, '2026-10-17 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.1, 63, 544, '2026-10-17 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.3, 60, 804, '2026-10-17 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.5, 54, 801, '2026-10-17 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.8, 59, 524, '2026-10-17 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.1, 58, 286, '2026-10-17 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.4, 59, 250, '2026-10-18 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.6, 55, 533, '2026-10-18 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.3, 50, 749, '2026-10-18 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 24, 48, 766, '2026-10-18 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 20.8, 54, 534, '2026-10-18 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.1, 53, 241, '2026-10-18 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.4, 54, 253, '2026-10-19 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.1, 50, 474, '2026-10-19 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 22.3, 48, 778, '2026-10-19 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 26, 46, 783, '2026-10-19 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 23.3, 50, 520, '2026-10-19 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 20.6, 50, 213, '2026-10-19 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.9, 62, 230, '2026-10-20 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.6, 52, 471, '2026-10-20 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 53, 751, '2026-10-20 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.5, 48, 747, '2026-10-20 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 55, 527, '2026-10-20 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.1, 57, 220, '2026-10-20 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.9, 58, 258, DATEADD(hour, 8, DATEADD(day, -7, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.1, 53, 475, DATEADD(hour, 10, DATEADD(day, -7, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.8, 51, 761, DATEADD(hour, 12, DATEADD(day, -7, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 22.5, 49, 733, DATEADD(hour, 14, DATEADD(day, -7, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 54, 528, DATEADD(hour, 16, DATEADD(day, -7, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.1, 57, 283, DATEADD(hour, 18, DATEADD(day, -7, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.9, 59, 261, DATEADD(hour, 8, DATEADD(day, -6, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.6, 53, 499, DATEADD(hour, 10, DATEADD(day, -6, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.8, 52, 764, DATEADD(hour, 12, DATEADD(day, -6, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 23.5, 47, 778, DATEADD(hour, 14, DATEADD(day, -6, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.8, 52, 503, DATEADD(hour, 16, DATEADD(day, -6, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.6, 56, 218, DATEADD(hour, 18, DATEADD(day, -6, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 13.9, 63, 258, DATEADD(hour, 8, DATEADD(day, -5, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.6, 60, 477, DATEADD(hour, 10, DATEADD(day, -5, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.8, 51, 741, DATEADD(hour, 12, DATEADD(day, -5, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 20.5, 54, 796, DATEADD(hour, 14, DATEADD(day, -5, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 55, 488, DATEADD(hour, 16, DATEADD(day, -5, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.6, 59, 214, DATEADD(hour, 18, DATEADD(day, -5, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 11.9, 67, 234, DATEADD(hour, 8, DATEADD(day, -4, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.1, 63, 544, DATEADD(hour, 10, DATEADD(day, -4, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.3, 60, 804, DATEADD(hour, 12, DATEADD(day, -4, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.5, 54, 801, DATEADD(hour, 14, DATEADD(day, -4, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.8, 59, 524, DATEADD(hour, 16, DATEADD(day, -4, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.1, 58, 286, DATEADD(hour, 18, DATEADD(day, -4, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.4, 59, 250, DATEADD(hour, 8, DATEADD(day, -3, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.6, 55, 533, DATEADD(hour, 10, DATEADD(day, -3, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.3, 50, 749, DATEADD(hour, 12, DATEADD(day, -3, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 24, 48, 766, DATEADD(hour, 14, DATEADD(day, -3, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 20.8, 54, 534, DATEADD(hour, 16, DATEADD(day, -3, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.1, 53, 241, DATEADD(hour, 18, DATEADD(day, -3, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.4, 54, 253, DATEADD(hour, 8, DATEADD(day, -2, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.1, 50, 474, DATEADD(hour, 10, DATEADD(day, -2, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 22.3, 48, 778, DATEADD(hour, 12, DATEADD(day, -2, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 26, 46, 783, DATEADD(hour, 14, DATEADD(day, -2, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 23.3, 50, 520, DATEADD(hour, 16, DATEADD(day, -2, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 20.6, 50, 213, DATEADD(hour, 18, DATEADD(day, -2, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.9, 62, 230, DATEADD(hour, 8, DATEADD(day, -1, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.6, 52, 471, DATEADD(hour, 10, DATEADD(day, -1, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 53, 751, DATEADD(hour, 12, DATEADD(day, -1, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.5, 48, 747, DATEADD(hour, 14, DATEADD(day, -1, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 55, 527, DATEADD(hour, 16, DATEADD(day, -1, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.1, 57, 220, DATEADD(hour, 18, DATEADD(day, -1, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
@@ -1,5 +1,5 @@
|
||||
-- 实验的初始数据库 WisdomCampus(教材"综合实训"附带的库):环境数据表 EnvironmentData、报警图片表 SecurityData、用户表 Users。
|
||||
-- EnvironmentData 预置了 7 天的历史数据,"数据分析"的图表一打开就有内容;SecurityData 是空的,拍照或有人入侵时才有记录。
|
||||
-- EnvironmentData 预置了今天之前 7 天的历史数据(按建库那天往前推),"数据分析"的图表一打开就有最近一周的内容;SecurityData 是空的,拍照或有人入侵时才有记录。
|
||||
-- 学生可以在数据库页面"恢复实验初始数据";评分时每个检查项用一份新的。
|
||||
CREATE DATABASE WisdomCampus
|
||||
GO
|
||||
@@ -10,45 +10,45 @@ CREATE TABLE EnvironmentData (Id int IDENTITY(1,1) PRIMARY KEY, Fire bit NOT NUL
|
||||
CREATE TABLE SecurityData (Id int IDENTITY(1,1) PRIMARY KEY, EvenTime datetime NOT NULL, EvenImage image NOT NULL)
|
||||
GO
|
||||
INSERT INTO Users (UserName, Password) VALUES ('admin', '123')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.9, 58, 258, '2026-10-14 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.1, 53, 475, '2026-10-14 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.8, 51, 761, '2026-10-14 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 22.5, 49, 733, '2026-10-14 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 54, 528, '2026-10-14 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.1, 57, 283, '2026-10-14 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.9, 59, 261, '2026-10-15 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.6, 53, 499, '2026-10-15 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.8, 52, 764, '2026-10-15 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 23.5, 47, 778, '2026-10-15 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.8, 52, 503, '2026-10-15 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.6, 56, 218, '2026-10-15 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 13.9, 63, 258, '2026-10-16 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.6, 60, 477, '2026-10-16 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.8, 51, 741, '2026-10-16 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 20.5, 54, 796, '2026-10-16 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 55, 488, '2026-10-16 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.6, 59, 214, '2026-10-16 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 11.9, 67, 234, '2026-10-17 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.1, 63, 544, '2026-10-17 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.3, 60, 804, '2026-10-17 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.5, 54, 801, '2026-10-17 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.8, 59, 524, '2026-10-17 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.1, 58, 286, '2026-10-17 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.4, 59, 250, '2026-10-18 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.6, 55, 533, '2026-10-18 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.3, 50, 749, '2026-10-18 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 24, 48, 766, '2026-10-18 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 20.8, 54, 534, '2026-10-18 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.1, 53, 241, '2026-10-18 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.4, 54, 253, '2026-10-19 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.1, 50, 474, '2026-10-19 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 22.3, 48, 778, '2026-10-19 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 26, 46, 783, '2026-10-19 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 23.3, 50, 520, '2026-10-19 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 20.6, 50, 213, '2026-10-19 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.9, 62, 230, '2026-10-20 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.6, 52, 471, '2026-10-20 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 53, 751, '2026-10-20 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.5, 48, 747, '2026-10-20 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 55, 527, '2026-10-20 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.1, 57, 220, '2026-10-20 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.9, 58, 258, DATEADD(hour, 8, DATEADD(day, -7, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.1, 53, 475, DATEADD(hour, 10, DATEADD(day, -7, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.8, 51, 761, DATEADD(hour, 12, DATEADD(day, -7, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 22.5, 49, 733, DATEADD(hour, 14, DATEADD(day, -7, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 54, 528, DATEADD(hour, 16, DATEADD(day, -7, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.1, 57, 283, DATEADD(hour, 18, DATEADD(day, -7, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.9, 59, 261, DATEADD(hour, 8, DATEADD(day, -6, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.6, 53, 499, DATEADD(hour, 10, DATEADD(day, -6, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.8, 52, 764, DATEADD(hour, 12, DATEADD(day, -6, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 23.5, 47, 778, DATEADD(hour, 14, DATEADD(day, -6, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.8, 52, 503, DATEADD(hour, 16, DATEADD(day, -6, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.6, 56, 218, DATEADD(hour, 18, DATEADD(day, -6, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 13.9, 63, 258, DATEADD(hour, 8, DATEADD(day, -5, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.6, 60, 477, DATEADD(hour, 10, DATEADD(day, -5, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.8, 51, 741, DATEADD(hour, 12, DATEADD(day, -5, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 20.5, 54, 796, DATEADD(hour, 14, DATEADD(day, -5, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 55, 488, DATEADD(hour, 16, DATEADD(day, -5, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.6, 59, 214, DATEADD(hour, 18, DATEADD(day, -5, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 11.9, 67, 234, DATEADD(hour, 8, DATEADD(day, -4, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.1, 63, 544, DATEADD(hour, 10, DATEADD(day, -4, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.3, 60, 804, DATEADD(hour, 12, DATEADD(day, -4, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.5, 54, 801, DATEADD(hour, 14, DATEADD(day, -4, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.8, 59, 524, DATEADD(hour, 16, DATEADD(day, -4, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.1, 58, 286, DATEADD(hour, 18, DATEADD(day, -4, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.4, 59, 250, DATEADD(hour, 8, DATEADD(day, -3, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.6, 55, 533, DATEADD(hour, 10, DATEADD(day, -3, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.3, 50, 749, DATEADD(hour, 12, DATEADD(day, -3, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 24, 48, 766, DATEADD(hour, 14, DATEADD(day, -3, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 20.8, 54, 534, DATEADD(hour, 16, DATEADD(day, -3, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.1, 53, 241, DATEADD(hour, 18, DATEADD(day, -3, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.4, 54, 253, DATEADD(hour, 8, DATEADD(day, -2, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.1, 50, 474, DATEADD(hour, 10, DATEADD(day, -2, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 22.3, 48, 778, DATEADD(hour, 12, DATEADD(day, -2, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 26, 46, 783, DATEADD(hour, 14, DATEADD(day, -2, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 23.3, 50, 520, DATEADD(hour, 16, DATEADD(day, -2, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 20.6, 50, 213, DATEADD(hour, 18, DATEADD(day, -2, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.9, 62, 230, DATEADD(hour, 8, DATEADD(day, -1, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.6, 52, 471, DATEADD(hour, 10, DATEADD(day, -1, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 53, 751, DATEADD(hour, 12, DATEADD(day, -1, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.5, 48, 747, DATEADD(hour, 14, DATEADD(day, -1, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 55, 527, DATEADD(hour, 16, DATEADD(day, -1, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.1, 57, 220, DATEADD(hour, 18, DATEADD(day, -1, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
@@ -1,5 +1,5 @@
|
||||
-- 实验的初始数据库 WisdomCampus(教材"综合实训"附带的库):环境数据表 EnvironmentData、报警图片表 SecurityData、用户表 Users。
|
||||
-- EnvironmentData 预置了 7 天的历史数据,"数据分析"的图表一打开就有内容;SecurityData 是空的,拍照或有人入侵时才有记录。
|
||||
-- EnvironmentData 预置了今天之前 7 天的历史数据(按建库那天往前推),"数据分析"的图表一打开就有最近一周的内容;SecurityData 是空的,拍照或有人入侵时才有记录。
|
||||
-- 学生可以在数据库页面"恢复实验初始数据";评分时每个检查项用一份新的。
|
||||
CREATE DATABASE WisdomCampus
|
||||
GO
|
||||
@@ -10,45 +10,45 @@ CREATE TABLE EnvironmentData (Id int IDENTITY(1,1) PRIMARY KEY, Fire bit NOT NUL
|
||||
CREATE TABLE SecurityData (Id int IDENTITY(1,1) PRIMARY KEY, EvenTime datetime NOT NULL, EvenImage image NOT NULL)
|
||||
GO
|
||||
INSERT INTO Users (UserName, Password) VALUES ('admin', '123')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.9, 58, 258, '2026-10-14 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.1, 53, 475, '2026-10-14 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.8, 51, 761, '2026-10-14 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 22.5, 49, 733, '2026-10-14 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 54, 528, '2026-10-14 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.1, 57, 283, '2026-10-14 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.9, 59, 261, '2026-10-15 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.6, 53, 499, '2026-10-15 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.8, 52, 764, '2026-10-15 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 23.5, 47, 778, '2026-10-15 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.8, 52, 503, '2026-10-15 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.6, 56, 218, '2026-10-15 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 13.9, 63, 258, '2026-10-16 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.6, 60, 477, '2026-10-16 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.8, 51, 741, '2026-10-16 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 20.5, 54, 796, '2026-10-16 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 55, 488, '2026-10-16 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.6, 59, 214, '2026-10-16 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 11.9, 67, 234, '2026-10-17 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.1, 63, 544, '2026-10-17 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.3, 60, 804, '2026-10-17 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.5, 54, 801, '2026-10-17 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.8, 59, 524, '2026-10-17 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.1, 58, 286, '2026-10-17 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.4, 59, 250, '2026-10-18 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.6, 55, 533, '2026-10-18 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.3, 50, 749, '2026-10-18 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 24, 48, 766, '2026-10-18 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 20.8, 54, 534, '2026-10-18 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.1, 53, 241, '2026-10-18 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.4, 54, 253, '2026-10-19 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.1, 50, 474, '2026-10-19 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 22.3, 48, 778, '2026-10-19 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 26, 46, 783, '2026-10-19 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 23.3, 50, 520, '2026-10-19 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 20.6, 50, 213, '2026-10-19 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.9, 62, 230, '2026-10-20 08:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.6, 52, 471, '2026-10-20 10:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 53, 751, '2026-10-20 12:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.5, 48, 747, '2026-10-20 14:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 55, 527, '2026-10-20 16:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.1, 57, 220, '2026-10-20 18:00:00')
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.9, 58, 258, DATEADD(hour, 8, DATEADD(day, -7, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.1, 53, 475, DATEADD(hour, 10, DATEADD(day, -7, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.8, 51, 761, DATEADD(hour, 12, DATEADD(day, -7, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 22.5, 49, 733, DATEADD(hour, 14, DATEADD(day, -7, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 54, 528, DATEADD(hour, 16, DATEADD(day, -7, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.1, 57, 283, DATEADD(hour, 18, DATEADD(day, -7, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.9, 59, 261, DATEADD(hour, 8, DATEADD(day, -6, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.6, 53, 499, DATEADD(hour, 10, DATEADD(day, -6, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.8, 52, 764, DATEADD(hour, 12, DATEADD(day, -6, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 23.5, 47, 778, DATEADD(hour, 14, DATEADD(day, -6, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.8, 52, 503, DATEADD(hour, 16, DATEADD(day, -6, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.6, 56, 218, DATEADD(hour, 18, DATEADD(day, -6, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 13.9, 63, 258, DATEADD(hour, 8, DATEADD(day, -5, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.6, 60, 477, DATEADD(hour, 10, DATEADD(day, -5, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.8, 51, 741, DATEADD(hour, 12, DATEADD(day, -5, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 20.5, 54, 796, DATEADD(hour, 14, DATEADD(day, -5, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 55, 488, DATEADD(hour, 16, DATEADD(day, -5, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.6, 59, 214, DATEADD(hour, 18, DATEADD(day, -5, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 11.9, 67, 234, DATEADD(hour, 8, DATEADD(day, -4, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.1, 63, 544, DATEADD(hour, 10, DATEADD(day, -4, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.3, 60, 804, DATEADD(hour, 12, DATEADD(day, -4, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.5, 54, 801, DATEADD(hour, 14, DATEADD(day, -4, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.8, 59, 524, DATEADD(hour, 16, DATEADD(day, -4, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.1, 58, 286, DATEADD(hour, 18, DATEADD(day, -4, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 16.4, 59, 250, DATEADD(hour, 8, DATEADD(day, -3, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.6, 55, 533, DATEADD(hour, 10, DATEADD(day, -3, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.3, 50, 749, DATEADD(hour, 12, DATEADD(day, -3, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 24, 48, 766, DATEADD(hour, 14, DATEADD(day, -3, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 20.8, 54, 534, DATEADD(hour, 16, DATEADD(day, -3, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.1, 53, 241, DATEADD(hour, 18, DATEADD(day, -3, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.4, 54, 253, DATEADD(hour, 8, DATEADD(day, -2, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.1, 50, 474, DATEADD(hour, 10, DATEADD(day, -2, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 22.3, 48, 778, DATEADD(hour, 12, DATEADD(day, -2, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 26, 46, 783, DATEADD(hour, 14, DATEADD(day, -2, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 23.3, 50, 520, DATEADD(hour, 16, DATEADD(day, -2, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 20.6, 50, 213, DATEADD(hour, 18, DATEADD(day, -2, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 15.9, 62, 230, DATEADD(hour, 8, DATEADD(day, -1, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 18.6, 52, 471, DATEADD(hour, 10, DATEADD(day, -1, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 53, 751, DATEADD(hour, 12, DATEADD(day, -1, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 21.5, 48, 747, DATEADD(hour, 14, DATEADD(day, -1, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 19.3, 55, 527, DATEADD(hour, 16, DATEADD(day, -1, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (0, 0, 17.1, 57, 220, DATEADD(hour, 18, DATEADD(day, -1, CONVERT(varchar(10), GETDATE(), 120))))
|
||||
@@ -806,3 +806,44 @@ test.skipIf(config.sandbox === 'none')(
|
||||
},
|
||||
30000,
|
||||
)
|
||||
|
||||
test('运行时异常:常见的 .NET 异常配中文说明,带出错的文件、行号和方法', async () => {
|
||||
const { runner } = await runMain(
|
||||
'exception',
|
||||
`
|
||||
Console.WriteLine("start");
|
||||
int n = int.Parse("abc");`,
|
||||
)
|
||||
const error = await runner.next('exception')
|
||||
expect(error.message).toStartWith('文字转换成数字或日期时格式不对')
|
||||
expect(error.message).toContain('System.FormatException')
|
||||
expect(error).toMatchObject({ file: 'Program.cs', line: 11 })
|
||||
expect(error.detail).toBe('出错位置:Program.cs 第 11 行(Main 方法)')
|
||||
}, 30000)
|
||||
|
||||
test('数据库:DATEADD、DATEDIFF、DATEPART 的日期单位写成关键字(day、hour)', async () => {
|
||||
const dbDir = resolve(ROOT, '.runs', 'test-dateadd-db')
|
||||
await rm(dbDir, { recursive: true, force: true })
|
||||
await mkdir(dbDir, { recursive: true })
|
||||
const { runner } = await runMain(
|
||||
'dateadd',
|
||||
`
|
||||
var master = new SqlConnection("Data Source=.;User ID=sa");
|
||||
master.Open();
|
||||
new SqlCommand("CREATE DATABASE T", master).ExecuteNonQuery();
|
||||
var conn = new SqlConnection("Data Source=.;Initial Catalog=T");
|
||||
conn.Open();
|
||||
foreach (var sql in new[] {
|
||||
"SELECT DATEADD(hour, 8, DATEADD(day, -7, '2024-01-16'))",
|
||||
"SELECT DATEDIFF(day, '2024-01-01 23:00', '2024-01-02 01:00')",
|
||||
"SELECT DATEPART(hh, '2024-01-16 09:05:03')",
|
||||
})
|
||||
Console.WriteLine("result=" + new SqlCommand(sql, conn).ExecuteScalar());`,
|
||||
'using System.Data.SqlClient;',
|
||||
dbDir,
|
||||
)
|
||||
await runner.finished()
|
||||
const output = runner.messages.flatMap((m) => (m.type === 'console' ? [m.text] : [])).join('')
|
||||
expect(runner.messages.filter((m) => m.type === 'exception')).toEqual([])
|
||||
expect(output).toBe('result=2024/1/9 8:00:00\nresult=1\nresult=9\n')
|
||||
}, 30000)
|
||||
+2
-1
@@ -120,7 +120,8 @@ export type RunnerMessage =
|
||||
/** 学生程序操作了实训台输出:led/text、adam4150/do、zigbee/relay、camera/on、camera/pan,data 见 DeviceCtrlHelper.cs、IPCameraHelper.cs */
|
||||
| { type: 'device'; device: string; action: string; data: unknown }
|
||||
| { type: 'console'; text: string }
|
||||
| { type: 'exception'; message: string; detail: string }
|
||||
/** 学生程序没有处理的异常;file、line 是出错的那行学生代码(找得到时才有),网页上点击可以跳过去 */
|
||||
| { type: 'exception'; message: string; detail: string; file?: string; line?: number }
|
||||
| { type: 'exited'; code: number }
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,6 +26,7 @@ export async function login(username: string, password: string) {
|
||||
const result = await api<Tokens & { user: User }>('POST', '/api/login', { username, password })
|
||||
setTokens(result)
|
||||
currentUser.value = result.user
|
||||
return result.user
|
||||
}
|
||||
|
||||
export async function logout() {
|
||||
|
||||
@@ -373,15 +373,24 @@ function onDrop(event: DragEvent) {
|
||||
place(d, controlDef(type), x, y)
|
||||
}
|
||||
|
||||
/** 双击工具箱:放在选中的容器(或窗体)左上角 */
|
||||
/** 双击工具箱:放在选中的容器(选中的是普通控件时放进它所在的容器)左上角,已有控件在那里就往右下错开 */
|
||||
function onToolDoubleClick(def: ControlDef) {
|
||||
const d = parsed.value.doc
|
||||
if (!d) return
|
||||
// 直接放进选中的容器,不按坐标判断(左上角可能正好压在另一个分组框上)
|
||||
const container = !def.component && selected.value && isContainerType(typeOf(d, selected.value) ?? '') ? selected.value : ''
|
||||
const result = addControl(d, def, container, { x: 12, y: container ? 20 : 12 })
|
||||
// 直接放进容器,不按坐标判断(左上角可能正好压在另一个分组框上)
|
||||
let container = ''
|
||||
if (!def.component && selected.value) {
|
||||
container = isContainerType(typeOf(d, selected.value) ?? '') ? selected.value : (listControls(d).find((c) => c.name === selected.value)?.parent ?? '')
|
||||
}
|
||||
const location = { x: 12, y: container ? 20 : 12 }
|
||||
const taken = childrenOf(d, container).map((child) => localRect(d, child))
|
||||
while (taken.some((r) => r.x === location.x && r.y === location.y)) {
|
||||
location.x += 12
|
||||
location.y += 12
|
||||
}
|
||||
const result = addControl(d, def, container, location)
|
||||
selected.value = result.name
|
||||
commit(result.doc)
|
||||
commit(fitInside(result.doc, result.name, container))
|
||||
}
|
||||
|
||||
function place(d: DesignerDocument, def: ControlDef, x: number, y: number) {
|
||||
@@ -389,7 +398,15 @@ function place(d: DesignerDocument, def: ControlDef, x: number, y: number) {
|
||||
const origin = container ? absRect(d, container) : { x: 0, y: 0 }
|
||||
const result = addControl(d, def, container, { x: x - origin.x, y: y - origin.y })
|
||||
selected.value = result.name
|
||||
commit(result.doc)
|
||||
commit(def.component ? result.doc : fitInside(result.doc, result.name, container))
|
||||
}
|
||||
|
||||
/** 新控件比容器宽(LED 显示屏默认 800 宽,新窗体只有 300):按比例缩小到右边留出边距,不用学生再去改大小 */
|
||||
function fitInside(d: DesignerDocument, name: string, container: string): DesignerDocument {
|
||||
const rect = localRect(d, name)
|
||||
const room = (container ? localRect(d, container).width : clientSize.value.width) - rect.x - 12
|
||||
if (rect.width <= room || room < 40) return d
|
||||
return setProp(d, name, 'Size', { kind: 'size', width: room, height: Math.max(20, Math.round((rect.height * room) / rect.width)) })
|
||||
}
|
||||
|
||||
// ---------- 属性窗口的回调 ----------
|
||||
@@ -469,6 +486,7 @@ function onComponentDoubleClick(name: string) {
|
||||
}
|
||||
|
||||
const formTitle = computed(() => String(tree.value?.props.Text ?? ''))
|
||||
const borderless = computed(() => tree.value?.props.FormBorderStyle === 'None')
|
||||
const formStyle = computed(() => ({
|
||||
width: `${clientSize.value.width}px`,
|
||||
height: `${clientSize.value.height}px`,
|
||||
@@ -511,7 +529,9 @@ const noop = () => {}
|
||||
<div class="actions">
|
||||
<button :disabled="!canUndo" title="撤销(Ctrl+Z)" @click="emit('undo')">↶ 撤销</button>
|
||||
<button :disabled="!canRedo" title="重做(Ctrl+Y)" @click="emit('redo')">↷ 重做</button>
|
||||
<span class="tips">拖动工具箱里的控件到窗体上;双击控件生成事件处理方法;Delete 删除</span>
|
||||
<span class="tips" title="拖动工具箱里的控件到窗体上;双击控件生成事件处理方法;Delete 删除"
|
||||
>拖动工具箱里的控件到窗体上;双击控件生成事件处理方法;Delete 删除</span
|
||||
>
|
||||
<select
|
||||
class="zoom"
|
||||
title="窗体显示的大小(不影响窗体的实际大小)"
|
||||
@@ -524,8 +544,9 @@ const noop = () => {}
|
||||
</select>
|
||||
<button class="view-code" title="设计器根据窗体上的控件自动生成的代码(Designer.cs)" @click="emit('openDesignerFile')">查看生成的代码</button>
|
||||
</div>
|
||||
<div class="window" :class="{ selected: selected === '' }" :style="zoom !== 1 ? { zoom } : undefined">
|
||||
<div class="titlebar" @pointerdown="selected = ''">
|
||||
<div class="window" :class="{ selected: selected === '', borderless }" :style="zoom !== 1 ? { zoom } : undefined">
|
||||
<!-- 无边框窗体和 VS 设计器里一样不画标题栏,点窗体空白处选中窗体 -->
|
||||
<div v-if="!borderless" class="titlebar" @pointerdown="selected = ''">
|
||||
<span class="title">{{ displayText(formTitle) }}</span>
|
||||
<span class="buttons">─ ☐ ✕</span>
|
||||
</div>
|
||||
@@ -707,6 +728,10 @@ const noop = () => {}
|
||||
box-shadow: 0 2px 10px rgb(0 0 0 / 0.15);
|
||||
}
|
||||
|
||||
.window.borderless {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.window.selected {
|
||||
outline: 1px dashed #2563eb;
|
||||
outline-offset: 2px;
|
||||
|
||||
@@ -7,7 +7,7 @@ import ChartDialog from './ChartDialog.vue'
|
||||
import type { DesignerDocument } from './document'
|
||||
import { checkName, getEventHandler, getItems, getPropText, typeOf } from './edit'
|
||||
import { resourceNameOf } from './resources'
|
||||
import { NAMED_COLORS, hexToRgb, parseValue, rgb, type Value } from './values'
|
||||
import { COLOR_LABELS, NAMED_COLORS, hexToRgb, parseValue, rgb, type Value } from './values'
|
||||
|
||||
const props = defineProps<{
|
||||
doc: DesignerDocument
|
||||
@@ -289,7 +289,7 @@ function renameHandler(event: string, value: string) {
|
||||
<i class="swatch" :style="{ background: color(prop)?.css ?? 'transparent' }" />
|
||||
<select :value="color(prop)?.name ?? (color(prop) ? 'custom' : '')" @change="setColor(prop, ($event.target as HTMLSelectElement).value)">
|
||||
<option value="">(默认)</option>
|
||||
<option v-for="(_, name) in NAMED_COLORS" :key="name" :value="name">{{ name }}</option>
|
||||
<option v-for="(_, name) in NAMED_COLORS" :key="name" :value="name" :title="name">{{ COLOR_LABELS[name] ?? name }}</option>
|
||||
<option v-if="color(prop) && !color(prop)!.name" value="custom" disabled>{{ color(prop)!.css }}</option>
|
||||
</select>
|
||||
<input
|
||||
|
||||
@@ -20,7 +20,9 @@ export function ensureHandler(code: string, className: string, handler: string,
|
||||
// 类里已有成员时,与上一个成员之间空一行
|
||||
const before = code.slice(0, lineStart)
|
||||
const hasMembers = !/\{\s*$/.test(before)
|
||||
const method = [...(hasMembers ? [''] : []), `${indent}private void ${handler}(object sender, ${args} e)`, `${indent}{`, '', `${indent}}`].join(eol)
|
||||
// 方法体的空行带好缩进(和 VS 一样),光标跳过去就在缩进后面,学生的代码不会顶格写
|
||||
const body = `${indent} `
|
||||
const method = [...(hasMembers ? [''] : []), `${indent}private void ${handler}(object sender, ${args} e)`, `${indent}{`, body, `${indent}}`].join(eol)
|
||||
const next = `${before}${method}${eol}${code.slice(lineStart)}`
|
||||
return { code: next, line: lineOf(next, before.length) + (hasMembers ? 3 : 2) }
|
||||
}
|
||||
|
||||
@@ -134,6 +134,30 @@ export const NAMED_COLORS: Record<string, string> = {
|
||||
Cyan: '#00ffff',
|
||||
}
|
||||
|
||||
/** 属性窗口里颜色的中文名,英文名放在悬停提示里 */
|
||||
export const COLOR_LABELS: Record<string, string> = {
|
||||
Transparent: '透明',
|
||||
Black: '黑色',
|
||||
White: '白色',
|
||||
Red: '红色',
|
||||
Green: '绿色',
|
||||
Blue: '蓝色',
|
||||
Yellow: '黄色',
|
||||
Orange: '橙色',
|
||||
Gray: '灰色',
|
||||
DimGray: '暗灰色',
|
||||
Silver: '银色',
|
||||
LightGray: '浅灰色',
|
||||
DarkBlue: '深蓝色',
|
||||
SkyBlue: '天蓝色',
|
||||
LightGreen: '浅绿色',
|
||||
Lime: '亮绿色',
|
||||
Purple: '紫色',
|
||||
Brown: '棕色',
|
||||
Pink: '粉红色',
|
||||
Cyan: '青色',
|
||||
}
|
||||
|
||||
/** 系统颜色(System.Drawing.SystemColors),按 Windows 10 默认主题取值 */
|
||||
const SYSTEM_COLORS: Record<string, string> = {
|
||||
Control: '#f0f0f0',
|
||||
|
||||
@@ -124,6 +124,8 @@ onMounted(() => {
|
||||
tabSize: 4,
|
||||
insertSpaces: true,
|
||||
minimap: { enabled: false },
|
||||
// 顶部不固定显示所在的类和方法:点报错跳到文件开头附近的行时,那一行会被它挡住
|
||||
stickyScroll: { enabled: false },
|
||||
scrollBeyondLastLine: false,
|
||||
renderWhitespace: 'selection',
|
||||
unicodeHighlight: { ambiguousCharacters: false },
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// 按需加载 Monaco(0.57 的模块布局):编辑器 API + 全部编辑功能(查找、折叠、括号匹配等)+ C# 和 SQL 语法高亮,
|
||||
// 不加载其他几十种语言和 JS/TS/CSS 语言服务,体积小很多。
|
||||
// 中文界面(查找替换、右键菜单、提示文字):必须在编辑器模块之前加载,编辑器模块加载时就会取界面文字
|
||||
import 'monaco-editor/nls/lang/zh-cn'
|
||||
import * as monaco from 'monaco-editor/editor/editor.api'
|
||||
import 'monaco-editor/features/register.all'
|
||||
import 'monaco-editor/languages/definitions/csharp/register'
|
||||
|
||||
@@ -77,6 +77,7 @@ const formatTime = (ms: number) => new Date(ms).toLocaleTimeString('zh-CN', { ho
|
||||
:show-mask="false"
|
||||
:trap-focus="false"
|
||||
:block-scroll="false"
|
||||
class="task-drawer"
|
||||
@update:show="emit('update:show', $event)"
|
||||
>
|
||||
<NDrawerContent title="任务" closable>
|
||||
@@ -117,6 +118,13 @@ const formatTime = (ms: number) => new Date(ms).toLocaleTimeString('zh-CN', { ho
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* 从顶部工具栏下面开始,不挡住"我的作业""数据库""退出"等链接 */
|
||||
.n-drawer.task-drawer {
|
||||
top: 49px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.task {
|
||||
font-size: 14px;
|
||||
|
||||
@@ -10,7 +10,7 @@ import EditTab from '../database/EditTab.vue'
|
||||
import ObjectExplorer, { type ExplorerAction } from '../database/ObjectExplorer.vue'
|
||||
import QueryTab from '../database/QueryTab.vue'
|
||||
import { execute, loadExplorer, quoteName, type ExplorerDatabase, type ExplorerTable } from '../database/sql'
|
||||
import { renderSampleTag, sampleOptions } from '../samples'
|
||||
import { lastSample, renderSampleTag, sampleOptions } from '../samples'
|
||||
|
||||
type Tab =
|
||||
| { key: string; kind: 'query'; title: string; database: string; sql?: string; run?: boolean }
|
||||
@@ -148,7 +148,8 @@ const restoreBusy = ref(false)
|
||||
|
||||
async function openRestore() {
|
||||
experiments.value = await api('GET', '/api/db/experiments')
|
||||
restoring.value = experiments.value[0]?.id ?? null
|
||||
// 默认选刚才做的实验
|
||||
restoring.value = experiments.value.find((e) => e.id === lastSample())?.id ?? experiments.value[0]?.id ?? null
|
||||
}
|
||||
|
||||
async function restore() {
|
||||
|
||||
@@ -28,8 +28,8 @@ import { currentUser } from '../auth'
|
||||
|
||||
<h3>2. 自由练习和做作业</h3>
|
||||
<ul>
|
||||
<li>登录后进入<b>自由练习</b>:左上角的下拉框选教材上的任务,可以随便改、随便运行。</li>
|
||||
<li>点右上角的<b>我的作业</b>,看老师布置的作业、截止时间和得分;点作业名开始做。</li>
|
||||
<li>登录后先看到<b>我的作业</b>:老师布置的作业、截止时间和得分;点作业名开始做。做实验时随时可以点右上角的<b>我的作业</b>回来。</li>
|
||||
<li>点<b>自由练习</b>:左上角的下拉框选教材上的任务,可以随便改、随便运行;下次打开时回到上次做的任务。</li>
|
||||
<li>工具栏的<b>任务</b>:任务目标、控件名、实现步骤和在平台上怎么试,第一次打开一个实验时自动弹出。面板不挡住设计器和代码,可以开着做。</li>
|
||||
<li>
|
||||
作业的起点有两种:<b>界面已做好</b>(只写事件和代码)、<b>空白工程</b>(从搭界面做起)。控件名照书上的写,自动评分按书上的控件名操作你的程序。
|
||||
|
||||
@@ -17,8 +17,10 @@ async function submit() {
|
||||
busy.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
await login(username.value.trim(), password.value)
|
||||
const redirect = typeof route.query.redirect === 'string' && route.query.redirect.startsWith('/') ? route.query.redirect : '/'
|
||||
const user = await login(username.value.trim(), password.value)
|
||||
// 学生登录后先看作业列表,老师进实验页
|
||||
const fallback = user.role === 'student' ? '/assignments' : '/'
|
||||
const redirect = typeof route.query.redirect === 'string' && route.query.redirect.startsWith('/') ? route.query.redirect : fallback
|
||||
await router.replace(redirect)
|
||||
} catch (e) {
|
||||
error.value = (e as Error).message
|
||||
|
||||
@@ -15,7 +15,7 @@ import RunArea from '../run/RunArea.vue'
|
||||
import { createRunSession } from '../run/session'
|
||||
import GradeResultView from '../grading/GradeResultView.vue'
|
||||
import { conventionHints } from '@iot/shared'
|
||||
import { downloadCsv } from '../teacher/csv'
|
||||
import { downloadCsv, textCell } from '../teacher/csv'
|
||||
|
||||
interface Row {
|
||||
userId: number
|
||||
@@ -270,7 +270,7 @@ function exportCsv() {
|
||||
'评语',
|
||||
],
|
||||
...rows.value.map((r) => [
|
||||
`\t${r.username}`,
|
||||
textCell(r.username),
|
||||
r.status ? (r.autoScore ?? '') : '未提交',
|
||||
...a.scoring.manual.map((_, i) => r.review?.scores[i] ?? ''),
|
||||
r.status ? (r.autoScore ?? 0) + (r.manualScore ?? 0) : '',
|
||||
@@ -293,7 +293,8 @@ async function signOut() {
|
||||
<strong>{{ assignment?.title }}</strong>
|
||||
<span class="muted">{{ assignment?.className }}</span>
|
||||
<span class="muted">已提交 {{ rows.filter((r) => r.status).length }} / {{ rows.length }} 人</span>
|
||||
<NButton size="small" @click="adoptAll">全部采纳 AI 建议</NButton>
|
||||
<!-- 没配置 AI 评阅时不会有 AI 建议,不显示这个按钮 -->
|
||||
<NButton v-if="rows.some((r) => r.aiStatus)" size="small" @click="adoptAll">全部采纳 AI 建议</NButton>
|
||||
<NButton size="small" @click="exportCsv">导出成绩</NButton>
|
||||
<span class="spacer" />
|
||||
<span class="user">{{ currentUser?.name }}</span>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { api } from '../api'
|
||||
import { currentUser, logout } from '../auth'
|
||||
import AssignmentsPanel from '../teacher/AssignmentsPanel.vue'
|
||||
import GradebookPanel from '../teacher/GradebookPanel.vue'
|
||||
import { downloadCsv } from '../teacher/csv'
|
||||
import { downloadCsv, textCell } from '../teacher/csv'
|
||||
import { decodeCsv, parseCsv, toRoster, type RosterRow } from '../teacher/roster'
|
||||
|
||||
interface ClassInfo {
|
||||
@@ -177,7 +177,7 @@ async function confirmImport() {
|
||||
|
||||
function exportRoster() {
|
||||
if (!selected.value) return
|
||||
downloadCsv(`${selected.value.name}-名单.csv`, [['用户名', '密码'], ...students.value.map((s) => [`\t${s.username}`, `\t${s.password}`])])
|
||||
downloadCsv(`${selected.value.name}-名单.csv`, [['用户名', '密码'], ...students.value.map((s) => [textCell(s.username), textCell(s.password)])])
|
||||
}
|
||||
|
||||
function downloadTemplate() {
|
||||
|
||||
@@ -30,7 +30,7 @@ import { createRunSession } from '../run/session'
|
||||
import GradeResultView from '../grading/GradeResultView.vue'
|
||||
import TaskPanel from '../grading/TaskPanel.vue'
|
||||
import { captureForms } from '../grading/screenshots'
|
||||
import { renderSampleTag, sampleOptions } from '../samples'
|
||||
import { lastSample, rememberSample, renderSampleTag, sampleOptions } from '../samples'
|
||||
|
||||
const props = defineProps<{ assignmentId?: string; submissionId?: string }>()
|
||||
|
||||
@@ -128,8 +128,8 @@ onMounted(async () => {
|
||||
}
|
||||
const list = await api<{ id: string; title: string }[]>('GET', '/api/samples')
|
||||
samples.value = sampleOptions(list)
|
||||
// 网址里带 ?sample=示例编号 时直接打开该示例(便于老师把链接发给学生)
|
||||
const initial = list.find((s) => s.id === route.query.sample) ?? list[0]
|
||||
// 网址里带 ?sample=示例编号 时直接打开该示例(便于老师把链接发给学生),否则打开上次做的实验
|
||||
const initial = list.find((s) => s.id === route.query.sample) ?? list.find((s) => s.id === lastSample()) ?? list[0]
|
||||
if (initial) await loadSample(initial.id)
|
||||
})
|
||||
|
||||
@@ -150,6 +150,7 @@ async function openProject(key: string, original: () => Promise<SourceFile[]>, d
|
||||
async function loadSample(id: string, discardWork = false) {
|
||||
sampleId.value = id
|
||||
void router.replace({ query: { sample: id } })
|
||||
rememberSample(id)
|
||||
sampleChecks.value = []
|
||||
sampleTask.value = null
|
||||
void api<string[]>('GET', `/api/samples/${encodeURIComponent(id)}/checks`)
|
||||
@@ -596,7 +597,7 @@ function run() {
|
||||
:before-start="flushSave"
|
||||
/>
|
||||
|
||||
<main class="workspace" :class="{ designing: activeForm, 'run-collapsed': runCollapsed }">
|
||||
<main class="workspace" :class="{ 'run-collapsed': runCollapsed }">
|
||||
<section class="editor">
|
||||
<div class="tab-row">
|
||||
<NTabs v-model:value="activeTab" type="card" size="small" class="file-tabs">
|
||||
@@ -698,7 +699,7 @@ function run() {
|
||||
}
|
||||
|
||||
.sample-select {
|
||||
width: 240px;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.metrics {
|
||||
@@ -758,16 +759,12 @@ function run() {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 设计页要放工具箱、窗体和属性窗口,代码页要放得下长代码行,左栏都放宽;切换标签页时布局不跳 */
|
||||
.workspace {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(360px, 2fr) 14px 3fr;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 设计器需要工具箱、窗体和属性窗口,左栏放宽 */
|
||||
.workspace.designing {
|
||||
grid-template-columns: minmax(0, 3fr) 14px minmax(360px, 2fr);
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.workspace.run-collapsed {
|
||||
@@ -815,6 +812,11 @@ function run() {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* 文件多时(项目 8 有十来个)标签换行显示,不藏在看不到滚动条的横向滚动里 */
|
||||
.file-tabs :deep(.n-tabs-wrapper) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.new-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
+105
-49
@@ -11,7 +11,9 @@ import MessageBoxDialog from '../winforms/MessageBoxDialog.vue'
|
||||
import type { RunSession } from './session'
|
||||
|
||||
const props = defineProps<{ session: RunSession }>()
|
||||
const emit = defineEmits<{ diagnostic: [file: string, line: number, column: number] }>()
|
||||
const emit = defineEmits<{
|
||||
diagnostic: [file: string, line: number, column: number]
|
||||
}>()
|
||||
|
||||
const state = props.session.state
|
||||
provide(RUN_IMAGES, props.session.images)
|
||||
@@ -26,7 +28,10 @@ const maximized = ref(false)
|
||||
const desktop = ref<HTMLElement>()
|
||||
const desktopSize = ref({ width: 0, height: 0 })
|
||||
const observer = new ResizeObserver(([entry]) => {
|
||||
desktopSize.value = { width: entry.contentRect.width, height: entry.contentRect.height }
|
||||
desktopSize.value = {
|
||||
width: entry.contentRect.width,
|
||||
height: entry.contentRect.height,
|
||||
}
|
||||
})
|
||||
onMounted(() => observer.observe(desktop.value!))
|
||||
onBeforeUnmount(() => observer.disconnect())
|
||||
@@ -39,7 +44,10 @@ const scale = computed(() => {
|
||||
state.forms.forEach((form, index) => {
|
||||
const p = form.root.props
|
||||
if (p.Visible === false) return
|
||||
const client = (p.ClientSize as SizeValue | undefined) ?? { width: 300, height: 300 }
|
||||
const client = (p.ClientSize as SizeValue | undefined) ?? {
|
||||
width: 300,
|
||||
height: 300,
|
||||
}
|
||||
const start = WINDOW_MARGIN + index * WINDOW_STEP
|
||||
width = Math.max(width, start + client.width + 2 + WINDOW_MARGIN)
|
||||
height = Math.max(height, start + client.height + 2 + (p.FormBorderStyle === 'None' ? 0 : TITLEBAR) + WINDOW_MARGIN)
|
||||
@@ -58,8 +66,34 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey))
|
||||
|
||||
<template>
|
||||
<section class="run-area" :class="{ maximized }">
|
||||
<div class="stage">
|
||||
<div class="desktop-tools">
|
||||
<span v-if="scale < 1" class="desktop-scale" title='窗体比运行区大,按比例缩小显示;点"放大"可以看得更清楚'>缩放 {{ Math.round(scale * 100) }}%</span>
|
||||
<button
|
||||
v-if="maximized && (state.status === 'compiling' || state.status === 'running')"
|
||||
type="button"
|
||||
title="结束正在运行的程序(Shift+F5)"
|
||||
@click="session.stop()"
|
||||
>
|
||||
■ 停止
|
||||
</button>
|
||||
<button type="button" :title="maximized ? '还原(Esc)' : '运行区占满整个网页'" @click="maximized = !maximized">
|
||||
{{ maximized ? '还原' : '放大' }}
|
||||
</button>
|
||||
</div>
|
||||
<div ref="desktop" class="desktop">
|
||||
<div class="desktop-canvas" :style="scale < 1 ? { transform: `scale(${scale})`, width: `${100 / scale}%`, height: `${100 / scale}%` } : undefined">
|
||||
<div
|
||||
class="desktop-canvas"
|
||||
:style="
|
||||
scale < 1
|
||||
? {
|
||||
transform: `scale(${scale})`,
|
||||
width: `${100 / scale}%`,
|
||||
height: `${100 / scale}%`,
|
||||
}
|
||||
: undefined
|
||||
"
|
||||
>
|
||||
<FormWindow
|
||||
v-for="(form, index) in state.forms"
|
||||
:key="form.root.id"
|
||||
@@ -76,35 +110,10 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey))
|
||||
连接已恢复。刚才的程序随连接断开已经结束,请重新运行
|
||||
</div>
|
||||
<div v-else-if="state.forms.length === 0" class="desktop-hint">点击"运行"后,学生程序的窗体会显示在这里</div>
|
||||
<div class="desktop-tools">
|
||||
<span v-if="scale < 1" class="desktop-scale" title='窗体比运行区大,按比例缩小显示;点"放大"可以看得更清楚'> 缩放 {{ Math.round(scale * 100) }}% </span>
|
||||
<button type="button" :title="maximized ? '还原(Esc)' : '运行区占满整个网页'" @click="maximized = !maximized">
|
||||
{{ maximized ? '还原' : '放大' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<TrainingBoard :board="state.board" :playing="state.playing" @set="session.setBoard" @reset="session.resetBoard" />
|
||||
<div v-if="state.savedImages.length" class="photos">
|
||||
<div class="photos-title">程序保存的图片({{ state.savedImages.length }})<span>只在这次运行中保留</span></div>
|
||||
<div class="photos-list">
|
||||
<figure
|
||||
v-for="(photo, i) in state.savedImages"
|
||||
:key="i"
|
||||
:title="`${photo.path}(点击查看大图)`"
|
||||
@click="viewing = { path: photo.path, url: session.images.get(photo.image) ?? '' }"
|
||||
>
|
||||
<img :src="session.images.get(photo.image)" alt="" />
|
||||
<figcaption>{{ photo.path }}</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="viewing" class="photo-viewer" @click="viewing = null">
|
||||
<figure>
|
||||
<img :src="viewing.url" alt="" />
|
||||
<figcaption>{{ viewing.path }}(点击任意处关闭)</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="output">
|
||||
<!-- 编译错误、异常、控制台输出和保存的图片紧跟在桌面下面,不会被很高的实训台挤到看不见的地方 -->
|
||||
<div v-if="state.diagnostics.length || state.errors.length || state.console" class="output">
|
||||
<div
|
||||
v-for="(d, i) in state.diagnostics"
|
||||
:key="`d${i}`"
|
||||
@@ -115,9 +124,41 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey))
|
||||
{{ d.file }}({{ d.line }},{{ d.column }}) {{ d.code }}:{{ d.message }}
|
||||
<div v-if="d.hint" class="hint">提示:{{ d.hint }}</div>
|
||||
</div>
|
||||
<pre v-for="(e, i) in state.errors" :key="`e${i}`" class="error">{{ e }}</pre>
|
||||
<pre
|
||||
v-for="(e, i) in state.errors"
|
||||
:key="`e${i}`"
|
||||
:class="['error', { jump: e.file && e.line }]"
|
||||
:title="e.file && e.line ? '点击跳到出错的代码' : undefined"
|
||||
@click="e.file && e.line && emit('diagnostic', e.file, e.line, 1)"
|
||||
>{{ e.text }}</pre>
|
||||
<pre v-if="state.console" class="console">{{ state.console }}</pre>
|
||||
</div>
|
||||
<div v-if="state.savedImages.length" class="photos">
|
||||
<div class="photos-title">程序保存的图片({{ state.savedImages.length }})<span>只在这次运行中保留</span></div>
|
||||
<div class="photos-list">
|
||||
<figure
|
||||
v-for="(photo, i) in state.savedImages"
|
||||
:key="i"
|
||||
:title="`${photo.path}(点击查看大图)`"
|
||||
@click="
|
||||
viewing = {
|
||||
path: photo.path,
|
||||
url: session.images.get(photo.image) ?? '',
|
||||
}
|
||||
"
|
||||
>
|
||||
<img :src="session.images.get(photo.image)" alt="" />
|
||||
<figcaption>{{ photo.path }}</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
<TrainingBoard :board="state.board" :playing="state.playing" @set="session.setBoard" @reset="session.resetBoard" />
|
||||
<div v-if="viewing" class="photo-viewer" @click="viewing = null">
|
||||
<figure>
|
||||
<img :src="viewing.url" alt="" />
|
||||
<figcaption>{{ viewing.path }}(点击任意处关闭)</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -131,6 +172,18 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey))
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 运行区内容超出高度时整体滚动,除了桌面都不被压缩 */
|
||||
.run-area > :not(.stage) {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.stage {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.desktop {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
@@ -151,36 +204,31 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey))
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
/* 放在桌面上方,不压住学生窗体的标题栏按钮 */
|
||||
.desktop-tools {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
z-index: 100001;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.desktop-scale {
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
background: rgb(0 0 0 / 0.35);
|
||||
color: #fff;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.desktop-tools button {
|
||||
padding: 2px 10px;
|
||||
border: 1px solid rgb(255 255 255 / 0.6);
|
||||
padding: 1px 10px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 3px;
|
||||
background: rgb(0 0 0 / 0.35);
|
||||
color: #fff;
|
||||
background: #fff;
|
||||
color: #374151;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.desktop-tools button:hover {
|
||||
background: rgb(0 0 0 / 0.55);
|
||||
border-color: #9ca3af;
|
||||
}
|
||||
|
||||
/* 放大:桌面在左边占满高度,实训台、图片列表和输出在右边一栏 */
|
||||
@@ -195,13 +243,13 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey))
|
||||
background: #f3f4f6;
|
||||
}
|
||||
|
||||
.run-area.maximized .desktop {
|
||||
.run-area.maximized .stage {
|
||||
grid-column: 1;
|
||||
grid-row: 1 / span 20;
|
||||
height: calc(100vh - 16px);
|
||||
}
|
||||
|
||||
.run-area.maximized > :not(.desktop) {
|
||||
.run-area.maximized > :not(.stage) {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
@@ -293,11 +341,19 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey))
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.diag {
|
||||
/* 长的报错和输出折行显示,不用左右滚动 */
|
||||
.output pre {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.diag,
|
||||
.error.jump {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.diag:hover {
|
||||
.diag:hover,
|
||||
.error.jump:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
+18
-5
@@ -8,6 +8,12 @@ export interface OpenForm {
|
||||
}
|
||||
|
||||
/** 学生程序保存的图片(如摄像头照片),path 相对程序所在目录 */
|
||||
export interface RunError {
|
||||
text: string
|
||||
file?: string
|
||||
line?: number
|
||||
}
|
||||
|
||||
export interface SavedImage {
|
||||
path: string
|
||||
image: string
|
||||
@@ -27,7 +33,8 @@ export function createRunSession() {
|
||||
status: 'idle' as 'idle' | 'compiling' | 'running' | 'stopped',
|
||||
diagnostics: [] as Diagnostic[],
|
||||
console: '',
|
||||
errors: [] as string[],
|
||||
/** 运行出错、连接断开等提示;运行时异常带着出错的文件和行号,点击跳过去 */
|
||||
errors: [] as RunError[],
|
||||
forms: [] as OpenForm[],
|
||||
messageBoxes: [] as PendingMessageBox[],
|
||||
/** 虚拟实训台,由服务器推送;连接建立前为 null */
|
||||
@@ -103,7 +110,7 @@ export function createRunSession() {
|
||||
ws.onclose = () => {
|
||||
// 服务器那边的程序随连接一起结束了;窗体留在页面上只会"点了没反应"
|
||||
if (!closed && (state.forms.length > 0 || state.status === 'compiling' || state.status === 'running')) {
|
||||
state.errors.push('与服务器的连接断开了(网络不稳或服务器正在更新),程序已经结束。连上之后请重新运行。')
|
||||
state.errors.push({ text: '与服务器的连接断开了(网络不稳或服务器正在更新),程序已经结束。连上之后请重新运行。' })
|
||||
}
|
||||
stopAudio()
|
||||
state.forms = []
|
||||
@@ -131,7 +138,7 @@ export function createRunSession() {
|
||||
async function run(files: SourceFile[], sampleId?: string) {
|
||||
await connect()
|
||||
if (socket?.readyState !== WebSocket.OPEN) {
|
||||
state.errors = ['连不上服务器,请检查网络,稍后再点"运行"。']
|
||||
state.errors = [{ text: '连不上服务器,请检查网络,稍后再点"运行"。' }]
|
||||
return
|
||||
}
|
||||
state.connection = 'open'
|
||||
@@ -194,6 +201,12 @@ export function createRunSession() {
|
||||
switch (message.type) {
|
||||
case 'status':
|
||||
state.status = message.state
|
||||
// 点"停止"时服务器直接结束进程,不再发 exited;窗体留着只会"点了没反应"
|
||||
if (message.state === 'stopped') {
|
||||
stopAudio()
|
||||
state.forms = []
|
||||
state.messageBoxes = []
|
||||
}
|
||||
break
|
||||
case 'compiled':
|
||||
state.diagnostics = message.diagnostics
|
||||
@@ -259,10 +272,10 @@ export function createRunSession() {
|
||||
state.console += message.text
|
||||
break
|
||||
case 'exception':
|
||||
state.errors.push(`${message.message}\n${message.detail}`)
|
||||
state.errors.push({ text: `运行出错:${message.message}\n${message.detail}`, file: message.file, line: message.line })
|
||||
break
|
||||
case 'serverError':
|
||||
state.errors.push(message.message)
|
||||
state.errors.push({ text: message.message })
|
||||
break
|
||||
case 'exited':
|
||||
stopAudio()
|
||||
|
||||
+20
-1
@@ -1,4 +1,4 @@
|
||||
// 实验下拉菜单:按教材的项目分组,组名是项目名,组里每项只写"任务N …";选中后框里显示完整标题
|
||||
// 实验下拉菜单:按教材的项目分组,组名是项目名,组里每项只写"任务N …";选中后框里显示完整标题。另外记住上次做的实验
|
||||
import { h } from 'vue'
|
||||
import type { SelectGroupOption, SelectOption, SelectRenderTag } from 'naive-ui'
|
||||
|
||||
@@ -32,3 +32,22 @@ export function sampleOptions(list: { id: string; title: string }[]): SelectGrou
|
||||
|
||||
/** 选择框里显示完整标题(带项目号),下拉列表里仍是简短的"任务N …" */
|
||||
export const renderSampleTag: SelectRenderTag = ({ option }) => h('span', { title: option.title as string }, option.title as string)
|
||||
|
||||
// 记住上次做的实验:从数据库页面返回、重新登录后回到这个实验,恢复数据时默认选它
|
||||
const LAST_SAMPLE_KEY = 'iot-sim:last-sample'
|
||||
|
||||
export function lastSample() {
|
||||
try {
|
||||
return localStorage.getItem(LAST_SAMPLE_KEY)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function rememberSample(id: string) {
|
||||
try {
|
||||
localStorage.setItem(LAST_SAMPLE_KEY, id)
|
||||
} catch {
|
||||
// 浏览器禁用了本地存储:下次打开第一个实验
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { computed, h, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { NButton, NDataTable, NEmpty, useMessage, type DataTableColumns } from 'naive-ui'
|
||||
import { api } from '../api'
|
||||
import { downloadCsv } from './csv'
|
||||
import { downloadCsv, textCell } from './csv'
|
||||
|
||||
interface Cell {
|
||||
submitted: boolean
|
||||
@@ -61,11 +61,14 @@ const columns = computed<DataTableColumns<Row>>(() => [
|
||||
{ title: `合计(${fullMarks.value})`, key: 'sum', width: 110, sorter: (a: Row, b: Row) => sum(a) - sum(b), render: (row: Row) => String(sum(row)) },
|
||||
])
|
||||
|
||||
/** 有 AI 建议分时才解释"AI 建议,待确认"(没配置 AI 评阅时不会出现) */
|
||||
const hasAi = computed(() => book.value.students.some((s) => Object.values(s.scores).some((c) => c.aiManual !== null)))
|
||||
|
||||
function exportCsv() {
|
||||
downloadCsv(`${props.className}-成绩表.csv`, [
|
||||
['用户名', ...book.value.assignments.map((a) => `${a.title}(${a.total})`), `合计(${fullMarks.value})`],
|
||||
...book.value.students.map((s) => [
|
||||
`\t${s.username}`,
|
||||
textCell(s.username),
|
||||
...book.value.assignments.map((a) => (s.scores[a.id]?.submitted ? s.scores[a.id].total : '未交')),
|
||||
sum(s),
|
||||
]),
|
||||
@@ -76,7 +79,10 @@ function exportCsv() {
|
||||
<template>
|
||||
<div>
|
||||
<div class="head">
|
||||
<span class="tip">总分 = 最近一次提交的自动评分 + 老师评分;"AI 建议,待确认"是含 AI 建议分的总分,老师采纳后才算数;"待评"只含自动评分。</span>
|
||||
<span class="tip"
|
||||
>总分 = 最近一次提交的自动评分 + 老师评分;<template v-if="hasAi">"AI 建议,待确认"是含 AI 建议分的总分,老师采纳后才算数;</template
|
||||
>"待评"只含自动评分。</span
|
||||
>
|
||||
<NButton size="small" :disabled="book.students.length === 0" @click="exportCsv">导出成绩表(CSV)</NButton>
|
||||
</div>
|
||||
<NDataTable
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 下载 CSV:开头带 BOM,Excel 打开时中文不乱码。
|
||||
// 账号、密码等长数字前加制表符(cell 以 \t 开头),Excel 不会把它变成科学计数法。
|
||||
// 账号、密码这类纯数字的文字用 textCell 包一下,Excel 不会把它变成数字(丢掉开头的 0、变成科学计数法)。
|
||||
export function downloadCsv(filename: string, rows: (string | number | null)[][]) {
|
||||
const quote = (value: string | number | null) => {
|
||||
const cell = value === null ? '' : String(value)
|
||||
@@ -12,3 +12,6 @@ export function downloadCsv(filename: string, rows: (string | number | null)[][]
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
}
|
||||
|
||||
/** 纯数字时前面加制表符,Excel 按文字处理;姓名等其他文字原样输出,不留多余的空白 */
|
||||
export const textCell = (value: string) => (/^\d+$/.test(value) ? `\t${value}` : value)
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
// 按控件类型渲染一个 WinForms 控件及其子控件。
|
||||
// 用户操作只通过 act() 上报,由运行器按真实 WinForms 的顺序触发事件。
|
||||
import { computed, inject, provide, ref, type CSSProperties } from 'vue'
|
||||
import { computed, inject, onBeforeUnmount, provide, ref, watch, type CSSProperties } from 'vue'
|
||||
import type { ControlNode, UserAction } from '@iot/shared'
|
||||
import { AMBIENT_BACK_COLOR, AMBIENT_BACK_TYPES, boxCss, DEFAULT_BACK_COLOR, displayText } from './format'
|
||||
import { backgroundCss, imageSources, RUN_IMAGES, srcset, type ImageValue } from './images'
|
||||
@@ -49,10 +49,12 @@ const passwordChar = computed(() => {
|
||||
return ch && ch !== '\0' ? ch : ''
|
||||
})
|
||||
|
||||
// ComboBox
|
||||
// ComboBox:下拉列表挂到页面最外层(和真实 WinForms 一样可以伸出窗体),按控件在屏幕上的位置和缩放摆放
|
||||
const dropDownOpen = ref(false)
|
||||
const items = computed(() => (p.value.Items as string[] | undefined) ?? [])
|
||||
const editable = computed(() => p.value.DropDownStyle !== 'DropDownList')
|
||||
const combo = ref<HTMLElement>()
|
||||
const listStyle = ref<CSSProperties>({})
|
||||
|
||||
function openDropDown() {
|
||||
if (!enabled.value) return
|
||||
@@ -60,11 +62,37 @@ function openDropDown() {
|
||||
dropDownOpen.value = false
|
||||
return
|
||||
}
|
||||
const el = combo.value
|
||||
if (!el) return
|
||||
const rect = el.getBoundingClientRect()
|
||||
listStyle.value = {
|
||||
left: `${rect.left}px`,
|
||||
top: `${rect.bottom}px`,
|
||||
width: `${el.offsetWidth}px`,
|
||||
transform: `scale(${rect.width / el.offsetWidth || 1})`,
|
||||
font: getComputedStyle(el).font,
|
||||
}
|
||||
// 先触发 Click(教材在 Click 里填充串口列表),列表随后按最新的 Items 显示
|
||||
act({ action: 'dropDown' })
|
||||
dropDownOpen.value = true
|
||||
}
|
||||
|
||||
/** 点列表以外的地方、滚动页面时收起列表 */
|
||||
function closeOnOutside(event: Event) {
|
||||
const target = event.target as Node
|
||||
if (combo.value?.contains(target) || (target instanceof Element && target.closest('.wf-combobox-list'))) return
|
||||
dropDownOpen.value = false
|
||||
}
|
||||
watch(dropDownOpen, (open) => {
|
||||
const method = open ? 'addEventListener' : 'removeEventListener'
|
||||
window[method]('mousedown', closeOnOutside, true)
|
||||
window[method]('scroll', closeOnOutside, true)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('mousedown', closeOnOutside, true)
|
||||
window.removeEventListener('scroll', closeOnOutside, true)
|
||||
})
|
||||
|
||||
function selectItem(index: number) {
|
||||
dropDownOpen.value = false
|
||||
act({ action: 'select', index })
|
||||
@@ -130,7 +158,7 @@ const itemHeight = computed(() => `${(p.value.ItemHeight as number | undefined)
|
||||
</div>
|
||||
|
||||
<!-- ComboBox -->
|
||||
<div v-else-if="node.type === 'ComboBox'" class="wf-control wf-combobox" :style="style">
|
||||
<div v-else-if="node.type === 'ComboBox'" ref="combo" class="wf-control wf-combobox" :style="style">
|
||||
<input
|
||||
:value="String(p.Text ?? '')"
|
||||
:readonly="!editable"
|
||||
@@ -139,10 +167,12 @@ const itemHeight = computed(() => `${(p.value.ItemHeight as number | undefined)
|
||||
@input="act({ action: 'text', value: ($event.target as HTMLInputElement).value })"
|
||||
/>
|
||||
<span class="wf-combobox-arrow" @mousedown.prevent="openDropDown">▾</span>
|
||||
<ul v-if="dropDownOpen" class="wf-combobox-list">
|
||||
<Teleport to="body">
|
||||
<ul v-if="dropDownOpen" class="wf-combobox-list" :style="listStyle">
|
||||
<li v-for="(item, index) in items" :key="index" @mousedown.prevent="selectItem(index)">{{ item }}</li>
|
||||
<li v-if="items.length === 0" class="wf-combobox-empty"> </li>
|
||||
</ul>
|
||||
</Teleport>
|
||||
</div>
|
||||
|
||||
<!-- ListBox:点空白处只触发 Click,点某一项先选中再触发 Click -->
|
||||
|
||||
@@ -136,12 +136,12 @@
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* 挂在 body 下,位置、宽度、缩放和字体由 ControlView.vue 按下拉框算好 */
|
||||
.wf-combobox-list {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 5;
|
||||
position: fixed;
|
||||
z-index: 2000;
|
||||
box-sizing: border-box;
|
||||
transform-origin: 0 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
Reference in new issue
Block a user