项目 4 任务 2 网络摄像机:PictureBox、Image / Bitmap、照片列表
- 摄像头:自绘东围墙场景(Camera/scene.svg),按云台位置切成 640×480 帧(build-frames.ts); Pan 每次转一格,红外对射被遮挡时画面出现翻墙的人;类名、委托、事件参数与教材 dll 一致, ImageRead 每 250 ms 在后台线程触发 - Image / Bitmap 保存真实图片数据:Save、FromFile、FromStream,文件夹不存在时报 GDI+ 一般性错误; 图片每张只发给浏览器一次(按内容哈希缓存) - 程序所在目录(AppDomain.CurrentDomain.BaseDirectory,编译检查单独放行)和当前目录指向可写的 bin/Debug, 沙箱里在沙箱的临时目录 - PictureBox(SizeMode、BorderStyle);设计器工具箱加"图片框";运行区显示程序保存的图片,点开看大图; 实训台摄像头图标显示开启状态 - 评分规则加图片框显示图片、摄像头开关与转向、保存图片三种检查;示例 p4-t2-网络摄像机 - 测试:摄像头端到端、图片读写与程序目录、AppDomain 编译检查、评分出错情况 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4ed9450c02
commit
6b088240da
76 files changed
+1290
-14
No files matched your search
+22
-1
@@ -15,6 +15,12 @@ export interface OpenForm {
|
||||
modal: boolean
|
||||
}
|
||||
|
||||
/** 学生程序保存的图片(如摄像头照片),path 相对程序所在目录 */
|
||||
export interface SavedImage {
|
||||
path: string
|
||||
image: string
|
||||
}
|
||||
|
||||
export interface PendingMessageBox {
|
||||
requestId: number
|
||||
text: string
|
||||
@@ -34,10 +40,14 @@ export function createRunSession() {
|
||||
messageBoxes: [] as PendingMessageBox[],
|
||||
/** 虚拟实训台,由服务器推送;连接建立前为 null */
|
||||
board: null as BoardState | null,
|
||||
/** 本次运行保存的图片,显示在运行区的照片列表里;下次运行时清空 */
|
||||
savedImages: [] as SavedImage[],
|
||||
metrics: { compileMs: 0, startMs: 0, lastRoundTripMs: 0 },
|
||||
})
|
||||
/** id → 控件节点,patch 按 id 修改节点属性 */
|
||||
const nodes = shallowReactive(new Map<string, ControlNode>())
|
||||
/** 图片编号 → 网址(blob:)。运行器每张图只发一次,控件属性里只带编号 */
|
||||
const images = shallowReactive(new Map<string, string>())
|
||||
let socket: WebSocket | undefined
|
||||
/** 正在建立或已建立的连接;同时多处调用 connect() 时共用同一个 */
|
||||
let connecting: Promise<void> | undefined
|
||||
@@ -76,7 +86,10 @@ export function createRunSession() {
|
||||
state.errors = []
|
||||
state.forms = []
|
||||
state.messageBoxes = []
|
||||
state.savedImages = []
|
||||
nodes.clear()
|
||||
for (const url of images.values()) URL.revokeObjectURL(url)
|
||||
images.clear()
|
||||
send({ type: 'run', files })
|
||||
}
|
||||
|
||||
@@ -173,6 +186,14 @@ export function createRunSession() {
|
||||
case 'messageBox':
|
||||
state.messageBoxes.push(message)
|
||||
break
|
||||
case 'image': {
|
||||
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 'imageSaved':
|
||||
state.savedImages.push({ path: message.path, image: message.image })
|
||||
break
|
||||
case 'board':
|
||||
state.board = message.state
|
||||
break
|
||||
@@ -197,7 +218,7 @@ export function createRunSession() {
|
||||
socket?.close()
|
||||
}
|
||||
|
||||
return { state, connect, run, stop, act, replyMessageBox, setBoard, resetBoard, close }
|
||||
return { state, images, connect, run, stop, act, replyMessageBox, setBoard, resetBoard, close }
|
||||
}
|
||||
|
||||
export type RunSession = ReturnType<typeof createRunSession>
|
||||
Reference in new issue
Block a user