移动端适配,开关改善select

This commit is contained in:
feie9456 2025-09-12 15:57:55 +08:00
parent 2fd050ec6d
commit c4a7d0d5a2
3 changed files with 73 additions and 25 deletions

View File

@ -16,7 +16,7 @@ const worldRef = ref<HTMLDivElement | null>(null)
const showAI = ref(true) const showAI = ref(true)
const state = reactive({ const state = reactive({
scale: 1, scale: 0.8,
minScale: 0.2, minScale: 0.2,
maxScale: 3, maxScale: 3,
translateX: 0, translateX: 0,
@ -128,7 +128,8 @@ function onCanvasDrop(e: DragEvent) {
const props: Record<string, number | string> = {} const props: Record<string, number | string> = {}
; (item.propertySchemas || []).forEach((p) => { props[p.key] = p.default }) ; (item.propertySchemas || []).forEach((p) => { props[p.key] = p.default })
const inst: Instance = { id: String(++idSeq), key: item.key, url: item.url, x: x, y: y, size: defSize, rotation: 0, props } const inst: Instance = { id: String(++idSeq), key: item.key, url: item.url, x: x, y: y, size: defSize, rotation: 0, props }
if (item.stateImages) inst.state = 'off' // state props.state
if (item.stateImages) inst.state = String(props['state'] ?? 'off') as any
instances.push(inst) instances.push(inst)
} }
@ -464,7 +465,8 @@ function buildNet(): BuiltNet {
vsrcs.push({ inst, nPlus: n0, nMinus: nInt, V }) vsrcs.push({ inst, nPlus: n0, nMinus: nInt, V })
resistors.push({ inst, nPlus: nInt, nMinus: n1, R: Math.max(1e-9, Rint) }) resistors.push({ inst, nPlus: nInt, nMinus: n1, R: Math.max(1e-9, Rint) })
} else if (key === 'switch') { } else if (key === 'switch') {
if (inst.state === 'on') { const sw = (inst.props?.['state'] as any) === 'on' || inst.state === 'on'
if (sw) {
vsrcs.push({ inst, nPlus: n0, nMinus: n1, V: 0 }) vsrcs.push({ inst, nPlus: n0, nMinus: n1, V: 0 })
} }
// off = open, // off = open,
@ -820,6 +822,13 @@ function loadFromJSONText(text: string) {
rotation: safeNumber(r.rotation, 0), rotation: safeNumber(r.rotation, 0),
props: rawProps, props: rawProps,
state: r.state === 'on' || r.state === 'off' ? r.state : undefined, state: r.state === 'on' || r.state === 'off' ? r.state : undefined,
}
// props.state 使 legacy state off
if (inst.key === 'switch') {
const cur = String((inst.props as any)['state'] ?? '')
if (cur !== 'on' && cur !== 'off') {
(inst.props as any)['state'] = (inst.state === 'on' ? 'on' : 'off')
}
} }
// props便线 // props便线
; (inst as any).__savedConnections = savedConnections ; (inst as any).__savedConnections = savedConnections
@ -951,10 +960,10 @@ function deleteSelected() {
</aside> </aside>
<!-- 画布视口可平移缩放接收拖拽 --> <!-- 画布视口可平移缩放接收拖拽 -->
<main class="viewport" ref="viewportRef" @mousedown="onViewportMouseDown" <main class="viewport" ref="viewportRef" @pointerdown="onViewportMouseDown"
@mousemove="(e) => { onViewportMouseMove(e); onViewportMouseMoveDrag(e); onViewportMouseMoveTrack(e); onViewportMouseMoveSlider(e) }" @pointermove="(e) => { onViewportMouseMove(e); onViewportMouseMoveDrag(e); onViewportMouseMoveTrack(e); onViewportMouseMoveSlider(e) }"
@mouseup="() => { onViewportMouseUp(); onViewportMouseUpDrag(); onViewportMouseUpSlider() }" @pointerup="() => { onViewportMouseUp(); onViewportMouseUpDrag(); onViewportMouseUpSlider() }"
@mouseleave="() => { onViewportMouseUp(); onViewportMouseUpDrag(); onViewportMouseUpSlider() }" @wheel="onWheel" @pointerleave="() => { onViewportMouseUp(); onViewportMouseUpDrag(); onViewportMouseUpSlider() }" @wheel="onWheel"
@dragover="onCanvasDragOver" @drop="onCanvasDrop" @click="clearSelection"> @dragover="onCanvasDragOver" @drop="onCanvasDrop" @click="clearSelection">
<!-- 世界平面网格背景应用 transform --> <!-- 世界平面网格背景应用 transform -->
<div class="world" :style="worldStyle" ref="worldRef"> <div class="world" :style="worldStyle" ref="worldRef">
@ -980,19 +989,32 @@ function deleteSelected() {
<!-- 已放置的元件可拖动 --> <!-- 已放置的元件可拖动 -->
<div v-for="inst in instances" :key="inst.id" class="inst" :class="{ selected: selectedId === inst.id }" <div v-for="inst in instances" :key="inst.id" class="inst" :class="{ selected: selectedId === inst.id }"
:style="{ left: inst.x + 'px', top: inst.y + 'px', transform: 'translate(-50%, -50%) rotate(' + (inst.rotation || 0) + 'deg)' }" :style="{ left: inst.x + 'px', top: inst.y + 'px', transform: 'translate(-50%, -50%) rotate(' + (inst.rotation || 0) + 'deg)' }"
@mousedown="(e) => onInstanceMouseDown(e, inst)" @click.stop="() => selectInstance(inst.id)" @pointerdown="(e) => onInstanceMouseDown(e, inst)" @click.stop="() => selectInstance(inst.id)"
:title="inst.key"> :title="inst.key">
<div class="inst-box" :style="{ width: inst.size + 'px', height: inst.size + 'px' }"> <div class="inst-box" :style="{ width: inst.size + 'px', height: inst.size + 'px' }">
<img <img
:src="(elements.find(e => e.key === inst.key)?.stateImages ? (inst.state === 'on' ? elements.find(e => e.key === inst.key)!.stateImages!.on : elements.find(e => e.key === inst.key)!.stateImages!.off) : inst.url)" :src="(elements.find(e => e.key === inst.key)?.stateImages
? (((inst.key === 'switch' ? (inst.props['state'] as string) : inst.state) === 'on')
? elements.find(e => e.key === inst.key)!.stateImages!.on
: elements.find(e => e.key === inst.key)!.stateImages!.off)
: inst.url)"
:alt="inst.key" :draggable="false" :alt="inst.key" :draggable="false"
@contextmenu.prevent="() => { const meta = elements.find(e => e.key === inst.key); if (meta?.stateImages) inst.state = inst.state === 'on' ? 'off' : 'on' }" /> @contextmenu.prevent="() => {
const meta = elements.find(e => e.key === inst.key);
if (!meta?.stateImages) return;
if (inst.key === 'switch') {
const cur = String(inst.props['state'] ?? 'off');
inst.props['state'] = cur === 'on' ? 'off' : 'on'
} else {
inst.state = inst.state === 'on' ? 'off' : 'on'
}
}" />
<!-- 滑动变阻器滑块 --> <!-- 滑动变阻器滑块 -->
<img v-if="inst.key === 'sliding_rheostat'" class="rheo-pin" <img v-if="inst.key === 'sliding_rheostat'" class="rheo-pin"
:src="(elements.find(e => e.key === inst.key) as any)?.pinUrl" alt="pin" draggable="false" :style="{ :src="(elements.find(e => e.key === inst.key) as any)?.pinUrl" alt="pin" draggable="false" :style="{
left: ((getRheostatPinOffset(inst) / inst.size) * 100) + '%', left: ((getRheostatPinOffset(inst) / inst.size) * 100) + '%',
}" @mousedown.stop="() => { sliderDraggingId = inst.id }" /> }" @pointerdown.stop="() => { sliderDraggingId = inst.id }" />
<!-- 灯泡发散光线叠加层 --> <!-- 灯泡发散光线叠加层 -->
<svg v-if="inst.key === 'light_bulb'" class="bulb-glow" :width="inst.size" :height="inst.size" <svg v-if="inst.key === 'light_bulb'" class="bulb-glow" :width="inst.size" :height="inst.size"
@ -1031,8 +1053,8 @@ function deleteSelected() {
left: getConnectionPointWorldPos(inst, idx).x + 'px', left: getConnectionPointWorldPos(inst, idx).x + 'px',
top: getConnectionPointWorldPos(inst, idx).y + 'px' top: getConnectionPointWorldPos(inst, idx).y + 'px'
}" @click.stop="(e) => onEndpointClick(inst.id, idx, e)" }" @click.stop="(e) => onEndpointClick(inst.id, idx, e)"
@mouseenter="() => { hoveredEndpoint = { instId: inst.id, cpIndex: idx } }" @pointerenter="() => { hoveredEndpoint = { instId: inst.id, cpIndex: idx } }"
@mouseleave="() => { if (hoveredEndpoint && hoveredEndpoint.instId === inst.id && hoveredEndpoint.cpIndex === idx) hoveredEndpoint = null }" @pointerleave="() => { if (hoveredEndpoint && hoveredEndpoint.instId === inst.id && hoveredEndpoint.cpIndex === idx) hoveredEndpoint = null }"
:title="cp.name || ('P' + (idx + 1))"></div> :title="cp.name || ('P' + (idx + 1))"></div>
</template> </template>
</div> </div>
@ -1083,8 +1105,13 @@ function deleteSelected() {
<span class="label">{{ schema.label }}</span> <span class="label">{{ schema.label }}</span>
<input v-if="schema.type === 'number'" type="number" step="any" <input v-if="schema.type === 'number'" type="number" step="any"
v-model.number="selectedInst!.props[schema.key]" /> v-model.number="selectedInst!.props[schema.key]" />
<input v-else type="text" v-model="selectedInst!.props[schema.key] as string" /> <input v-else-if="schema.type === 'text'" type="text" v-model="selectedInst!.props[schema.key] as string" />
<span class="unit" v-if="schema.unit">{{ schema.unit }}</span> <select v-else-if="schema.type === 'select'" v-model="selectedInst!.props[schema.key] as string">
<option v-for="opt in (Array.isArray((schema as any).options) ? (schema as any).options : [])" :key="typeof opt === 'string' ? opt : (opt.value)" :value="typeof opt === 'string' ? opt : (opt.value)">
{{ typeof opt === 'string' ? opt : (opt.label || opt.value) }}
</option>
</select>
<span class="unit" v-if="schema.type === 'number' && (schema as any).unit">{{ (schema as any).unit }}</span>
</label> </label>
</template> </template>
</div> </div>
@ -1105,6 +1132,8 @@ function deleteSelected() {
/* 左栏 / 画布 / AI 辅助 */ /* 左栏 / 画布 / AI 辅助 */
grid-template-rows: 100vh; grid-template-rows: 100vh;
overflow: hidden; overflow: hidden;
touch-action: none;
user-select: none;
} }
.sidebar { .sidebar {

View File

@ -35,13 +35,27 @@ export type CircuitElement = {
pinUrl?: string pinUrl?: string
} }
export type PropertySchema = { export type PropertySchema =
| {
key: string key: string
label: string label: string
type: 'number' | 'text' type: 'number'
unit?: string unit?: string
default: number | string default: number
} }
| {
key: string
label: string
type: 'text'
default: string
}
| {
key: string
label: string
type: 'select'
options: Array<{ label?: string; value: string }> | string[]
default: string
}
// 预设定义:展示名称 + 要覆盖的属性值 // 预设定义:展示名称 + 要覆盖的属性值
export type Preset = { export type Preset = {
@ -85,11 +99,13 @@ export const elements: CircuitElement[] = [
defaultSize: 130, defaultSize: 130,
connectionPoints: [ connectionPoints: [
{ x: 0.05, y: 0.7, name: 'A' }, { x: 0.05, y: 0.7, name: 'A' },
{ x: 0.83, y: 0.7, name: 'B' }, { x: 0.95, y: 0.7, name: 'B' },
], ],
// 点击元件切换 on/off 显示 // 点击元件切换 on/off 显示
stateImages: { on: switchOn, off: switchOff }, stateImages: { on: switchOn, off: switchOff },
// 开关没有额外可编辑属性 propertySchemas: [
{ key: 'state', label: '状态', type: 'select', options: ['off', 'on'], default: 'off' },
],
}, },
{ {
key: 'light_bulb', key: 'light_bulb',

View File

@ -4,4 +4,7 @@ import vue from '@vitejs/plugin-vue'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [vue()], plugins: [vue()],
server:{
host: '0.0.0.0'
}
}) })