设计器:撤销 / 重做;工具箱、属性、事件窗口改为中文

- 撤销 / 重做按窗体保存在页面层,切换标签页不丢;Ctrl+Z、Ctrl+Y / Ctrl+Shift+Z
- 撤销"生成事件处理方法"时事件绑定和插入的方法一起撤掉;
  学生已在方法里写了代码则只撤销设计,不动代码
- Designer.cs 在代码页被手改后,该窗体历史失效并清空,避免覆盖手改内容
- 没有实际改动的操作(如双击已绑定的事件)不记入历史
- 工具箱、属性名、事件名、枚举选项显示中文;悬停显示英文名和用途说明
- 开发模式下把页面状态挂到 window.__iot,供浏览器自动化测试读取

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-22 11:18:43 +08:00
1 parent ea5a25af06
commit 02d2f0eabc
6 files changed
+302 -67

No files matched your search

+9 -10
View File
@@ -23,7 +23,8 @@ const emit = defineEmits<{
const tab = ref<'props' | 'events'>('props')
const def = computed<ControlDef>(() => (props.selected === '' ? FORM_DEF : controlDef(typeOf(props.doc, props.selected) ?? '')))
const title = computed(() => `${props.selected || props.doc.formName} ${def.value.type.split('.').at(-1)}`)
const typeName = computed(() => def.value.type.split('.').at(-1)!)
const title = computed(() => `${props.selected || props.doc.formName}(${def.value.label})`)
function current(prop: PropDef): Value | undefined {
const text = getPropText(props.doc, props.selected, prop.name)
@@ -131,7 +132,7 @@ function renameHandler(event: string, value: string) {
<template>
<div class="grid">
<div class="grid-title" :title="title">{{ title }}</div>
<div class="grid-title" :title="`类型:${typeName}`">{{ title }}</div>
<div class="tabs">
<button :class="{ active: tab === 'props' }" @click="tab = 'props'">属性</button>
<button :class="{ active: tab === 'events' }" title="事件" @click="tab = 'events'">⚡ 事件</button>
@@ -139,7 +140,7 @@ function renameHandler(event: string, value: string) {
<div v-if="tab === 'props'" class="rows">
<div class="row">
<label title="控件的名称,代码里用 this.名称 访问它">(Name)</label>
<label title="Name:控件的名称,代码里用这个名字访问它">名称</label>
<div class="editor">
<input v-model="nameDraft" :disabled="selected === ''" @change="commitName" @keydown.enter="commitName" />
<div v-if="nameError" class="error">{{ nameError }}</div>
@@ -148,7 +149,7 @@ function renameHandler(event: string, value: string) {
</div>
<div v-for="prop in def.props" :key="prop.name" class="row">
<label :title="prop.label">{{ prop.name }}</label>
<label :title="`${prop.name}:${prop.description}`">{{ prop.label }}</label>
<div class="editor">
<code v-if="raw(prop) !== undefined" class="raw" :title="raw(prop)">{{ raw(prop) }}</code>
@@ -162,8 +163,8 @@ function renameHandler(event: string, value: string) {
<select v-else-if="prop.type === 'bool'" :value="String(boolOf(prop))"
@change="set(prop, { kind: 'bool', value: ($event.target as HTMLSelectElement).value === 'true' })">
<option value="true">True</option>
<option value="false">False</option>
<option value="true">是</option>
<option value="false">否</option>
</select>
<input v-else-if="prop.type === 'int'" type="number" :value="num(prop)"
@@ -181,7 +182,7 @@ function renameHandler(event: string, value: string) {
<select v-else-if="prop.type === 'enum'" :value="enumOf(prop)"
@change="set(prop, { kind: 'enum', type: prop.enumType!, member: ($event.target as HTMLSelectElement).value })">
<option v-for="option in prop.options" :key="option" :value="option">{{ option }}</option>
<option v-for="option in prop.options" :key="option" :value="option">{{ prop.optionLabels?.[option] ?? option }}</option>
</select>
<span v-else-if="prop.type === 'color'" class="color">
@@ -211,7 +212,6 @@ function renameHandler(event: string, value: string) {
<textarea v-else-if="prop.type === 'items'" rows="4" :value="itemsText" placeholder="每行一项"
@change="setItemsFromText(($event.target as HTMLTextAreaElement).value)" />
<div class="hint">{{ prop.label }}</div>
</div>
</div>
</div>
@@ -219,13 +219,12 @@ function renameHandler(event: string, value: string) {
<div v-else class="rows">
<div class="note events-note">双击事件名生成事件处理方法,并跳到代码</div>
<div v-for="event in def.events" :key="event.name" class="row">
<label class="event-name" :title="`${event.label}(双击生成处理方法)`" @dblclick="emit('openEvent', selected, event.name)">{{ event.name }}</label>
<label class="event-name" :title="`${event.name}:双击生成事件处理方法`" @dblclick="emit('openEvent', selected, event.name)">{{ event.label }}</label>
<div class="editor">
<span class="pair">
<input :value="handlerOf(event.name)" placeholder="(未绑定)" @change="renameHandler(event.name, ($event.target as HTMLInputElement).value)" />
<button class="go" title="生成或跳到事件处理方法" @click="emit('openEvent', selected, event.name)">→</button>
</span>
<div class="hint">{{ event.label }}</div>
</div>
</div>
</div>