实验页:程序运行时改了代码或设计器,停手 1.5 秒后自动重新运行,不用再按 F5。重启前先用边写边检查的接口检查整个工程,有错误就不重启,原来的程序接着运行,工具栏提示"代码有错误,改好后自动重新运行";检查期间又改了、程序已停或换了工程时作废;停止后改动不会自动启动程序。使用说明、设计方案同步
CI/CD / test (push) Canceled after 0s
CI/CD / deploy (push) Canceled after 0s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-28 15:15:46 +08:00
1 parent 6a18bdf2e4
commit 9ed446f95a
3 files changed
+52

No files matched your search

+1
View File
@@ -334,6 +334,7 @@ flowchart TB
- 2026-10-26:**窗体改名**。23 个示例里 11 个的主窗体不叫 Form1(Login、frm_analog、frm_LED、frm_control、Main),学生的空白工程却只有 Form1,以前属性窗口改不了窗体名(项目 8 任务 2 的评分还要找 Main 窗体)。现在选中窗体可以改"名称":和 VS 一样,类名、构造函数、各文件里的 `new X()` 和 X 类型的变量都改(跳过字符串和注释),Designer.cs 里窗体的块头注释和 `this.Name` 也改,两个文件跟着改名;事件处理方法名(如 Form1_Load)和窗体标题不变。改名不进设计器的撤销历史。任务说明里提示先改名再双击窗体生成 Load 事件。
- 2026-10-26:代码用 **bun fmt** 格式化,格式写在 `bunfig.toml`(不写分号、单引号、一行最长 160,和原来的写法一致);`docs/教材摘要/` 是书上的摘录,不格式化。
- 2026-10-28:**兼容机房的 Chrome 94**。用 Chromium 94 实测,打开 http 地址时实验页报 `crypto.randomUUID is not a function`(这个函数只在 https 和 localhost 下有);还时好时坏地一片空白:Chrome 94 在 Promise 回调里调用 `import()`(Vite 的预加载就是这样)时,有时把 `import("./X.js")` 相对于网页地址解析,请求 `/X.js` 拿到首页 HTML。改法:打包目标改成 Chrome / Edge 94;`web/src/polyfills.ts` 补上 `crypto.randomUUID`、`structuredClone`(只复制普通数据)、`findLast` / `findLastIndex`;`vite.config.ts` 的小插件在 Vite 找完预加载依赖之后把代码块之间的 `import("./X.js")` 改成 `import("/assets/X.js")`,改过的代码块换新文件名(不用缓存里的旧文件);容器查询(Chrome 105)改成 ResizeObserver 量宽度加 class(实训台的三档布局、设计器窄时隐藏提示文字);`:has()`(Chrome 105)改成 class(只读和禁用的文本框灰底、没标题的分组框)。Monaco 自带的几条 `:has()`、`color-mix()` 样式在 Chrome 94 里不生效,只影响 Markdown 悬停提示里的复选框等细节。实测 Chrome 94 经 http 打开登录、实验页(设计器改属性、代码补全、运行程序)、数据库、班级管理、使用说明,各页面直接打开 5 遍都正常。
- 2026-10-28:**运行中改了自动重新运行**。以前改了代码或设计器,运行区的窗体不变,要按 F5 才看得到。现在程序运行时(`WorkbenchPage.vue`)每次修改后等 1.5 秒没有新的改动,先用语言服务检查整个工程(和边写边检查同一个接口,不生成程序集):没有错误就重新编译运行;有错误就不重启,原来的程序接着运行,工具栏提示"代码有错误,改好后自动重新运行"。检查期间又改了、程序已经停了或换了工程,这次作废。只在运行中触发,停止后改动不会自动启动程序。
## 技术风险与待确认事项
+1
View File
@@ -91,6 +91,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey))
<h3>4. 写代码、运行</h3>
<ul>
<li><b>F5</b> 或 <b>▶ 运行</b>:编译并运行,窗体显示在右边的运行区;<b>Shift+F5</b> 或 <b>■ 停止</b> 结束程序。</li>
<li>程序运行时改了代码或窗体,停手 1.5 秒后自动重新运行,不用再按 F5;代码有错误时原来的程序接着运行,改好后再自动重新运行。</li>
<li>编译错误显示在窗体下面,点它跳到出错的代码;常见错误会给出中文<b>提示</b>。</li>
<li>程序运行时出错(比如文本框是空的就转换成数字),窗体下面显示中文说明和出错的文件、行号,点它跳到那一行。</li>
<li><code>Console.WriteLine</code> 的输出、程序保存的图片(项目 4 的拍照)也显示在窗体下面。</li>
+50
View File
@@ -22,6 +22,7 @@ import {
startupForm,
} from '../designer/templates'
import CodeEditor from '../editor/CodeEditor.vue'
import { checkProject } from '../editor/language'
import { api } from '../api'
import { currentUser, logout } from '../auth'
import { deleteWork, loadWork, sampleWorkKey, saveWork } from '../editor/works'
@@ -136,6 +137,8 @@ onMounted(async () => {
/** 打开一个工程:服务器上有保存的作品就用作品,没有就用原始代码;discardWork 表示丢弃作品恢复原始代码 */
async function openProject(key: string, original: () => Promise<SourceFile[]>, discardWork: boolean) {
await flushSave()
// 换了工程:之前安排的自动重新运行作废
clearTimeout(rerunTimer)
workKey.value = key
designerHistory.clear()
if (discardWork) await deleteWork(key).catch(() => {})
@@ -331,6 +334,7 @@ function updateFile(path: string, content: string) {
function filesChanged() {
modified.value = true
scheduleRerun()
// 老师试运行学生的提交时没有保存位置,不保存
if (workKey.value) scheduleSave()
}
@@ -501,11 +505,50 @@ function toggleRunArea(collapsed = !runCollapsed.value) {
function run() {
if (running.value) return
toggleRunArea(false)
start()
}
function start() {
clearTimeout(rerunTimer)
rerunBlocked.value = false
session.run(
files.value.map((f) => ({ ...f })),
props.assignmentId ? assignment.value?.sampleId : sampleId.value,
)
}
// ---------- 运行中改了代码或窗体:自动重新运行,不用再按 F5 ----------
/** 停止修改多久后重新运行 */
const RERUN_DELAY_MS = 1500
let rerunTimer: ReturnType<typeof setTimeout> | undefined
/** 每次修改都递增,用来判断检查期间是否又改了 */
let editCount = 0
/** 改出了编译错误:原来的程序继续运行,改好后再重新运行 */
const rerunBlocked = ref(false)
function scheduleRerun() {
editCount++
if (!running.value) return
clearTimeout(rerunTimer)
rerunTimer = setTimeout(() => void rerun(), RERUN_DELAY_MS)
}
async function rerun() {
// 上一次还在编译:等它运行起来再说
if (state.status === 'compiling') {
rerunTimer = setTimeout(() => void rerun(), RERUN_DELAY_MS)
return
}
if (state.status !== 'running') return
const edit = editCount
// 先检查一遍:有错误就不重启,免得正在运行的程序被换成一堆报错(错误照常用波浪线标在代码里)
const diagnostics = await checkProject()
// 检查期间又改了(会再安排一次)、程序已经停了,或者检查失败:这次不重新运行
if (edit !== editCount || state.status !== 'running' || !diagnostics) return
rerunBlocked.value = diagnostics.some((d) => d.severity === 'error')
if (!rerunBlocked.value) start()
}
onBeforeUnmount(() => clearTimeout(rerunTimer))
</script>
<template>
@@ -561,6 +604,7 @@ function run() {
</NPopconfirm>
<span v-if="!viewing" class="save-state" :class="saveState">{{ SAVE_TEXT[saveState] }}</span>
<NTag :type="statusType">{{ STATUS_TEXT[state.status] }}</NTag>
<span v-if="rerunBlocked && state.status === 'running'" class="rerun-blocked">代码有错误,改好后自动重新运行</span>
<span v-if="!assignmentId && !viewing && currentUser?.role === 'teacher'" class="metrics">
编译 {{ state.metrics.compileMs }} ms · 启动 {{ state.metrics.startMs }} ms · 最近一次操作往返 {{ state.metrics.lastRoundTripMs }} ms
</span>
@@ -746,6 +790,12 @@ function run() {
color: #dc2626;
}
.rerun-blocked {
color: #b45309;
font-size: 12px;
white-space: nowrap;
}
.user {
font-size: 13px;
white-space: nowrap;