项目 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
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
// 按控件类型渲染一个 WinForms 控件及其子控件。
|
||||
// 用户操作只通过 act() 上报,由运行器按真实 WinForms 的顺序触发事件。
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, inject, ref, type CSSProperties } from 'vue'
|
||||
import type { ControlNode, UserAction } from '@iot/shared'
|
||||
import { boxCss, displayText } from './format'
|
||||
import { RUN_IMAGES, type ImageValue } from './images'
|
||||
import LedControl from './LedControl.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -49,6 +50,24 @@ function selectItem(index: number) {
|
||||
act({ action: 'select', index })
|
||||
}
|
||||
|
||||
// PictureBox:按 SizeMode 摆放图片
|
||||
const runImages = inject(RUN_IMAGES, new Map<string, string>())
|
||||
const image = computed(() => p.value.Image as ImageValue | null | undefined)
|
||||
const imageUrl = computed(() => (image.value ? runImages.get(image.value.key) : undefined))
|
||||
const imageStyle = computed((): CSSProperties => {
|
||||
const natural = { width: `${image.value?.width ?? 0}px`, height: `${image.value?.height ?? 0}px` }
|
||||
switch (p.value.SizeMode) {
|
||||
case 'StretchImage':
|
||||
return { width: '100%', height: '100%', objectFit: 'fill' }
|
||||
case 'Zoom':
|
||||
return { width: '100%', height: '100%', objectFit: 'contain' }
|
||||
case 'CenterImage':
|
||||
return { ...natural, left: '50%', top: '50%', transform: 'translate(-50%, -50%)' }
|
||||
default:
|
||||
return natural
|
||||
}
|
||||
})
|
||||
|
||||
// ListBox
|
||||
const itemHeight = computed(() => `${(p.value.ItemHeight as number | undefined) ?? 12}px`)
|
||||
</script>
|
||||
@@ -132,6 +151,17 @@ const itemHeight = computed(() => `${(p.value.ItemHeight as number | undefined)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PictureBox -->
|
||||
<div
|
||||
v-else-if="node.type === 'PictureBox'"
|
||||
class="wf-control wf-picturebox"
|
||||
:class="`wf-picturebox-border-${p.BorderStyle ?? 'None'}`"
|
||||
:style="style"
|
||||
@click="act({ action: 'click' })"
|
||||
>
|
||||
<img v-if="imageUrl" :src="imageUrl" :style="imageStyle" alt="" draggable="false" />
|
||||
</div>
|
||||
|
||||
<!-- CheckBox / RadioButton:自绘勾选框,状态完全由运行器决定(点击只上报,不在浏览器里自行切换) -->
|
||||
<div
|
||||
v-else-if="node.type === 'CheckBox' || node.type === 'RadioButton'"
|
||||
|
||||
Reference in new issue
Block a user