params.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. const {
  2. createParameterDialogState,
  3. findParameterGroup,
  4. findParameterRegister,
  5. getActiveParameterGroup,
  6. getPageState,
  7. getSettingsPageState,
  8. getTransportPageState,
  9. getVisiblePageState,
  10. resolveActiveParamView
  11. } = require('../../features/parameter-groups/view-model.js')
  12. const {
  13. createDialogHandlers,
  14. createParameterGroupPoller,
  15. parameterGroupService
  16. } = require('../../features/parameter-groups/index.js')
  17. const settingsService = require('../../store/settings-store.js')
  18. const themeService = require('../../store/theme-store.js')
  19. const transport = require('../../transport/ble-core.js')
  20. const {
  21. createPageToast
  22. } = require('../../utils/page-toast.js')
  23. const {
  24. PARAMETER_REGISTER_DRAG_THRESHOLD_PX,
  25. buildActiveParameterRegisterRows,
  26. clampIndex,
  27. getFallbackDragRowOffsetPx,
  28. getWindowWidth,
  29. resolveDragTargetIndex
  30. } = require('../../features/parameter-groups/drag-view-model.js')
  31. function getParameterGroupsFromState(state = {}) {
  32. return state.parameterGroups || []
  33. }
  34. Page({
  35. data: {
  36. ...getPageState(),
  37. activeParamView: 'parameterGroups',
  38. activeParameterGroupId: '',
  39. activeParameterRegisterRows: [],
  40. parameterDialog: createParameterDialogState()
  41. },
  42. onTabItemTap() {
  43. this.backToParamsHome()
  44. },
  45. onLoad() {
  46. this.pageToast = createPageToast(this, this.data)
  47. this.parameterGroupPoller = createParameterGroupPoller(() => this.data)
  48. this.parameterGroupTouchStarts = {}
  49. this.parameterWindowWidth = getWindowWidth()
  50. parameterGroupService.init()
  51. themeService.init()
  52. settingsService.init()
  53. this.unsubscribeTheme = themeService.subscribe((themeState) => {
  54. this.setData(themeState)
  55. })
  56. this.unsubscribeTransport = transport.subscribe((transportState) => {
  57. this.setData(getTransportPageState(transportState))
  58. if (transportState.connectedDevice) {
  59. setTimeout(() => this.scheduleVisibleParameterAutoReads(), 0)
  60. } else {
  61. this.clearParameterAutoTimers()
  62. }
  63. })
  64. this.unsubscribeParameterGroups = parameterGroupService.subscribe((parameterState) => {
  65. const activeParameterGroup = getActiveParameterGroup(getParameterGroupsFromState(parameterState), this.data.activeParameterGroupId)
  66. this.setData({
  67. ...parameterState,
  68. activeParameterGroup,
  69. activeParameterRegisterRows: buildActiveParameterRegisterRows(activeParameterGroup, this.parameterRegisterDrag),
  70. activeParamView: this.data.activeParamView === 'parameterGroup' && !activeParameterGroup
  71. ? 'parameterGroups'
  72. : this.data.activeParamView
  73. })
  74. })
  75. this.unsubscribeSettings = settingsService.subscribe((settingsState) => {
  76. const protocolChanged = this.data.protocolMode && this.data.protocolMode !== settingsState.protocolMode
  77. const nextState = getSettingsPageState(this.data, settingsState)
  78. const activeParamView = protocolChanged ? 'parameterGroups' : nextState.activeParamView
  79. const parameterGroups = getParameterGroupsFromState(parameterGroupService.getState())
  80. const activeParameterGroupId = protocolChanged ? '' : this.data.activeParameterGroupId
  81. const activeParameterGroup = getActiveParameterGroup(parameterGroups, activeParameterGroupId)
  82. const safeActiveView = activeParamView === 'parameterGroup' && !activeParameterGroup
  83. ? 'parameterGroups'
  84. : activeParamView
  85. this.clearParameterAutoTimers()
  86. this.setData({
  87. ...nextState,
  88. parameterGroups,
  89. activeParameterGroupId,
  90. activeParamView: safeActiveView,
  91. activeParameterGroup,
  92. activeParameterRegisterRows: buildActiveParameterRegisterRows(activeParameterGroup, this.parameterRegisterDrag)
  93. })
  94. if (safeActiveView === 'parameterGroups' || safeActiveView === 'parameterGroup') {
  95. setTimeout(() => this.scheduleVisibleParameterAutoReads(), 0)
  96. } else {
  97. this.clearParameterAutoTimers()
  98. }
  99. })
  100. },
  101. onShow() {
  102. if (this.pageToast) {
  103. this.pageToast.setActive(true)
  104. }
  105. const pageState = getVisiblePageState(this.data)
  106. this.setData({
  107. ...pageState,
  108. activeParameterGroup: getActiveParameterGroup(getParameterGroupsFromState(pageState), this.data.activeParameterGroupId),
  109. activeParameterRegisterRows: buildActiveParameterRegisterRows(
  110. getActiveParameterGroup(getParameterGroupsFromState(pageState), this.data.activeParameterGroupId),
  111. this.parameterRegisterDrag
  112. )
  113. })
  114. this.pageToast.showFromState(pageState)
  115. this.scheduleVisibleParameterAutoReads()
  116. },
  117. onHide() {
  118. if (this.pageToast) {
  119. this.pageToast.setActive(false)
  120. }
  121. this.clearParameterRegisterDrag()
  122. this.clearParameterAutoTimers()
  123. },
  124. onUnload() {
  125. if (this.pageToast) {
  126. this.pageToast.destroy()
  127. this.pageToast = null
  128. }
  129. if (this.unsubscribeTheme) {
  130. this.unsubscribeTheme()
  131. this.unsubscribeTheme = null
  132. }
  133. if (this.unsubscribeTransport) {
  134. this.unsubscribeTransport()
  135. this.unsubscribeTransport = null
  136. }
  137. if (this.unsubscribeParameterGroups) {
  138. this.unsubscribeParameterGroups()
  139. this.unsubscribeParameterGroups = null
  140. }
  141. if (this.unsubscribeSettings) {
  142. this.unsubscribeSettings()
  143. this.unsubscribeSettings = null
  144. }
  145. this.clearParameterAutoTimers()
  146. },
  147. openParamView(event) {
  148. if (this.pageToast) this.pageToast.clear()
  149. this.closeParameterDraft()
  150. this.clearParameterRegisterDrag()
  151. const activeParamView = event.currentTarget.dataset.view
  152. if (!activeParamView) return
  153. this.setData({
  154. activeParamView
  155. })
  156. if (activeParamView === 'parameterGroups') {
  157. setTimeout(() => this.scheduleVisibleParameterAutoReads(), 0)
  158. }
  159. },
  160. openParameterGroup(event) {
  161. const groupId = event.currentTarget.dataset.groupId
  162. const group = findParameterGroup(getParameterGroupsFromState(this.data), groupId)
  163. if (!group) return
  164. if (this.pageToast) this.pageToast.clear()
  165. this.closeParameterDraft()
  166. this.setData({
  167. activeParameterGroup: group,
  168. activeParameterGroupId: groupId,
  169. activeParamView: 'parameterGroup',
  170. activeParameterRegisterRows: buildActiveParameterRegisterRows(group, this.parameterRegisterDrag)
  171. })
  172. },
  173. backToParamsHome() {
  174. if (this.pageToast) this.pageToast.clear()
  175. this.closeParameterDraft()
  176. this.clearParameterRegisterDrag()
  177. this.clearParameterAutoTimers()
  178. const activeParamView = resolveActiveParamView('', this.data)
  179. this.setData({
  180. activeParameterGroup: null,
  181. activeParameterGroupId: '',
  182. activeParamView,
  183. activeParameterRegisterRows: []
  184. })
  185. if (activeParamView === 'parameterGroups') {
  186. setTimeout(() => this.scheduleVisibleParameterAutoReads(), 0)
  187. }
  188. },
  189. noop() {},
  190. ...createDialogHandlers(parameterGroupService),
  191. async importParameterGroupsJson() {
  192. const count = await parameterGroupService.importJsonFromMessageFile()
  193. if (count && this.pageToast) this.pageToast.show(`已导入 ${count} 个寄存器组`)
  194. },
  195. async syncParameterGroups() {
  196. if (this.data.isModbusProtocol) return
  197. if (!this.data.connectedDevice) return
  198. const result = await parameterGroupService.syncFromStorageAccessCodeInfo({
  199. maxPacketLength: this.data.parameterMaxPacketLength
  200. })
  201. if (result && result.ok && this.pageToast) {
  202. const codeInfoAddressText = result.codeInfoAddressText || Number(result.codeInfoAddress || 0).toString(16).toUpperCase().padStart(4, '0')
  203. const codeInfoByteLengthText = result.codeInfoByteLengthText || Number(result.codeInfoByteLength || 0).toString(16).toUpperCase().padStart(4, '0')
  204. const addedCount = Number(result.addedGroups || 0) + Number(result.addedRegisters || 0)
  205. const updatedCount = Number(result.updatedGroups || 0) + Number(result.updatedRegisters || 0)
  206. const changedText = [
  207. addedCount ? `新增 ${addedCount}` : '',
  208. updatedCount ? `更新 ${updatedCount}` : ''
  209. ].filter(Boolean).join(',')
  210. this.pageToast.show(`同步完成 codeInfo 0x${codeInfoAddressText} / 0x${codeInfoByteLengthText},${result.structCount} 项${changedText ? `,${changedText}` : ''}`)
  211. }
  212. },
  213. async completeParameterStructs() {
  214. const result = await parameterGroupService.completeStructInstanceGroupsWithStructFile()
  215. if (result && result.completedCount && this.pageToast) {
  216. this.pageToast.show(`已补全 ${result.completedCount} 个寄存器组`)
  217. }
  218. },
  219. toggleParameterPolling() {
  220. if (this.data.isStorageAccessProtocol && !this.data.connectedDevice) return
  221. const enabled = !this.data.parameterAutoPollEnabled
  222. settingsService.setParameterAutoPollEnabled(enabled)
  223. if (enabled) {
  224. this.scheduleParameterAutoPoll(0)
  225. } else {
  226. this.clearParameterAutoTimers()
  227. }
  228. },
  229. async saveParameterGroupsJson() {
  230. const count = await parameterGroupService.saveJsonToChat()
  231. if (count && this.pageToast) this.pageToast.show(`已保存 ${count} 个寄存器组`)
  232. },
  233. toggleParameterGroup(event) {
  234. const groupId = event.currentTarget.dataset.groupId
  235. if (this.parameterGroupLongPressGuard === groupId) {
  236. this.parameterGroupLongPressGuard = ''
  237. return
  238. }
  239. const group = findParameterGroup(getParameterGroupsFromState(this.data), groupId)
  240. if (!group) return
  241. parameterGroupService.setGroupExpanded(groupId, !group.expanded)
  242. },
  243. onParameterRegisterValueInput(event) {
  244. parameterGroupService.updateRegisterValue(
  245. event.currentTarget.dataset.groupId,
  246. Number(event.currentTarget.dataset.index),
  247. event.detail.value
  248. )
  249. },
  250. async onParameterRegisterValueBlur(event) {
  251. const groupId = event.currentTarget.dataset.groupId
  252. const registerIndex = Number(event.currentTarget.dataset.index)
  253. try {
  254. parameterGroupService.validateRegisterInputValue(groupId, registerIndex, event.detail.value)
  255. } catch (error) {
  256. if (this.pageToast) this.pageToast.show(error.message || '输入值无效', 'error')
  257. return
  258. }
  259. if (!this.data.isStorageAccessProtocol || !this.data.connectedDevice) return
  260. const group = findParameterGroup(getParameterGroupsFromState(this.data), groupId)
  261. const register = group && group.registers ? group.registers[registerIndex] : null
  262. if (!group || !register || !register.isDirty || !group.writable || group.addressOverflow) return
  263. this.clearParameterAutoTimers()
  264. const ok = await parameterGroupService.writeRegister(groupId, registerIndex)
  265. if (this.data.parameterAutoPollEnabled) {
  266. this.scheduleParameterAutoPoll(this.data.parameterPollInterval || 100)
  267. }
  268. if (ok && this.pageToast) {
  269. this.pageToast.show(`${register.name || '变量'}已写入`)
  270. }
  271. },
  272. async readParameterGroup(event) {
  273. if (!this.data.connectedDevice) return
  274. const groupId = event.currentTarget.dataset.groupId
  275. const ok = await parameterGroupService.readGroup(groupId, {
  276. maxPacketLength: this.data.parameterMaxPacketLength
  277. })
  278. if (ok && this.pageToast) this.pageToast.show('参数组读取完成')
  279. },
  280. async writeParameterGroup(event) {
  281. if (!this.data.connectedDevice) return
  282. const groupId = event.currentTarget.dataset.groupId
  283. const ok = await parameterGroupService.writeGroup(groupId)
  284. if (ok && this.pageToast) this.pageToast.show('参数组写入完成')
  285. },
  286. onParameterGroupTouchStart(event) {
  287. const groupId = event.currentTarget.dataset.groupId
  288. const touch = (event.changedTouches || [])[0]
  289. if (!groupId || !touch) return
  290. this.parameterGroupTouchStarts[groupId] = touch.clientX
  291. },
  292. onParameterGroupTouchEnd(event) {
  293. const groupId = event.currentTarget.dataset.groupId
  294. const group = findParameterGroup(getParameterGroupsFromState(this.data), groupId)
  295. const touch = (event.changedTouches || [])[0]
  296. const startX = this.parameterGroupTouchStarts[groupId]
  297. if (!groupId || !group || group.expanded || !touch || !Number.isFinite(startX)) return
  298. const deltaX = touch.clientX - startX
  299. if (deltaX > 42) {
  300. parameterGroupService.setGroupDeleteVisible(groupId, true)
  301. } else if (deltaX < -24) {
  302. parameterGroupService.setGroupDeleteVisible(groupId, false)
  303. }
  304. },
  305. onParameterRegisterDragStart(event) {
  306. const touch = (event.changedTouches || [])[0]
  307. if (!touch) return
  308. const groupId = event.currentTarget.dataset.groupId
  309. const index = Number(event.currentTarget.dataset.index)
  310. const activeParameterGroup = findParameterGroup(getParameterGroupsFromState(this.data), groupId)
  311. if (!groupId || !activeParameterGroup || !Number.isInteger(index)) return
  312. this.parameterRegisterDrag = {
  313. groupId,
  314. index,
  315. moved: false,
  316. rowCenters: [],
  317. rowOffset: getFallbackDragRowOffsetPx(this.parameterWindowWidth),
  318. startY: touch.clientY,
  319. targetIndex: index,
  320. translateY: 0
  321. }
  322. if (this.data.activeParameterGroupId === groupId) {
  323. this.setData({
  324. activeParameterRegisterRows: buildActiveParameterRegisterRows(activeParameterGroup, this.parameterRegisterDrag)
  325. })
  326. }
  327. this.measureParameterRegisterRows(this.parameterRegisterDrag)
  328. },
  329. onParameterRegisterDragMove(event) {
  330. const touch = (event.changedTouches || [])[0]
  331. if (!touch || !this.parameterRegisterDrag) return
  332. const drag = this.parameterRegisterDrag
  333. const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId)
  334. if (!group) return
  335. const translateY = Math.round(touch.clientY - drag.startY)
  336. const moved = Math.abs(translateY) > PARAMETER_REGISTER_DRAG_THRESHOLD_PX
  337. const targetIndex = moved
  338. ? resolveDragTargetIndex(drag, touch.clientY, group.registers.length)
  339. : drag.index
  340. if (
  341. drag.translateY === translateY
  342. && drag.moved === moved
  343. && drag.targetIndex === targetIndex
  344. ) {
  345. return
  346. }
  347. drag.translateY = translateY
  348. drag.moved = moved
  349. drag.targetIndex = targetIndex
  350. if (this.data.activeParameterGroupId === group.id) {
  351. this.setData({
  352. activeParameterRegisterRows: buildActiveParameterRegisterRows(group, drag)
  353. })
  354. }
  355. },
  356. onParameterRegisterDragEnd(event) {
  357. const drag = this.parameterRegisterDrag
  358. this.parameterRegisterDrag = null
  359. if (!drag || !drag.groupId) return
  360. const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId)
  361. if (!group) return
  362. if (this.data.activeParameterGroupId === group.id) {
  363. this.setData({
  364. activeParameterRegisterRows: buildActiveParameterRegisterRows(group, null)
  365. })
  366. }
  367. if (!drag.moved) return
  368. const targetIndex = clampIndex(
  369. Number(drag.targetIndex) || drag.index,
  370. 0,
  371. group.registers.length - 1
  372. )
  373. if (targetIndex === drag.index) return
  374. const updatedGroup = parameterGroupService.reorderRegister(drag.groupId, drag.index, targetIndex)
  375. if (!updatedGroup) return
  376. this.parameterRegisterLongPressGuard = `${drag.groupId}:${targetIndex}`
  377. setTimeout(() => {
  378. if (this.parameterRegisterLongPressGuard === `${drag.groupId}:${targetIndex}`) {
  379. this.parameterRegisterLongPressGuard = ''
  380. }
  381. }, 260)
  382. if (this.data.activeParameterGroupId === updatedGroup.id) {
  383. this.setData({
  384. activeParameterGroup: updatedGroup,
  385. activeParameterRegisterRows: buildActiveParameterRegisterRows(updatedGroup, null)
  386. })
  387. }
  388. },
  389. onParameterRegisterDragCancel() {
  390. const drag = this.parameterRegisterDrag
  391. this.parameterRegisterDrag = null
  392. if (!drag || !drag.groupId) return
  393. const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId)
  394. if (!group || this.data.activeParameterGroupId !== group.id) return
  395. this.setData({
  396. activeParameterRegisterRows: buildActiveParameterRegisterRows(group, null)
  397. })
  398. },
  399. deleteParameterGroup(event) {
  400. const groupId = event.currentTarget.dataset.groupId
  401. this.clearParameterAutoTimer(groupId)
  402. parameterGroupService.removeGroup(groupId)
  403. if (this.data.activeParameterGroupId === groupId) {
  404. this.setData({
  405. activeParameterGroup: null,
  406. activeParameterGroupId: '',
  407. activeParamView: 'parameterGroups'
  408. })
  409. }
  410. if (this.pageToast) this.pageToast.show('寄存器组已删除')
  411. },
  412. clearParameterAutoTimer(groupId) {
  413. if (this.parameterGroupPoller) this.parameterGroupPoller.clearTimer(groupId)
  414. },
  415. clearParameterAutoTimers() {
  416. if (this.parameterGroupPoller) this.parameterGroupPoller.clearAll()
  417. },
  418. scheduleVisibleParameterAutoReads() {
  419. if (this.parameterGroupPoller) this.parameterGroupPoller.scheduleVisible()
  420. },
  421. scheduleParameterAutoPoll(delay) {
  422. if (this.parameterGroupPoller) this.parameterGroupPoller.schedule(delay)
  423. },
  424. clearParameterRegisterDrag() {
  425. if (!this.parameterRegisterDrag) return
  426. const drag = this.parameterRegisterDrag
  427. this.parameterRegisterDrag = null
  428. const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId)
  429. this.setData({
  430. activeParameterRegisterRows: buildActiveParameterRegisterRows(group, null)
  431. })
  432. },
  433. measureParameterRegisterRows(dragReference) {
  434. const query = this.createSelectorQuery()
  435. query.selectAll('.generic-register-row').boundingClientRect((rects) => {
  436. if (!this.parameterRegisterDrag || this.parameterRegisterDrag !== dragReference) return
  437. if (!Array.isArray(rects) || !rects.length) return
  438. dragReference.rowCenters = rects.map((rect) => Number(rect.top || 0) + Number(rect.height || 0) / 2)
  439. dragReference.rowOffset = Math.max(
  440. 1,
  441. Math.round(Number((rects[dragReference.index] || {}).height) || dragReference.rowOffset || 0)
  442. )
  443. const group = findParameterGroup(getParameterGroupsFromState(this.data), dragReference.groupId)
  444. if (!group || this.data.activeParameterGroupId !== group.id) return
  445. this.setData({
  446. activeParameterRegisterRows: buildActiveParameterRegisterRows(group, dragReference)
  447. })
  448. }).exec()
  449. }
  450. })