const { createParameterDialogState, findParameterGroup, findParameterRegister, getActiveParameterGroup, getPageState, getSettingsPageState, getTransportPageState, getVisiblePageState, resolveActiveParamView } = require('../../features/parameter-groups/view-model.js') const { createDialogHandlers, createParameterGroupPoller, parameterGroupService } = require('../../features/parameter-groups/index.js') const settingsService = require('../../store/settings-store.js') const themeService = require('../../store/theme-store.js') const transport = require('../../transport/ble-core.js') const { createPageToast } = require('../../utils/page-toast.js') const { PARAMETER_REGISTER_DRAG_THRESHOLD_PX, buildActiveParameterRegisterRows, clampIndex, getFallbackDragRowOffsetPx, getWindowWidth, resolveDragTargetIndex } = require('../../features/parameter-groups/drag-view-model.js') function getParameterGroupsFromState(state = {}) { return state.parameterGroups || [] } Page({ data: { ...getPageState(), activeParamView: 'parameterGroups', activeParameterGroupId: '', activeParameterRegisterRows: [], parameterDialog: createParameterDialogState() }, onTabItemTap() { this.backToParamsHome() }, onLoad() { this.pageToast = createPageToast(this, this.data) this.parameterGroupPoller = createParameterGroupPoller(() => this.data) this.parameterGroupTouchStarts = {} this.parameterWindowWidth = getWindowWidth() parameterGroupService.init() themeService.init() settingsService.init() this.unsubscribeTheme = themeService.subscribe((themeState) => { this.setData(themeState) }) this.unsubscribeTransport = transport.subscribe((transportState) => { this.setData(getTransportPageState(transportState)) if (transportState.connectedDevice) { setTimeout(() => this.scheduleVisibleParameterAutoReads(), 0) } else { this.clearParameterAutoTimers() } }) this.unsubscribeParameterGroups = parameterGroupService.subscribe((parameterState) => { const activeParameterGroup = getActiveParameterGroup(getParameterGroupsFromState(parameterState), this.data.activeParameterGroupId) this.setData({ ...parameterState, activeParameterGroup, activeParameterRegisterRows: buildActiveParameterRegisterRows(activeParameterGroup, this.parameterRegisterDrag), activeParamView: this.data.activeParamView === 'parameterGroup' && !activeParameterGroup ? 'parameterGroups' : this.data.activeParamView }) }) this.unsubscribeSettings = settingsService.subscribe((settingsState) => { const protocolChanged = this.data.protocolMode && this.data.protocolMode !== settingsState.protocolMode const nextState = getSettingsPageState(this.data, settingsState) const activeParamView = protocolChanged ? 'parameterGroups' : nextState.activeParamView const parameterGroups = getParameterGroupsFromState(parameterGroupService.getState()) const activeParameterGroupId = protocolChanged ? '' : this.data.activeParameterGroupId const activeParameterGroup = getActiveParameterGroup(parameterGroups, activeParameterGroupId) const safeActiveView = activeParamView === 'parameterGroup' && !activeParameterGroup ? 'parameterGroups' : activeParamView this.clearParameterAutoTimers() this.setData({ ...nextState, parameterGroups, activeParameterGroupId, activeParamView: safeActiveView, activeParameterGroup, activeParameterRegisterRows: buildActiveParameterRegisterRows(activeParameterGroup, this.parameterRegisterDrag) }) if (safeActiveView === 'parameterGroups' || safeActiveView === 'parameterGroup') { setTimeout(() => this.scheduleVisibleParameterAutoReads(), 0) } else { this.clearParameterAutoTimers() } }) }, onShow() { if (this.pageToast) { this.pageToast.setActive(true) } const pageState = getVisiblePageState(this.data) this.setData({ ...pageState, activeParameterGroup: getActiveParameterGroup(getParameterGroupsFromState(pageState), this.data.activeParameterGroupId), activeParameterRegisterRows: buildActiveParameterRegisterRows( getActiveParameterGroup(getParameterGroupsFromState(pageState), this.data.activeParameterGroupId), this.parameterRegisterDrag ) }) this.pageToast.showFromState(pageState) this.scheduleVisibleParameterAutoReads() }, onHide() { if (this.pageToast) { this.pageToast.setActive(false) } this.clearParameterRegisterDrag() this.clearParameterAutoTimers() }, onUnload() { if (this.pageToast) { this.pageToast.destroy() this.pageToast = null } if (this.unsubscribeTheme) { this.unsubscribeTheme() this.unsubscribeTheme = null } if (this.unsubscribeTransport) { this.unsubscribeTransport() this.unsubscribeTransport = null } if (this.unsubscribeParameterGroups) { this.unsubscribeParameterGroups() this.unsubscribeParameterGroups = null } if (this.unsubscribeSettings) { this.unsubscribeSettings() this.unsubscribeSettings = null } this.clearParameterAutoTimers() }, openParamView(event) { if (this.pageToast) this.pageToast.clear() this.closeParameterDraft() this.clearParameterRegisterDrag() const activeParamView = event.currentTarget.dataset.view if (!activeParamView) return this.setData({ activeParamView }) if (activeParamView === 'parameterGroups') { setTimeout(() => this.scheduleVisibleParameterAutoReads(), 0) } }, openParameterGroup(event) { const groupId = event.currentTarget.dataset.groupId const group = findParameterGroup(getParameterGroupsFromState(this.data), groupId) if (!group) return if (this.pageToast) this.pageToast.clear() this.closeParameterDraft() this.setData({ activeParameterGroup: group, activeParameterGroupId: groupId, activeParamView: 'parameterGroup', activeParameterRegisterRows: buildActiveParameterRegisterRows(group, this.parameterRegisterDrag) }) }, backToParamsHome() { if (this.pageToast) this.pageToast.clear() this.closeParameterDraft() this.clearParameterRegisterDrag() this.clearParameterAutoTimers() const activeParamView = resolveActiveParamView('', this.data) this.setData({ activeParameterGroup: null, activeParameterGroupId: '', activeParamView, activeParameterRegisterRows: [] }) if (activeParamView === 'parameterGroups') { setTimeout(() => this.scheduleVisibleParameterAutoReads(), 0) } }, noop() {}, ...createDialogHandlers(parameterGroupService), async importParameterGroupsJson() { const count = await parameterGroupService.importJsonFromMessageFile() if (count && this.pageToast) this.pageToast.show(`已导入 ${count} 个寄存器组`) }, async syncParameterGroups() { if (this.data.isModbusProtocol) return if (!this.data.connectedDevice) return const result = await parameterGroupService.syncFromStorageAccessCodeInfo({ maxPacketLength: this.data.parameterMaxPacketLength }) if (result && result.ok && this.pageToast) { const codeInfoAddressText = result.codeInfoAddressText || Number(result.codeInfoAddress || 0).toString(16).toUpperCase().padStart(4, '0') const codeInfoByteLengthText = result.codeInfoByteLengthText || Number(result.codeInfoByteLength || 0).toString(16).toUpperCase().padStart(4, '0') const addedCount = Number(result.addedGroups || 0) + Number(result.addedRegisters || 0) const updatedCount = Number(result.updatedGroups || 0) + Number(result.updatedRegisters || 0) const changedText = [ addedCount ? `新增 ${addedCount}` : '', updatedCount ? `更新 ${updatedCount}` : '' ].filter(Boolean).join(',') this.pageToast.show(`同步完成 codeInfo 0x${codeInfoAddressText} / 0x${codeInfoByteLengthText},${result.structCount} 项${changedText ? `,${changedText}` : ''}`) } }, async completeParameterStructs() { const result = await parameterGroupService.completeStructInstanceGroupsWithStructFile() if (result && result.completedCount && this.pageToast) { this.pageToast.show(`已补全 ${result.completedCount} 个寄存器组`) } }, toggleParameterPolling() { if (this.data.isStorageAccessProtocol && !this.data.connectedDevice) return const enabled = !this.data.parameterAutoPollEnabled settingsService.setParameterAutoPollEnabled(enabled) if (enabled) { this.scheduleParameterAutoPoll(0) } else { this.clearParameterAutoTimers() } }, async saveParameterGroupsJson() { const count = await parameterGroupService.saveJsonToChat() if (count && this.pageToast) this.pageToast.show(`已保存 ${count} 个寄存器组`) }, toggleParameterGroup(event) { const groupId = event.currentTarget.dataset.groupId if (this.parameterGroupLongPressGuard === groupId) { this.parameterGroupLongPressGuard = '' return } const group = findParameterGroup(getParameterGroupsFromState(this.data), groupId) if (!group) return parameterGroupService.setGroupExpanded(groupId, !group.expanded) }, onParameterRegisterValueInput(event) { parameterGroupService.updateRegisterValue( event.currentTarget.dataset.groupId, Number(event.currentTarget.dataset.index), event.detail.value ) }, async onParameterRegisterValueBlur(event) { const groupId = event.currentTarget.dataset.groupId const registerIndex = Number(event.currentTarget.dataset.index) try { parameterGroupService.validateRegisterInputValue(groupId, registerIndex, event.detail.value) } catch (error) { if (this.pageToast) this.pageToast.show(error.message || '输入值无效', 'error') return } if (!this.data.isStorageAccessProtocol || !this.data.connectedDevice) return const group = findParameterGroup(getParameterGroupsFromState(this.data), groupId) const register = group && group.registers ? group.registers[registerIndex] : null if (!group || !register || !register.isDirty || !group.writable || group.addressOverflow) return this.clearParameterAutoTimers() const ok = await parameterGroupService.writeRegister(groupId, registerIndex) if (this.data.parameterAutoPollEnabled) { this.scheduleParameterAutoPoll(this.data.parameterPollInterval || 100) } if (ok && this.pageToast) { this.pageToast.show(`${register.name || '变量'}已写入`) } }, async readParameterGroup(event) { if (!this.data.connectedDevice) return const groupId = event.currentTarget.dataset.groupId const ok = await parameterGroupService.readGroup(groupId, { maxPacketLength: this.data.parameterMaxPacketLength }) if (ok && this.pageToast) this.pageToast.show('参数组读取完成') }, async writeParameterGroup(event) { if (!this.data.connectedDevice) return const groupId = event.currentTarget.dataset.groupId const ok = await parameterGroupService.writeGroup(groupId) if (ok && this.pageToast) this.pageToast.show('参数组写入完成') }, onParameterGroupTouchStart(event) { const groupId = event.currentTarget.dataset.groupId const touch = (event.changedTouches || [])[0] if (!groupId || !touch) return this.parameterGroupTouchStarts[groupId] = touch.clientX }, onParameterGroupTouchEnd(event) { const groupId = event.currentTarget.dataset.groupId const group = findParameterGroup(getParameterGroupsFromState(this.data), groupId) const touch = (event.changedTouches || [])[0] const startX = this.parameterGroupTouchStarts[groupId] if (!groupId || !group || group.expanded || !touch || !Number.isFinite(startX)) return const deltaX = touch.clientX - startX if (deltaX > 42) { parameterGroupService.setGroupDeleteVisible(groupId, true) } else if (deltaX < -24) { parameterGroupService.setGroupDeleteVisible(groupId, false) } }, onParameterRegisterDragStart(event) { const touch = (event.changedTouches || [])[0] if (!touch) return const groupId = event.currentTarget.dataset.groupId const index = Number(event.currentTarget.dataset.index) const activeParameterGroup = findParameterGroup(getParameterGroupsFromState(this.data), groupId) if (!groupId || !activeParameterGroup || !Number.isInteger(index)) return this.parameterRegisterDrag = { groupId, index, moved: false, rowCenters: [], rowOffset: getFallbackDragRowOffsetPx(this.parameterWindowWidth), startY: touch.clientY, targetIndex: index, translateY: 0 } if (this.data.activeParameterGroupId === groupId) { this.setData({ activeParameterRegisterRows: buildActiveParameterRegisterRows(activeParameterGroup, this.parameterRegisterDrag) }) } this.measureParameterRegisterRows(this.parameterRegisterDrag) }, onParameterRegisterDragMove(event) { const touch = (event.changedTouches || [])[0] if (!touch || !this.parameterRegisterDrag) return const drag = this.parameterRegisterDrag const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId) if (!group) return const translateY = Math.round(touch.clientY - drag.startY) const moved = Math.abs(translateY) > PARAMETER_REGISTER_DRAG_THRESHOLD_PX const targetIndex = moved ? resolveDragTargetIndex(drag, touch.clientY, group.registers.length) : drag.index if ( drag.translateY === translateY && drag.moved === moved && drag.targetIndex === targetIndex ) { return } drag.translateY = translateY drag.moved = moved drag.targetIndex = targetIndex if (this.data.activeParameterGroupId === group.id) { this.setData({ activeParameterRegisterRows: buildActiveParameterRegisterRows(group, drag) }) } }, onParameterRegisterDragEnd(event) { const drag = this.parameterRegisterDrag this.parameterRegisterDrag = null if (!drag || !drag.groupId) return const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId) if (!group) return if (this.data.activeParameterGroupId === group.id) { this.setData({ activeParameterRegisterRows: buildActiveParameterRegisterRows(group, null) }) } if (!drag.moved) return const targetIndex = clampIndex( Number(drag.targetIndex) || drag.index, 0, group.registers.length - 1 ) if (targetIndex === drag.index) return const updatedGroup = parameterGroupService.reorderRegister(drag.groupId, drag.index, targetIndex) if (!updatedGroup) return this.parameterRegisterLongPressGuard = `${drag.groupId}:${targetIndex}` setTimeout(() => { if (this.parameterRegisterLongPressGuard === `${drag.groupId}:${targetIndex}`) { this.parameterRegisterLongPressGuard = '' } }, 260) if (this.data.activeParameterGroupId === updatedGroup.id) { this.setData({ activeParameterGroup: updatedGroup, activeParameterRegisterRows: buildActiveParameterRegisterRows(updatedGroup, null) }) } }, onParameterRegisterDragCancel() { const drag = this.parameterRegisterDrag this.parameterRegisterDrag = null if (!drag || !drag.groupId) return const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId) if (!group || this.data.activeParameterGroupId !== group.id) return this.setData({ activeParameterRegisterRows: buildActiveParameterRegisterRows(group, null) }) }, deleteParameterGroup(event) { const groupId = event.currentTarget.dataset.groupId this.clearParameterAutoTimer(groupId) parameterGroupService.removeGroup(groupId) if (this.data.activeParameterGroupId === groupId) { this.setData({ activeParameterGroup: null, activeParameterGroupId: '', activeParamView: 'parameterGroups' }) } if (this.pageToast) this.pageToast.show('寄存器组已删除') }, clearParameterAutoTimer(groupId) { if (this.parameterGroupPoller) this.parameterGroupPoller.clearTimer(groupId) }, clearParameterAutoTimers() { if (this.parameterGroupPoller) this.parameterGroupPoller.clearAll() }, scheduleVisibleParameterAutoReads() { if (this.parameterGroupPoller) this.parameterGroupPoller.scheduleVisible() }, scheduleParameterAutoPoll(delay) { if (this.parameterGroupPoller) this.parameterGroupPoller.schedule(delay) }, clearParameterRegisterDrag() { if (!this.parameterRegisterDrag) return const drag = this.parameterRegisterDrag this.parameterRegisterDrag = null const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId) this.setData({ activeParameterRegisterRows: buildActiveParameterRegisterRows(group, null) }) }, measureParameterRegisterRows(dragReference) { const query = this.createSelectorQuery() query.selectAll('.generic-register-row').boundingClientRect((rects) => { if (!this.parameterRegisterDrag || this.parameterRegisterDrag !== dragReference) return if (!Array.isArray(rects) || !rects.length) return dragReference.rowCenters = rects.map((rect) => Number(rect.top || 0) + Number(rect.height || 0) / 2) dragReference.rowOffset = Math.max( 1, Math.round(Number((rects[dragReference.index] || {}).height) || dragReference.rowOffset || 0) ) const group = findParameterGroup(getParameterGroupsFromState(this.data), dragReference.groupId) if (!group || this.data.activeParameterGroupId !== group.id) return this.setData({ activeParameterRegisterRows: buildActiveParameterRegisterRows(group, dragReference) }) }).exec() } })