项目 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:
xuyueandClaude Opus 5.5 committed 2026-10-23 14:11:05 +08:00
1 parent a9af26c2d6
commit eedf1aa2fb
66 files changed
+3538 -43

No files matched your search

+32 -3
View File
@@ -8,7 +8,9 @@ import { NButton, NInput, NModal, NPopconfirm, NPopover, NSelect, NTab, NTabs, N
import type { Assignment, Review, SourceFile, Submission } from '@iot/shared'
import FormDesigner from '../designer/FormDesigner.vue'
import { createDesignerHistory } from '../designer/history'
import { formNameTaken, newFormFiles, nextFormName, projectNamespace, startupForm } from '../designer/templates'
import { gallery, loadGallery, projectResources, resourcePrefix, resourcesFile, RESOURCES_FILE } from '../designer/resources'
import ResourcesDialog from '../designer/ResourcesDialog.vue'
import { formNameTaken, isFormDesigner, newFormFiles, nextFormName, projectNamespace, startupForm } from '../designer/templates'
import CodeEditor from '../editor/CodeEditor.vue'
import { api } from '../api'
import { currentUser, logout } from '../auth'
@@ -34,7 +36,7 @@ const DESIGN = '设计:'
const forms = computed(() => {
const startup = startupForm(files.value)
return files.value
.filter((f) => f.path.endsWith('.Designer.cs'))
.filter((f) => isFormDesigner(f.path))
.map((f) => ({ tab: DESIGN + f.path, designer: f.path, code: f.path.replace(/\.Designer\.cs$/, '.cs') }))
// 启动窗体排在最前
.sort((a, b) => Number(b.code === `${startup}.cs`) - Number(a.code === `${startup}.cs`))
@@ -43,6 +45,7 @@ const forms = computed(() => {
* 标签页按窗体分组:每个窗体依次是 [设计]、代码;其他文件(Program.cs 等)放最后。
* Designer.cs 由设计器生成,平时不显示(和 VS 里藏在窗体文件下面一样),
* 在设计器里点"查看生成的代码"或点到其中的编译错误时才临时出现,切走后收起。
* 项目资源生成的 Properties/Resources.Designer.cs 也一样,在"项目资源"里管理。
*/
const tabs = computed(() => {
const grouped = new Set(forms.value.flatMap((f) => [f.code, f.designer]))
@@ -52,7 +55,9 @@ const tabs = computed(() => {
{ name: f.code, label: f.code },
...(activeTab.value === f.designer ? [{ name: f.designer, label: f.designer }] : []),
]),
...files.value.filter((f) => !grouped.has(f.path)).map((f) => ({ name: f.path, label: f.path })),
...files.value
.filter((f) => !grouped.has(f.path) && (f.path !== RESOURCES_FILE || activeTab.value === RESOURCES_FILE))
.map((f) => ({ name: f.path, label: f.path })),
]
})
const activeForm = computed(() => forms.value.find((f) => f.tab === activeTab.value))
@@ -270,6 +275,24 @@ function filesChanged() {
if (workKey.value) scheduleSave()
}
// ---------- 项目资源 ----------
void loadGallery()
const resourcesOpen = ref(false)
const resourceNames = computed(() => projectResources(files.value))
/** 设计器里图片属性的选项:项目资源里的图片(声音不能当图片用) */
const designerResources = computed(() => ({
names: resourceNames.value.filter((name) => gallery.value.find((g) => g.name === name)?.kind !== 'audio'),
prefix: resourcePrefix(projectNamespace(files.value)),
}))
/** 保存勾选的项目资源:重新生成 Properties/Resources.Designer.cs(一个都没有时删掉) */
function saveResources(names: string[]) {
const file = resourcesFile(projectNamespace(files.value), names)
const rest = files.value.filter((f) => f.path !== RESOURCES_FILE)
files.value = file ? [...rest, file] : rest
filesChanged()
}
// ---------- 新建、删除窗体 ----------
const newFormOpen = ref(false)
const newFormName = ref('')
@@ -444,6 +467,9 @@ function run() {
<NButton size="small" type="primary" :disabled="!!newFormError" @click="createForm">创建</NButton>
</div>
</NPopover>
<NButton size="small" quaternary title="相当于 VS 的“项目属性 → 资源”:添加图片和声音,代码里用 Properties.Resources.名字" @click="resourcesOpen = true">
项目资源{{ resourceNames.length ? `(${resourceNames.length})` : '' }}
</NButton>
<NPopconfirm v-if="deletableForm" positive-text="删除" negative-text="取消" @positive-click="deleteForm">
<template #trigger>
<NButton size="small" quaternary>删除窗体</NButton>
@@ -463,7 +489,10 @@ function run() {
@redo="designerHistory.redo(activeForm.designer, activeForm.code)"
@open-code="openCode"
@open-designer-file="activeTab = activeForm.designer"
:resources="designerResources"
@add-resource="resourcesOpen = true"
/>
<ResourcesDialog v-model:show="resourcesOpen" :selected="resourceNames" @save="saveResources" />
<CodeEditor
v-show="!activeForm"
ref="editorRef"