Files
IOT_SIM/web/src/designer/resources.ts
T
xuyueandClaude Opus 5.5 eedf1aa2fb 项目 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>
2026-10-23 14:11:05 +08:00

82 lines
3.5 KiB
TypeScript

// 项目资源(Properties.Resources):学生从平台素材库里选图片和声音,平台生成 Properties/Resources.Designer.cs。
// 生成的类和 VS 的强类型资源类用法一样(Properties.Resources.switch_on 是 Bitmap,声音是 byte[]),
// 取值时调用运行器的素材库。这个文件由平台生成,不在标签页里显示。
import { ref } from 'vue'
import type { SourceFile } from '@iot/shared'
export interface GalleryItem {
/** 资源名,也是 Properties.Resources 的属性名,和教材一致(如 switch_on) */
name: string
file: string
/** 中文说明 */
label: string
kind: 'image' | 'audio'
}
export const RESOURCES_FILE = 'Properties/Resources.Designer.cs'
/** 素材清单(服务器上 /resources/index.json),第一次用到时加载 */
export const gallery = ref<GalleryItem[]>([])
let loading: Promise<void> | undefined
export function loadGallery() {
loading ??= fetch('/resources/index.json')
.then((r) => (r.ok ? r.json() : []))
.then((items: GalleryItem[]) => {
gallery.value = items
})
.catch(() => {
loading = undefined
})
return loading
}
/** 素材文件的网址(设计器预览、资源对话框里显示) */
export function resourceUrl(name: string) {
const item = gallery.value.find((g) => g.name === name)
return `/resources/${item?.file ?? `${name}.png`}`
}
/** 工程里已添加的资源名(从生成的 Resources.Designer.cs 里读出) */
export function projectResources(files: SourceFile[]): string[] {
const text = files.find((f) => f.path === RESOURCES_FILE)?.content ?? ''
return [...text.matchAll(/internal static \S+ ([\p{L}\p{N}_]+) \{/gu)].map((m) => m[1])
}
/** 设计器里引用资源的写法:global::命名空间.Properties.Resources.名字 */
export const resourcePrefix = (namespace: string) => `global::${namespace}.Properties.Resources.`
/** 从属性值里认出资源名:global::X.Properties.Resources.switch_off → switch_off */
export function resourceNameOf(text: string) {
return /\.Properties\.Resources\.([\p{L}\p{N}_]+)$/u.exec(text.trim())?.[1]
}
/** 生成 Properties/Resources.Designer.cs;没有资源时返回 null(删掉这个文件) */
export function resourcesFile(namespace: string, names: string[]): SourceFile | null {
const items = names.map((name) => gallery.value.find((g) => g.name === name)).filter((g): g is GalleryItem => g !== undefined)
if (items.length === 0) return null
const members = items.map((item) =>
item.kind === 'image'
? ` /// <summary>${item.label}</summary>\n internal static System.Drawing.Bitmap ${item.name} {\n get { return global::Sim.Host.ProjectResources.Image("${item.file}"); }\n }`
: ` /// <summary>${item.label}</summary>\n internal static byte[] ${item.name} {\n get { return global::Sim.Host.ProjectResources.Bytes("${item.file}"); }\n }`,
)
const content = `//------------------------------------------------------------------------------
// <auto-generated>
// 项目资源(平台素材库)。在"项目资源"里添加或移除,不要手动修改这个文件。
// </auto-generated>
//------------------------------------------------------------------------------
namespace ${namespace}.Properties {
/// <summary>
/// 项目资源:Properties.Resources.资源名
/// </summary>
internal class Resources {
${members.join('\n\n')}
}
}
`
return { path: RESOURCES_FILE, content }
}