项目 7 情景控制:虚拟时钟、项目资源、Windows Media Player、TrackBar
- 虚拟时钟:实训台时钟面板可拨时间、1×/10×/60× 倍速、恢复真实时间;编译时把 DateTime.Now / Today / UtcNow 改写成 SimClock;评分规则加 clock 步骤 - 项目资源(Properties.Resources):平台素材库(自绘开关图片、合成门铃声),"项目资源"对话框勾选, 平台生成 Properties/Resources.Designer.cs;设计器图片框的"图片"属性可选资源;程序目录下的 Resources 指向素材库;未添加资源时给中文提示 - Windows Media Player(AxWMPLib):设置 URL 自动播放、Ctlcontrols.play/stop,浏览器里播放声音, 实训台加广播喇叭;编译时给窗体嵌空资源,OcxState 取到 null 不报错;图片和声音统一按内容缓存(blob) - TrackBar:拖动、点轨道、方向键,先 ValueChanged 再 Scroll;设计器加"滑动条""媒体播放器" - 评分规则加 value、media 检查;示例项目 7 任务 1–3(修正书上开关灯判断写反、漏填串口列表) - 修正:Properties/ 下的资源类不当作窗体;带命名空间的 System.AppDomain.CurrentDomain.BaseDirectory 放行 - 测试:虚拟时钟、TrackBar、项目 7 示例评分、作业起点带资源类 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a9af26c2d6
commit
eedf1aa2fb
66 files changed
+3538
-43
No files matched your search
+36
-1
@@ -42,6 +42,8 @@ export function createRunSession() {
|
||||
board: null as BoardState | null,
|
||||
/** 本次运行保存的图片,显示在运行区的照片列表里;下次运行时清空 */
|
||||
savedImages: [] as SavedImage[],
|
||||
/** 正在播放的声音(媒体播放器控件,实训台上的广播喇叭随之亮起) */
|
||||
playing: null as { name: string } | null,
|
||||
metrics: { compileMs: 0, startMs: 0, lastRoundTripMs: 0 },
|
||||
})
|
||||
/** id → 控件节点,patch 按 id 修改节点属性 */
|
||||
@@ -52,6 +54,34 @@ export function createRunSession() {
|
||||
/** 正在建立或已建立的连接;同时多处调用 connect() 时共用同一个 */
|
||||
let connecting: Promise<void> | undefined
|
||||
let actionSentAt = 0
|
||||
/** 每个媒体播放器控件一个 <audio> */
|
||||
const players = new Map<string, HTMLAudioElement>()
|
||||
|
||||
function stopAudio() {
|
||||
for (const audio of players.values()) audio.pause()
|
||||
players.clear()
|
||||
state.playing = null
|
||||
}
|
||||
|
||||
function media(message: Extract<ServerMessage, { type: 'media' }>) {
|
||||
players.get(message.id)?.pause()
|
||||
players.delete(message.id)
|
||||
if (message.action === 'stop' || !message.key) {
|
||||
if (players.size === 0) state.playing = null
|
||||
return
|
||||
}
|
||||
const url = images.get(message.key)
|
||||
if (!url) return
|
||||
const audio = new Audio(url)
|
||||
players.set(message.id, audio)
|
||||
state.playing = { name: message.name ?? '' }
|
||||
audio.onended = () => {
|
||||
players.delete(message.id)
|
||||
if (players.size === 0) state.playing = null
|
||||
}
|
||||
// 浏览器可能因为页面还没被点过而拒绝自动播放:喇叭照样亮一会儿
|
||||
audio.play().catch(() => setTimeout(() => audio.onended?.(new Event('ended')), 2000))
|
||||
}
|
||||
|
||||
function connect() {
|
||||
connecting ??= validAccessToken().then(
|
||||
@@ -87,6 +117,7 @@ export function createRunSession() {
|
||||
state.forms = []
|
||||
state.messageBoxes = []
|
||||
state.savedImages = []
|
||||
stopAudio()
|
||||
nodes.clear()
|
||||
for (const url of images.values()) URL.revokeObjectURL(url)
|
||||
images.clear()
|
||||
@@ -186,11 +217,14 @@ export function createRunSession() {
|
||||
case 'messageBox':
|
||||
state.messageBoxes.push(message)
|
||||
break
|
||||
case 'image': {
|
||||
case 'blob': {
|
||||
const bytes = Uint8Array.from(atob(message.data), (c) => c.charCodeAt(0))
|
||||
images.set(message.key, URL.createObjectURL(new Blob([bytes], { type: message.mime })))
|
||||
break
|
||||
}
|
||||
case 'media':
|
||||
media(message)
|
||||
break
|
||||
case 'imageSaved':
|
||||
state.savedImages.push({ path: message.path, image: message.image })
|
||||
break
|
||||
@@ -207,6 +241,7 @@ export function createRunSession() {
|
||||
state.errors.push(message.message)
|
||||
break
|
||||
case 'exited':
|
||||
stopAudio()
|
||||
state.forms = []
|
||||
state.messageBoxes = []
|
||||
break
|
||||
|
||||
Reference in new issue
Block a user