@flexem/fc-gui 3.0.0-alpha.15 → 3.0.0-alpha.151

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/CHANGELOG.md +429 -1
  2. package/assets/img/black_first_page.png +0 -0
  3. package/assets/img/black_last_page.png +0 -0
  4. package/assets/img/black_next_page.png +0 -0
  5. package/assets/img/black_previous_page.png +0 -0
  6. package/bundles/@flexem/fc-gui.umd.js +24059 -19951
  7. package/bundles/@flexem/fc-gui.umd.js.map +1 -1
  8. package/bundles/@flexem/fc-gui.umd.min.js +5 -5
  9. package/bundles/@flexem/fc-gui.umd.min.js.map +1 -1
  10. package/communication/variable/variable-communicator.d.ts +4 -0
  11. package/communication/variable/variable-value.d.ts +4 -1
  12. package/communication/variable/variable-value.js +4 -1
  13. package/communication/variable/variable-value.metadata.json +1 -1
  14. package/config/alarm/alarm.store.d.ts +6 -0
  15. package/config/alarm/alarm.store.js +0 -0
  16. package/config/alarm/alarm.store.metadata.json +1 -0
  17. package/config/alarm/get-alarms-args.d.ts +12 -0
  18. package/config/alarm/get-alarms-args.js +13 -0
  19. package/config/alarm/get-alarms-args.metadata.json +1 -0
  20. package/config/alarm/index.d.ts +2 -0
  21. package/config/alarm/index.js +1 -0
  22. package/config/alarm/index.metadata.json +1 -0
  23. package/config/config-store.d.ts +2 -0
  24. package/config/index.d.ts +1 -0
  25. package/config/index.js +1 -0
  26. package/config/index.metadata.json +1 -1
  27. package/elements/air-quality/air-quality-element.d.ts +31 -0
  28. package/elements/air-quality/air-quality-element.js +194 -0
  29. package/elements/air-quality/air-quality-element.metadata.json +1 -0
  30. package/elements/alarm/alarm-element.d.ts +69 -0
  31. package/elements/alarm/alarm-element.js +497 -0
  32. package/elements/alarm/alarm-element.metadata.json +1 -0
  33. package/elements/bar-graph-element.d.ts +10 -2
  34. package/elements/bar-graph-element.js +135 -5
  35. package/elements/bar-graph-element.metadata.json +1 -1
  36. package/elements/base/readable-element.d.ts +6 -1
  37. package/elements/base/readable-element.js +65 -2
  38. package/elements/base/readable-element.metadata.json +1 -1
  39. package/elements/base/state-control-element.d.ts +3 -1
  40. package/elements/base/state-control-element.js +3 -0
  41. package/elements/datetime-display/datetime-display-element.d.ts +1 -0
  42. package/elements/datetime-display/datetime-display-element.js +10 -2
  43. package/elements/datetime-display/datetime-display-element.metadata.json +1 -1
  44. package/elements/datetime-display/time-zone-select-json.d.ts +8 -0
  45. package/elements/datetime-display/time-zone-select-json.js +558 -0
  46. package/elements/historical-curve/historical-curve.element.d.ts +33 -3
  47. package/elements/historical-curve/historical-curve.element.js +368 -26
  48. package/elements/historical-curve/historical-curve.element.metadata.json +1 -1
  49. package/elements/main-element.d.ts +1 -0
  50. package/elements/main-element.js +59 -9
  51. package/elements/main-element.metadata.json +1 -1
  52. package/elements/meter-element.d.ts +7 -1
  53. package/elements/meter-element.js +76 -7
  54. package/elements/meter-element.metadata.json +1 -1
  55. package/elements/numerical-display/numerical-display-element.d.ts +16 -3
  56. package/elements/numerical-display/numerical-display-element.js +83 -11
  57. package/elements/numerical-display/numerical-display-element.metadata.json +1 -1
  58. package/elements/per-view-variable-communicator.d.ts +3 -0
  59. package/elements/per-view-variable-communicator.js +15 -1
  60. package/elements/per-view-variable-communicator.metadata.json +1 -1
  61. package/elements/ring-graph/ring-graph-element.d.ts +13 -1
  62. package/elements/ring-graph/ring-graph-element.js +164 -3
  63. package/elements/ring-graph/ring-graph-element.metadata.json +1 -1
  64. package/elements/scroll-alarm/scroll-alarm-element.d.ts +74 -0
  65. package/elements/scroll-alarm/scroll-alarm-element.js +761 -0
  66. package/elements/scroll-alarm/scroll-alarm-element.metadata.json +1 -0
  67. package/elements/shared/graph/graph-state-element.d.ts +1 -0
  68. package/elements/shared/graph/graph-state-element.js +30 -4
  69. package/elements/shared/graph/graph-state-element.metadata.json +1 -1
  70. package/elements/shared/text/text-element.d.ts +9 -0
  71. package/elements/shared/text/text-element.js +34 -3
  72. package/elements/shared/text/text-element.metadata.json +1 -1
  73. package/elements/shared/text/text-state-element.d.ts +28 -2
  74. package/elements/shared/text/text-state-element.js +188 -65
  75. package/elements/shared/text/text-state-element.metadata.json +1 -1
  76. package/elements/static-elements/hyperlink-element.d.ts +24 -2
  77. package/elements/static-elements/hyperlink-element.js +124 -3
  78. package/elements/static-elements/hyperlink-element.metadata.json +1 -1
  79. package/elements/static-elements/text-element.d.ts +24 -2
  80. package/elements/static-elements/text-element.js +120 -3
  81. package/elements/static-elements/text-element.metadata.json +1 -1
  82. package/elements/switch-indicator-light/bit-indicator-light-operator.d.ts +1 -0
  83. package/elements/switch-indicator-light/bit-indicator-light-operator.js +5 -2
  84. package/elements/switch-indicator-light/bit-indicator-light-operator.metadata.json +1 -1
  85. package/elements/switch-indicator-light/switch-indicator-light-element.d.ts +11 -2
  86. package/elements/switch-indicator-light/switch-indicator-light-element.js +55 -11
  87. package/elements/switch-indicator-light/switch-indicator-light-element.metadata.json +1 -1
  88. package/elements/switch-indicator-light/word-indicator-light-operator.d.ts +1 -0
  89. package/elements/switch-indicator-light/word-indicator-light-operator.js +5 -2
  90. package/elements/switch-indicator-light/word-indicator-light-operator.metadata.json +1 -1
  91. package/elements/video/video-element.d.ts +10 -0
  92. package/elements/video/video-element.js +119 -21
  93. package/elements/video/video-element.metadata.json +1 -1
  94. package/elements/view-operation/view-operation.element.d.ts +24 -2
  95. package/elements/view-operation/view-operation.element.js +128 -4
  96. package/elements/view-operation/view-operation.element.metadata.json +1 -1
  97. package/elements/weather/weater-element.js +0 -1
  98. package/gui/gui-context.d.ts +12 -2
  99. package/gui/gui-host.d.ts +1 -1
  100. package/gui/gui-view.d.ts +4 -1
  101. package/gui/gui-view.js +51 -7
  102. package/gui/gui-view.metadata.json +1 -1
  103. package/gui/gui.component.d.ts +3 -0
  104. package/gui/gui.component.js +15 -2
  105. package/gui/gui.component.metadata.json +1 -1
  106. package/localization/localization.service.d.ts +7 -0
  107. package/localization/localization.service.js +10 -3
  108. package/localization/localization.service.metadata.json +1 -1
  109. package/localization/localization.service.zh_CN.js +8 -1
  110. package/localization/localization.service.zh_CN.metadata.json +1 -1
  111. package/modal/write-value/write-value-modal-args.d.ts +5 -1
  112. package/modal/write-value/write-value-modal-args.js +3 -1
  113. package/modal/write-value/write-value-modal-args.metadata.json +1 -1
  114. package/modal/write-value/write-value-modal.component.d.ts +13 -7
  115. package/modal/write-value/write-value-modal.component.html +10 -5
  116. package/modal/write-value/write-value-modal.component.js +87 -15
  117. package/modal/write-value/write-value-modal.component.metadata.json +1 -1
  118. package/model/air-quality/air-quality-info.d.ts +23 -0
  119. package/model/air-quality/air-quality-info.js +4 -0
  120. package/model/air-quality/air-quality-info.metadata.json +1 -0
  121. package/model/air-quality/air-quality.model.d.ts +7 -0
  122. package/model/air-quality/air-quality.model.js +0 -0
  123. package/model/air-quality/air-quality.model.metadata.json +1 -0
  124. package/model/alarm/alarm.model.d.ts +13 -0
  125. package/model/alarm/alarm.model.js +0 -0
  126. package/model/alarm/alarm.model.metadata.json +1 -0
  127. package/model/bar-graph/bar-graph.d.ts +4 -0
  128. package/model/base/font-setting-model.d.ts +12 -1
  129. package/model/base/font-setting-model.metadata.json +1 -1
  130. package/model/base/readable-model.d.ts +4 -0
  131. package/model/datetime-display/datetime-display.d.ts +1 -0
  132. package/model/historical-curve/historical-curve-axis-settings.d.ts +11 -0
  133. package/model/historical-curve/historical-curve-axis-settings.js +5 -0
  134. package/model/historical-curve/historical-curve-axis-settings.metadata.json +1 -1
  135. package/model/historical-curve/historical-curve-chanel.model.d.ts +8 -0
  136. package/model/meter/meter.d.ts +4 -0
  137. package/model/ring-graph/ring-graph.model.d.ts +8 -0
  138. package/model/scroll-alarm/scroll-alarm.model.d.ts +21 -0
  139. package/model/scroll-alarm/scroll-alarm.model.js +0 -0
  140. package/model/scroll-alarm/scroll-alarm.model.metadata.json +1 -0
  141. package/model/shared/text/text.d.ts +3 -0
  142. package/model/switch-indicator-light/switch-indicator-light.d.ts +2 -0
  143. package/model/view-operation/view-operation-element.model.d.ts +7 -1
  144. package/package.json +1 -1
  145. package/public_api.js +1 -0
  146. package/remote/communication/variable/remote-variable-communicator.d.ts +27 -0
  147. package/remote/communication/variable/remote-variable-communicator.js +148 -3
  148. package/remote/communication/variable/remote-variable-communicator.metadata.json +1 -1
  149. package/remote/communication/variable/remote-variable-protocol.d.ts +5 -0
  150. package/service/index.d.ts +4 -0
  151. package/service/index.js +1 -0
  152. package/service/index.metadata.json +1 -1
  153. package/service/language.service.d.ts +37 -0
  154. package/service/language.service.js +0 -0
  155. package/service/language.service.metadata.json +1 -0
  156. package/service/released-variable/index.d.ts +1 -0
  157. package/service/released-variable/index.js +0 -0
  158. package/service/released-variable/index.metadata.json +1 -0
  159. package/service/released-variable/released-variable.service.d.ts +4 -0
  160. package/service/released-variable/released-variable.service.js +0 -0
  161. package/service/released-variable/released-variable.service.metadata.json +1 -0
  162. package/service/system-text-library.service.d.ts +76 -0
  163. package/service/system-text-library.service.js +28 -0
  164. package/service/system-text-library.service.metadata.json +1 -0
  165. package/service/text-library.service.d.ts +49 -0
  166. package/service/text-library.service.js +0 -0
  167. package/service/text-library.service.metadata.json +1 -0
  168. package/service/weather.service.d.ts +1 -0
  169. package/shared/gui-consts.d.ts +3 -0
  170. package/shared/gui-consts.js +3 -0
  171. package/shared/gui-consts.metadata.json +1 -1
  172. package/utils/data-type/fbox-data-type.service.js +40 -0
@@ -0,0 +1,761 @@
1
+ import * as d3 from 'd3';
2
+ import { ConditionalDynamicDisplayElement } from '../base/conditional-dynamic-display-element';
3
+ import { GetAlarmsArgs } from '../../config';
4
+ import * as moment from 'moment';
5
+ import { LOGGER_SERVICE_TOKEN } from '../../logger';
6
+ var ScrollAlarmElementStatus;
7
+ (function (ScrollAlarmElementStatus) {
8
+ ScrollAlarmElementStatus[ScrollAlarmElementStatus["Normal"] = 0] = "Normal";
9
+ ScrollAlarmElementStatus[ScrollAlarmElementStatus["Loading"] = 1] = "Loading"; // 加载中
10
+ })(ScrollAlarmElementStatus || (ScrollAlarmElementStatus = {}));
11
+ export class ScrollAlarmElement extends ConditionalDynamicDisplayElement {
12
+ constructor(element, injector, permissionChecker, variableCommunicator, variableStore, alarmsStore, signalRAppId) {
13
+ var _a, _b, _c, _d, _e, _f;
14
+ super(element, permissionChecker, variableCommunicator, variableStore, signalRAppId);
15
+ this.alarmsStore = alarmsStore;
16
+ this.elementStatus = ScrollAlarmElementStatus.Loading;
17
+ this.alarmNames = [];
18
+ this.isWaitingForAlarmRefresh = false; // 是否正在等待刷新(节流期间)
19
+ this.isScrolling = false;
20
+ this.isDisposed = false;
21
+ this.isRefreshingCurrentPage = false; // 标记是否正在刷新当前页(而不是加载新页)
22
+ // 新的分页和滚动逻辑变量
23
+ this.currentPage = 1;
24
+ this.maxResultCount = 500; // 每页数据量
25
+ this.displayedItems = []; // 当前显示的所有数据
26
+ this.pageWidths = []; // 存储每页的宽度
27
+ this.totalWidth = 0; // 所有显示数据的总宽度
28
+ this.currentLeft = 0;
29
+ this.autoCycle = true;
30
+ this.hasMoreData = true;
31
+ this.isLoadingNextPage = false;
32
+ this.hasTriedFirstPage = false; // 是否已经尝试加载过第一页
33
+ this.isSimulateMode = false; // 是否是模拟运行模式
34
+ this.rootElement.selectAll('*').remove();
35
+ this.setStatusAsLoading();
36
+ this.logger = injector.get(LOGGER_SERVICE_TOKEN);
37
+ this.variableCommunicator = variableCommunicator;
38
+ this.autoCycle = (_b = (_a = this.model.generalSetting) === null || _a === void 0 ? void 0 : _a.autoCycle) !== null && _b !== void 0 ? _b : true;
39
+ this.maxResultCount = (_d = (_c = this.model.generalSetting) === null || _c === void 0 ? void 0 : _c.pageSize) !== null && _d !== void 0 ? _d : 500;
40
+ // 检测是否是模拟运行模式(通过检查 alarmsStore 的标记属性,不受代码混淆影响)
41
+ this.isSimulateMode = alarmsStore && alarmsStore.isSimulationStore === true;
42
+ if (this.model.filterSetting && this.model.filterSetting.detailsData && this.model.filterSetting.detailsData.length > 0) {
43
+ this.alarmNames = this.model.filterSetting.detailsData.map(item => item.name);
44
+ // 如果是模拟运行模式,使用静态显示逻辑(类似设计态)
45
+ if (this.isSimulateMode) {
46
+ // 初始化显示容器
47
+ this.initDisplayContainer();
48
+ // 获取字体设置
49
+ this.headerFont = ((_e = this.model.generalSetting) === null || _e === void 0 ? void 0 : _e.headerFont) || {};
50
+ // 使用静态显示逻辑:只显示字段标签
51
+ this.renderStaticDisplay();
52
+ this.setStatusAsNormal();
53
+ }
54
+ else {
55
+ // 真实运行模式:使用原有的数据查询逻辑
56
+ this.updateQueryTimeRange();
57
+ // 监听告警更新 (带节流,1秒内只执行一次)
58
+ variableCommunicator.subscribeUserDeviceAlarms(signalRAppId).subscribe(alarms => {
59
+ alarms.forEach(alarm => {
60
+ if (this.alarmNames.indexOf(alarm.name) !== -1) {
61
+ // 如果已经在等待刷新,直接返回,避免重复清理和创建定时器
62
+ if (this.isWaitingForAlarmRefresh) {
63
+ return;
64
+ }
65
+ // 标记为等待刷新状态
66
+ this.isWaitingForAlarmRefresh = true;
67
+ // 设置节流定时器,1秒后执行
68
+ this.alarmRefreshThrottleId = setTimeout(() => {
69
+ // 收到新告警消息,实时替换当前滚动内容,不影响滚动
70
+ this.replaceCurrentScrollingContent();
71
+ // 重置等待刷新标志
72
+ this.isWaitingForAlarmRefresh = false;
73
+ }, 1000);
74
+ return;
75
+ }
76
+ });
77
+ });
78
+ // 初始化显示容器
79
+ this.initDisplayContainer();
80
+ // 获取字体设置 - 参考 text-element.ts 的实现方式
81
+ this.headerFont = ((_f = this.model.generalSetting) === null || _f === void 0 ? void 0 : _f.headerFont) || {};
82
+ this.getAlarmData();
83
+ }
84
+ }
85
+ else {
86
+ // 未配置告警(没有勾选过告警),不执行,直接展示空
87
+ this.displayedItems = [];
88
+ // 初始化显示容器以显示空内容
89
+ this.initDisplayContainer();
90
+ if (this.allAlarmsContainer) {
91
+ while (this.allAlarmsContainer.firstChild) {
92
+ this.allAlarmsContainer.removeChild(this.allAlarmsContainer.firstChild);
93
+ }
94
+ this.totalWidth = 0;
95
+ this.pageWidths = [];
96
+ }
97
+ this.setStatusAsNormal();
98
+ }
99
+ }
100
+ dispose() {
101
+ this.isDisposed = true;
102
+ clearInterval(this.scrollIntervalId);
103
+ clearTimeout(this.getAlarmDataId);
104
+ clearTimeout(this.alarmRefreshThrottleId);
105
+ if (this.element && this.element.tooltip) {
106
+ this.element.tooltip.hidden(true);
107
+ }
108
+ this.logger.debug(`[GUI]Dispose Scroll Alarm Refresh Interval:${d3.time.format('%x %X')(new Date())}`);
109
+ // 重置所有状态
110
+ this.isScrolling = false;
111
+ }
112
+ getAlarmData() {
113
+ if (this.isDisposed || this.isLoadingNextPage)
114
+ return;
115
+ this.isLoadingNextPage = true;
116
+ // Only show loading for the first page load
117
+ if (this.currentPage === 1 && this.displayedItems.length === 0) {
118
+ this.setStatusAsLoading();
119
+ }
120
+ clearTimeout(this.getAlarmDataId);
121
+ this.getAlarmDataId = setTimeout(() => {
122
+ var _a, _b;
123
+ this.updateQueryTimeRange();
124
+ const skipCount = (this.currentPage - 1) * this.maxResultCount;
125
+ // 从配置中获取状态和排序设置
126
+ const selectedStates = (_a = this.model.generalSetting) === null || _a === void 0 ? void 0 : _a.selectState; // 前端使用的字段名
127
+ const timeSort = (_b = this.model.generalSetting) === null || _b === void 0 ? void 0 : _b.timeSort; // 前端使用的字段名
128
+ // 构建排序字符串 - API 字段名是 sorting
129
+ let sorting;
130
+ if (timeSort === '0') {
131
+ sorting = 'TriggeredTime ASC'; // 升序
132
+ }
133
+ else if (timeSort === '1') {
134
+ sorting = 'TriggeredTime DESC'; // 降序
135
+ }
136
+ // 后端 API 的 state 参数现已支持数组,直接传递 selectedStates
137
+ // 兼容处理:如果 selectedStates 为空数组或 undefined,传 undefined(不过滤)
138
+ const stateFilter = (selectedStates && Array.isArray(selectedStates) && selectedStates.length > 0)
139
+ ? selectedStates
140
+ : undefined;
141
+ const input = new GetAlarmsArgs(this.alarmNames, this.startTime, this.endTime, this.maxResultCount, skipCount, stateFilter, // 传递状态数组到后端,后端已支持数组过滤
142
+ sorting // 传递排序参数
143
+ );
144
+ this.alarmsStore.getHistoryAlarms(input).subscribe(result => {
145
+ this.isLoadingNextPage = false;
146
+ if (!result.error && result.items && result.items.length > 0) {
147
+ const newPage = result.items;
148
+ // 检查是否还有更多数据
149
+ // 如果是第一页且数据量少于 maxResultCount,说明总数据不超过一页
150
+ if (this.currentPage === 1 && newPage.length < this.maxResultCount) {
151
+ // 总数据不超过一页,不需要加载更多数据
152
+ this.hasMoreData = false;
153
+ }
154
+ else if (!this.autoCycle && newPage.length < this.maxResultCount) {
155
+ // 非自动循环模式,且当前页数据不足,说明没有更多数据
156
+ this.hasMoreData = false;
157
+ }
158
+ else {
159
+ this.hasMoreData = true;
160
+ }
161
+ if (this.isRefreshingCurrentPage) {
162
+ // 刷新当前页模式:替换最后一页的数据,保持滚动位置
163
+ const itemsPerPage = this.maxResultCount;
164
+ const startIndex = (this.currentPage - 1) * itemsPerPage;
165
+ // 移除当前页的旧数据
166
+ this.displayedItems.splice(startIndex, itemsPerPage);
167
+ // 移除当前页的DOM元素
168
+ const oldPageWidth = this.pageWidths.pop() || 0;
169
+ this.totalWidth -= oldPageWidth;
170
+ const elementsToRemove = Array.from(this.allAlarmsContainer.children).slice(startIndex, startIndex + itemsPerPage);
171
+ elementsToRemove.forEach(el => {
172
+ this.allAlarmsContainer.removeChild(el);
173
+ });
174
+ // 插入新的当前页数据
175
+ this.displayedItems.splice(startIndex, 0, ...newPage);
176
+ // 渲染新页(会追加到末尾)
177
+ this.renderNewPage(newPage);
178
+ // 更新页码
179
+ this.currentPage++;
180
+ // 重置标志
181
+ this.isRefreshingCurrentPage = false;
182
+ }
183
+ else {
184
+ // 正常加载新页模式:追加数据
185
+ this.displayedItems = this.displayedItems.concat(newPage);
186
+ // 渲染新页
187
+ this.renderNewPage(newPage);
188
+ // 更新页码
189
+ this.currentPage++;
190
+ }
191
+ // 检查是否需要删除旧页(保持最多3页)
192
+ if (this.currentPage > 4) {
193
+ this.removeOldestPage();
194
+ }
195
+ // 如果是首次加载或还没有开始滚动,初始化滚动
196
+ if (!this.isScrolling && this.displayedItems.length > 0) {
197
+ this.initScrolling();
198
+ }
199
+ this.setStatusAsNormal();
200
+ this.hasTriedFirstPage = true;
201
+ }
202
+ else {
203
+ // 如果没有数据
204
+ if (this.currentPage === 1 && !this.hasTriedFirstPage) {
205
+ // 第一次加载就没有数据,设置为正常状态,停止加载
206
+ this.hasTriedFirstPage = true;
207
+ this.hasMoreData = false;
208
+ this.setStatusAsNormal();
209
+ // 清空显示
210
+ this.displayedItems = [];
211
+ if (this.allAlarmsContainer) {
212
+ while (this.allAlarmsContainer.firstChild) {
213
+ this.allAlarmsContainer.removeChild(this.allAlarmsContainer.firstChild);
214
+ }
215
+ this.totalWidth = 0;
216
+ this.pageWidths = [];
217
+ }
218
+ }
219
+ else if (this.autoCycle && this.hasTriedFirstPage && this.displayedItems.length > 0) {
220
+ // 不是第一页,且之前有数据,自动循环开启,循环回到第一页
221
+ this.currentPage = 1;
222
+ this.getAlarmData();
223
+ }
224
+ else {
225
+ // 其他情况:停止加载
226
+ this.hasMoreData = false;
227
+ this.setStatusAsNormal();
228
+ }
229
+ }
230
+ }, error => {
231
+ this.isLoadingNextPage = false;
232
+ this.setStatusAsNormal();
233
+ this.logger.error('Failed to get alarm data:', error);
234
+ });
235
+ }, 500);
236
+ }
237
+ initDisplayContainer() {
238
+ var _a;
239
+ const elementHeight = this.model.size.height;
240
+ const elementWidth = this.model.size.width;
241
+ const bgColor = ((_a = this.model.generalSetting) === null || _a === void 0 ? void 0 : _a.headerFillColor) || '#ffffff';
242
+ const clipId = 'scroll-alarm-clip-' + Math.random().toString(36).slice(2);
243
+ // 背景矩形
244
+ this.bgRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
245
+ this.bgRect.setAttribute('x', '0');
246
+ this.bgRect.setAttribute('y', '0');
247
+ this.bgRect.setAttribute('width', elementWidth.toString());
248
+ this.bgRect.setAttribute('height', elementHeight.toString());
249
+ this.bgRect.setAttribute('fill', bgColor);
250
+ this.bgRect.setAttribute('stroke', '#8ea0aa');
251
+ this.bgRect.setAttribute('stroke-width', '1');
252
+ this.$element[0].appendChild(this.bgRect);
253
+ // clipPath 限制文字滚动区域
254
+ const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
255
+ const clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
256
+ clipPath.setAttribute('id', clipId);
257
+ const clipRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
258
+ clipRect.setAttribute('x', '2');
259
+ clipRect.setAttribute('y', '1');
260
+ clipRect.setAttribute('width', (elementWidth - 4).toString());
261
+ clipRect.setAttribute('height', (elementHeight - 2).toString());
262
+ clipPath.appendChild(clipRect);
263
+ defs.appendChild(clipPath);
264
+ this.$element[0].appendChild(defs);
265
+ // 滚动内容的 g 容器,套上 clipPath
266
+ this.scrollGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
267
+ this.scrollGroup.setAttribute('clip-path', `url(#${clipId})`);
268
+ this.$element[0].appendChild(this.scrollGroup);
269
+ // 内部 g 用于整体平移(滚动动画改这个的 transform)
270
+ this.allAlarmsContainer = document.createElementNS('http://www.w3.org/2000/svg', 'g');
271
+ this.scrollGroup.appendChild(this.allAlarmsContainer);
272
+ // 初始位置在右侧屏幕外
273
+ this.currentLeft = elementWidth;
274
+ this.allAlarmsContainer.setAttribute('transform', `translate(${elementWidth}, 0)`);
275
+ }
276
+ _buildFontAttrs() {
277
+ const f = this.headerFont || {};
278
+ const fontSize = parseInt((f.fontSize || '16px'), 10);
279
+ return {
280
+ fontSize,
281
+ fontFamily: f.fontFamily || 'Microsoft YaHei',
282
+ color: f.color || '#e33c39',
283
+ isBold: !!f.isBold,
284
+ isItalic: !!f.isItalic,
285
+ isUnderline: !!f.isUnderline
286
+ };
287
+ }
288
+ _createSvgTextNode(textContent) {
289
+ const elementHeight = this.model.size.height;
290
+ const fa = this._buildFontAttrs();
291
+ const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
292
+ const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
293
+ const textY = elementHeight / 2;
294
+ text.setAttribute('y', textY.toString());
295
+ text.setAttribute('dominant-baseline', 'central');
296
+ text.setAttribute('fill', fa.color);
297
+ text.setAttribute('font-size', fa.fontSize.toString());
298
+ text.setAttribute('font-family', fa.fontFamily);
299
+ if (fa.isBold) {
300
+ text.setAttribute('font-weight', 'bold');
301
+ }
302
+ if (fa.isItalic) {
303
+ text.setAttribute('font-style', 'italic');
304
+ }
305
+ // 不使用 SVG text-decoration,避免跨平台下划线位置不一致(Chrome/Android 与 iOS Safari 渲染差异)
306
+ text.textContent = textContent;
307
+ g.appendChild(text);
308
+ if (fa.isUnderline) {
309
+ // dominant-baseline: central 下,基线约在 textY + fontSize*0.35
310
+ // 下划线紧贴文字底部(基线下 fontSize*0.1),再加 2px 间距避免与字形粘连
311
+ // stroke-width 随字号缩放,参照浏览器标准约 fontSize/14,最小 1px
312
+ const lineY = textY + Math.round(fa.fontSize * 0.35) + Math.round(fa.fontSize * 0.1) + 2;
313
+ const strokeWidth = Math.max(1, Math.round(fa.fontSize / 14));
314
+ const underline = document.createElementNS('http://www.w3.org/2000/svg', 'line');
315
+ underline.setAttribute('y1', lineY.toString());
316
+ underline.setAttribute('y2', lineY.toString());
317
+ underline.setAttribute('x1', '0');
318
+ underline.setAttribute('x2', '0'); // 宽度在 append 后由 _updateUnderlineWidth 设置
319
+ underline.setAttribute('stroke', fa.color);
320
+ underline.setAttribute('stroke-width', strokeWidth.toString());
321
+ underline.setAttribute('class', 'text-underline');
322
+ g.appendChild(underline);
323
+ }
324
+ return g;
325
+ }
326
+ _getSvgTextWidth(g) {
327
+ const text = g.querySelector('text');
328
+ if (!text) {
329
+ return 100;
330
+ }
331
+ return text.getComputedTextLength
332
+ ? text.getComputedTextLength()
333
+ : (text.getBBox ? text.getBBox().width : 100);
334
+ }
335
+ _updateUnderlineWidth(g, width) {
336
+ const underline = g.querySelector('line.text-underline');
337
+ if (underline) {
338
+ underline.setAttribute('x2', width.toString());
339
+ }
340
+ }
341
+ renderNewPage(pageData) {
342
+ const GAP = 80;
343
+ let pageWidth = 0;
344
+ // 新页的起始 x = 已有总宽度 + gap(如果已有内容)
345
+ let xOffset = this.totalWidth > 0 ? this.totalWidth + GAP : 0;
346
+ pageData.forEach((alarm) => {
347
+ var _a, _b, _c, _d, _e, _f, _g, _h;
348
+ const contentParts = [];
349
+ if (((_a = this.model.generalSetting) === null || _a === void 0 ? void 0 : _a.showTriggerTime) && alarm.triggeredTime) {
350
+ contentParts.push(moment(alarm.triggeredTime).format('YYYY/MM/DD HH:mm:ss'));
351
+ }
352
+ const language = ((_d = (_c = (_b = window.abp) === null || _b === void 0 ? void 0 : _b.localization) === null || _c === void 0 ? void 0 : _c.currentLanguage) === null || _d === void 0 ? void 0 : _d.name) || 'zh-Hans';
353
+ const isChinese = language === 'zh-Hans' || language === 'zh';
354
+ if (((_e = this.model.generalSetting) === null || _e === void 0 ? void 0 : _e.showAlarmLevel) && alarm.alarmLevel !== undefined) {
355
+ const levelMap = isChinese ? ['警告', '次要', '主要', '严重'] : ['Warning', 'Minor', 'Major', 'Critical'];
356
+ contentParts.push(levelMap[alarm.alarmLevel] || (isChinese ? '警告' : 'Warning'));
357
+ }
358
+ if (((_f = this.model.generalSetting) === null || _f === void 0 ? void 0 : _f.showAlarmName) && alarm.name) {
359
+ contentParts.push(alarm.name);
360
+ }
361
+ if (((_g = this.model.generalSetting) === null || _g === void 0 ? void 0 : _g.showAlarmContent) && alarm.message) {
362
+ contentParts.push(alarm.message);
363
+ }
364
+ if (((_h = this.model.generalSetting) === null || _h === void 0 ? void 0 : _h.showAlarmState) && alarm.state !== undefined) {
365
+ const stateMap = isChinese
366
+ ? ['触发/未确认', '触发/已确认', '恢复/未确认', '恢复/已确认']
367
+ : ['Triggered/Unconfirmed', 'Triggered/Confirmed', 'Restored/Unconfirmed', 'Restored/Confirmed'];
368
+ contentParts.push(stateMap[alarm.state] || (isChinese ? '触发/未确认' : 'Triggered/Unconfirmed'));
369
+ }
370
+ const textNode = this._createSvgTextNode(contentParts.join(' '));
371
+ textNode.setAttribute('transform', `translate(${xOffset}, 0)`);
372
+ this.allAlarmsContainer.appendChild(textNode);
373
+ const textWidth = this._getSvgTextWidth(textNode);
374
+ this._updateUnderlineWidth(textNode, textWidth);
375
+ xOffset += textWidth + GAP;
376
+ pageWidth += (pageWidth > 0 ? GAP : 0) + textWidth;
377
+ });
378
+ this.pageWidths.push(pageWidth);
379
+ this.totalWidth += (this.totalWidth > 0 && pageWidth > 0 ? GAP : 0) + pageWidth;
380
+ }
381
+ _rebaseTextNodes() {
382
+ const GAP = 80;
383
+ let x = 0;
384
+ Array.from(this.allAlarmsContainer.children).forEach(node => {
385
+ node.setAttribute('transform', `translate(${x}, 0)`);
386
+ const w = this._getSvgTextWidth(node);
387
+ this._updateUnderlineWidth(node, w);
388
+ x += w + GAP;
389
+ });
390
+ const newTotal = x > 0 ? x - GAP : 0;
391
+ this.totalWidth = newTotal;
392
+ this.pageWidths = [newTotal];
393
+ }
394
+ removeOldestPage() {
395
+ // 移除最旧的一页数据
396
+ this.displayedItems.splice(0, this.maxResultCount);
397
+ const oldPageWidth = this.pageWidths.shift() || 0;
398
+ this.totalWidth -= oldPageWidth + (this.pageWidths.length > 0 ? 80 : 0);
399
+ // 从DOM中移除最旧的元素
400
+ const elementsToRemove = Array.from(this.allAlarmsContainer.children).slice(0, this.maxResultCount);
401
+ elementsToRemove.forEach(el => {
402
+ this.allAlarmsContainer.removeChild(el);
403
+ });
404
+ // g 元素的 transform 从 0 重排(DOM删除后剩余节点要重新编排位置)
405
+ const GAP = 80;
406
+ let x = 0;
407
+ Array.from(this.allAlarmsContainer.children).forEach(node => {
408
+ node.setAttribute('transform', `translate(${x}, 0)`);
409
+ const w = this._getSvgTextWidth(node);
410
+ this._updateUnderlineWidth(node, w);
411
+ x += w + GAP;
412
+ });
413
+ // currentLeft 补偿被移除页的宽度(含 gap),保持视觉位置不变
414
+ requestAnimationFrame(() => {
415
+ this.currentLeft += oldPageWidth + 80;
416
+ this.allAlarmsContainer.setAttribute('transform', `translate(${this.currentLeft}, 0)`);
417
+ });
418
+ }
419
+ scrollContent() {
420
+ if (this.displayedItems.length <= 0)
421
+ return;
422
+ // 每次滚动的距离
423
+ const scrollStep = 2; // 减小滚动步长,使滚动更平滑
424
+ // 更新位置 - 从右往左滚动
425
+ this.currentLeft -= scrollStep;
426
+ // 检查是否需要加载下一页(只有在自动循环模式下才需要预加载)
427
+ if (this.autoCycle && this.hasMoreData && !this.isLoadingNextPage) {
428
+ const currentPosition = Math.abs(this.currentLeft);
429
+ const loadedWidth = this.totalWidth - (this.pageWidths[this.pageWidths.length - 1] || 0);
430
+ // 当滚动到已加载内容的60%时,加载下一页(提前预加载,确保无缝衔接)
431
+ if (currentPosition > loadedWidth * 0.6) {
432
+ this.getAlarmData();
433
+ }
434
+ }
435
+ // 当内容完全滚出容器左侧时(currentLeft + totalWidth < -50)
436
+ // 说明所有内容都已经滚出视图(不开启自动循环时多滚动50px确保最后一个字完全滚出)
437
+ if (this.currentLeft + this.totalWidth < -50) {
438
+ if (this.autoCycle) {
439
+ if (this.hasMoreData) {
440
+ // 有更多数据:重置到容器宽度,让内容从右侧重新滚入
441
+ this.currentLeft = this.model.size.width;
442
+ }
443
+ else {
444
+ if (this.currentPage > 2) {
445
+ // 数据总数超过一页:重置到第一页并重新查询
446
+ this.resetToFirstPage();
447
+ }
448
+ else {
449
+ // 单页循环:重排 x 从 0 开始,currentLeft 重置到右侧
450
+ this._rebaseTextNodes();
451
+ this.currentLeft = this.model.size.width;
452
+ }
453
+ }
454
+ }
455
+ else {
456
+ // 非循环滚动:停止滚动
457
+ clearInterval(this.scrollIntervalId);
458
+ this.isScrolling = false;
459
+ return; // 提前返回,不再更新位置
460
+ }
461
+ }
462
+ // 更新显示位置
463
+ this.allAlarmsContainer.setAttribute('transform', `translate(${this.currentLeft}, 0)`);
464
+ }
465
+ initScrolling() {
466
+ clearInterval(this.scrollIntervalId);
467
+ // 如果没有数据,则不启动滚动
468
+ if (this.displayedItems.length === 0) {
469
+ return;
470
+ }
471
+ // 确保初始位置在容器右侧(从右边滚入)
472
+ if (this.currentPage === 1 && this.displayedItems.length <= this.maxResultCount) {
473
+ // 第一页初始加载时,确保从右侧开始
474
+ this.currentLeft = this.model.size.width;
475
+ this.allAlarmsContainer.setAttribute('transform', `translate(${this.currentLeft}, 0)`);
476
+ }
477
+ const scrollInterval = 100; // 默认100ms,滚动更流畅
478
+ // 延迟启动滚动确保内容渲染完成
479
+ setTimeout(() => {
480
+ this.isScrolling = true;
481
+ this.scrollIntervalId = setInterval(() => {
482
+ if (this.isScrolling) {
483
+ this.scrollContent();
484
+ }
485
+ }, scrollInterval);
486
+ }, 500);
487
+ }
488
+ resetToFirstPage() {
489
+ // 重置到第一页并重新查询数据
490
+ this.currentPage = 1;
491
+ this.hasMoreData = true;
492
+ this.hasTriedFirstPage = false;
493
+ this.displayedItems = [];
494
+ if (this.allAlarmsContainer) {
495
+ while (this.allAlarmsContainer.firstChild) {
496
+ this.allAlarmsContainer.removeChild(this.allAlarmsContainer.firstChild);
497
+ }
498
+ this.totalWidth = 0;
499
+ this.pageWidths = [];
500
+ }
501
+ // 重置到容器右侧,让内容从右边滚入
502
+ this.currentLeft = this.model.size.width;
503
+ this.allAlarmsContainer.setAttribute('transform', `translate(${this.currentLeft}, 0)`);
504
+ this.getAlarmData();
505
+ }
506
+ updateQueryTimeRange() {
507
+ this.endTime = moment();
508
+ switch (this.model.generalSetting.displayPeriod) {
509
+ case 1:
510
+ this.startTime = moment().subtract(1, 'hours');
511
+ break;
512
+ case 3:
513
+ this.startTime = moment().subtract(7, 'days');
514
+ break;
515
+ case 4:
516
+ this.startTime = moment().subtract(30, 'days');
517
+ break;
518
+ case 5:
519
+ this.startTime = moment().subtract(6, 'months');
520
+ break;
521
+ case 6:
522
+ this.startTime = moment().subtract(30, 'minutes');
523
+ break;
524
+ case 7:
525
+ this.startTime = moment().subtract(8, 'hours');
526
+ break;
527
+ default:
528
+ this.startTime = moment().subtract(1, 'days');
529
+ }
530
+ }
531
+ setStatusAsNormal() {
532
+ this.elementStatus = ScrollAlarmElementStatus.Normal;
533
+ this.clearStatus();
534
+ }
535
+ setStatusAsLoading() {
536
+ this.elementStatus = ScrollAlarmElementStatus.Loading;
537
+ this.renderStatus('assets/img/loading.svg', '#226abc');
538
+ }
539
+ renderStatus(icon, stroke) {
540
+ if (this.elementStatus === ScrollAlarmElementStatus.Normal) {
541
+ this.clearStatus();
542
+ return;
543
+ }
544
+ // 确保宽度和高度为正值,避免 SVG 错误
545
+ const width = Math.max(this.model.size.width || 100, 0);
546
+ const height = Math.max(this.model.size.height || 50, 0);
547
+ // 先移除旧的 StateFrame,避免重复追加
548
+ this.$element.find('rect#StateFrame').remove();
549
+ this.rootElement.append('rect').attr('id', 'StateFrame').attr('fill', 'transparent')
550
+ .attr('width', width)
551
+ .attr('height', height);
552
+ const doc = this.$element[0].ownerDocument;
553
+ const currentRect = this.$element.find('rect#StateFrame').first();
554
+ if (!currentRect.length) {
555
+ return;
556
+ }
557
+ this.$element.find('image#StateImage').remove();
558
+ const imgObj = doc.createElementNS('http://www.w3.org/2000/svg', 'image');
559
+ if (imgObj) {
560
+ let x = Number(currentRect[0].getAttribute('width')) - 20;
561
+ const currentRectX = currentRect[0].getAttribute('x');
562
+ if (currentRectX !== null) {
563
+ x += Number(currentRectX);
564
+ }
565
+ currentRect[0].setAttribute('stroke', stroke);
566
+ currentRect[0].setAttribute('stroke-opacity', '0.5');
567
+ imgObj.href.baseVal = icon;
568
+ imgObj.setAttributeNS(null, 'id', 'StateImage');
569
+ imgObj.setAttributeNS(null, 'x', x.toString());
570
+ imgObj.setAttributeNS(null, 'y', '0');
571
+ imgObj.setAttributeNS(null, 'height', '20px');
572
+ imgObj.setAttributeNS(null, 'width', '20px');
573
+ const titleElement = doc.createElementNS('http://www.w3.org/2000/svg', 'title');
574
+ imgObj.appendChild(titleElement);
575
+ this.$element.append(imgObj);
576
+ }
577
+ }
578
+ clearStatus() {
579
+ this.$element.find('rect#StateFrame').remove();
580
+ this.$element.find('image#StateImage').remove();
581
+ }
582
+ /**
583
+ * 模拟运行时的静态显示逻辑:只显示字段标签,不查询数据
584
+ */
585
+ renderStaticDisplay() {
586
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
587
+ // 根据配置构建显示的字段标签
588
+ const textParts = [];
589
+ if (((_a = this.model.generalSetting) === null || _a === void 0 ? void 0 : _a.showTriggerTime) !== false) {
590
+ // 使用moment.format方法格式化日期时间
591
+ const timeStr = moment().format('YYYY/MM/DD HH:mm:ss');
592
+ textParts.push(timeStr);
593
+ }
594
+ if (((_b = this.model.generalSetting) === null || _b === void 0 ? void 0 : _b.showAlarmLevel) !== false) {
595
+ // 获取当前语言环境
596
+ const language = ((_e = (_d = (_c = window.abp) === null || _c === void 0 ? void 0 : _c.localization) === null || _d === void 0 ? void 0 : _d.currentLanguage) === null || _e === void 0 ? void 0 : _e.name) || 'zh-Hans';
597
+ const isChinese = language === 'zh-Hans' || language === 'zh';
598
+ textParts.push(isChinese ? '严重' : 'Critical');
599
+ }
600
+ if (((_f = this.model.generalSetting) === null || _f === void 0 ? void 0 : _f.showAlarmName) === true) {
601
+ // 获取当前语言环境
602
+ const language = ((_j = (_h = (_g = window.abp) === null || _g === void 0 ? void 0 : _g.localization) === null || _h === void 0 ? void 0 : _h.currentLanguage) === null || _j === void 0 ? void 0 : _j.name) || 'zh-Hans';
603
+ const isChinese = language === 'zh-Hans' || language === 'zh';
604
+ textParts.push(isChinese ? '室内温度' : 'Indoor Temperature');
605
+ }
606
+ if (((_k = this.model.generalSetting) === null || _k === void 0 ? void 0 : _k.showAlarmContent) !== false) {
607
+ // 获取当前语言环境
608
+ const language = ((_o = (_m = (_l = window.abp) === null || _l === void 0 ? void 0 : _l.localization) === null || _m === void 0 ? void 0 : _m.currentLanguage) === null || _o === void 0 ? void 0 : _o.name) || 'zh-Hans';
609
+ const isChinese = language === 'zh-Hans' || language === 'zh';
610
+ textParts.push(isChinese ? '室内温度高于30°C或小于10°C' : 'Indoor temperature is higher than 30℃ or lower than 10℃');
611
+ }
612
+ if (((_p = this.model.generalSetting) === null || _p === void 0 ? void 0 : _p.showAlarmState) === true) {
613
+ // 获取当前语言环境
614
+ const language = ((_s = (_r = (_q = window.abp) === null || _q === void 0 ? void 0 : _q.localization) === null || _r === void 0 ? void 0 : _r.currentLanguage) === null || _s === void 0 ? void 0 : _s.name) || 'zh-Hans';
615
+ const isChinese = language === 'zh-Hans' || language === 'zh';
616
+ textParts.push(isChinese ? '触发/未确认' : 'Triggered/Unconfirmed');
617
+ }
618
+ // 创建 SVG text 节点(与真实运行模式一致,不依赖 foreignObject)
619
+ const textNode = this._createSvgTextNode(textParts.join(' '));
620
+ textNode.setAttribute('transform', 'translate(0, 0)');
621
+ this.currentLeft = this.model.size.width;
622
+ this.allAlarmsContainer.setAttribute('transform', `translate(${this.currentLeft}, 0)`);
623
+ this.allAlarmsContainer.appendChild(textNode);
624
+ // SVG text 宽度通过内部 text 元素的 getComputedTextLength 获取
625
+ const textWidth = this._getSvgTextWidth(textNode);
626
+ this._updateUnderlineWidth(textNode, textWidth);
627
+ this.totalWidth = textWidth;
628
+ this.pageWidths.push(textWidth);
629
+ // 启动滚动(autoCycle 只控制是否循环,不控制是否滚动)
630
+ this.initStaticScrolling();
631
+ }
632
+ /**
633
+ * 模拟运行时的静态滚动逻辑
634
+ */
635
+ initStaticScrolling() {
636
+ clearInterval(this.scrollIntervalId);
637
+ const scrollInterval = 100;
638
+ // 延迟启动滚动确保内容渲染完成
639
+ setTimeout(() => {
640
+ this.isScrolling = true;
641
+ this.scrollIntervalId = setInterval(() => {
642
+ if (this.isScrolling) {
643
+ this.scrollStaticContent();
644
+ }
645
+ }, scrollInterval);
646
+ }, 500);
647
+ }
648
+ /**
649
+ * 模拟运行时的静态内容滚动
650
+ */
651
+ scrollStaticContent() {
652
+ // 每次滚动的距离
653
+ const scrollStep = 2;
654
+ // 更新位置 - 从右往左滚动
655
+ this.currentLeft -= scrollStep;
656
+ // 更新显示位置
657
+ this.allAlarmsContainer.setAttribute('transform', `translate(${this.currentLeft}, 0)`);
658
+ // 当内容完全滚出容器左侧时(内容右边缘完全移出容器左边缘)
659
+ // currentLeft + totalWidth 表示内容右边缘的位置
660
+ // 当这个值 <= -50 时,表示内容完全不可见(不开启自动循环时多滚动50px确保最后一个字完全滚出)
661
+ const rightEdge = this.currentLeft + this.totalWidth;
662
+ if (rightEdge <= -50) {
663
+ if (this.autoCycle) {
664
+ // 自动循环模式:重置到容器右侧(使用配置宽度)
665
+ this.currentLeft = this.model.size.width;
666
+ this.allAlarmsContainer.setAttribute('transform', `translate(${this.currentLeft}, 0)`);
667
+ }
668
+ else {
669
+ // 非循环模式:停止滚动
670
+ clearInterval(this.scrollIntervalId);
671
+ this.isScrolling = false;
672
+ return;
673
+ }
674
+ }
675
+ }
676
+ /**
677
+ * 实时替换当前滚动内容(用于告警数据变化时)
678
+ */
679
+ replaceCurrentScrollingContent() {
680
+ var _a, _b;
681
+ // 更新查询时间范围
682
+ this.updateQueryTimeRange();
683
+ // 查询所有当前应该显示的数据(查询足够多的数据以确保包含所有新增/删除)
684
+ const selectedStates = (_a = this.model.generalSetting) === null || _a === void 0 ? void 0 : _a.selectState;
685
+ const timeSort = (_b = this.model.generalSetting) === null || _b === void 0 ? void 0 : _b.timeSort;
686
+ // 使用一个较大的值确保能查询到所有数据,避免因新增数据导致查询不全
687
+ const queryCount = Math.max(this.displayedItems.length || this.maxResultCount, 100);
688
+ // 确定排序参数
689
+ let sorting = 'TriggeredTime ASC'; // 默认升序
690
+ if (timeSort === 0 || timeSort === '0') {
691
+ sorting = 'TriggeredTime ASC'; // 升序
692
+ }
693
+ else if (timeSort === 1 || timeSort === '1') {
694
+ sorting = 'TriggeredTime DESC'; // 降序
695
+ }
696
+ // 兼容处理:如果 selectedStates 为空数组或 undefined,传 undefined(不过滤)
697
+ const stateFilter = (selectedStates && Array.isArray(selectedStates) && selectedStates.length > 0)
698
+ ? selectedStates
699
+ : undefined;
700
+ const input = new GetAlarmsArgs(this.alarmNames, this.startTime, this.endTime, queryCount, 0, // skipCount
701
+ stateFilter, sorting);
702
+ this.alarmsStore.getHistoryAlarms(input).subscribe(result => {
703
+ if (!result.error && result.items) {
704
+ const newItems = result.items;
705
+ // 如果没有数据,清空显示
706
+ if (newItems.length === 0) {
707
+ this.displayedItems = [];
708
+ this.totalWidth = 0;
709
+ this.pageWidths = [];
710
+ this.currentPage = 1;
711
+ this.hasTriedFirstPage = false;
712
+ this.hasMoreData = true;
713
+ // 清空DOM
714
+ if (this.allAlarmsContainer) {
715
+ while (this.allAlarmsContainer.firstChild) {
716
+ this.allAlarmsContainer.removeChild(this.allAlarmsContainer.firstChild);
717
+ }
718
+ this.currentLeft = this.model.size.width;
719
+ this.allAlarmsContainer.setAttribute('transform', `translate(${this.currentLeft}, 0)`);
720
+ }
721
+ return;
722
+ }
723
+ // 如果是首次加载或当前没有数据,直接渲染并启动滚动
724
+ if (this.displayedItems.length === 0) {
725
+ this.displayedItems = newItems;
726
+ while (this.allAlarmsContainer.firstChild) {
727
+ this.allAlarmsContainer.removeChild(this.allAlarmsContainer.firstChild);
728
+ }
729
+ this.totalWidth = 0;
730
+ this.pageWidths = [];
731
+ // 设置 hasMoreData 状态,防止滚动时误触发 getAlarmData
732
+ this.hasMoreData = result.totalCount > newItems.length;
733
+ // 设置初始位置到容器右侧
734
+ this.currentLeft = this.model.size.width;
735
+ this.allAlarmsContainer.setAttribute('transform', `translate(${this.currentLeft}, 0)`);
736
+ this.renderNewPage(newItems);
737
+ // 启动滚动
738
+ if (!this.isScrolling) {
739
+ this.initScrolling();
740
+ }
741
+ return;
742
+ }
743
+ // 保存当前的滚动位置
744
+ const currentScrollPosition = this.currentLeft;
745
+ // 清空所有SVG text节点,重新用 renderNewPage 渲染
746
+ while (this.allAlarmsContainer.firstChild) {
747
+ this.allAlarmsContainer.removeChild(this.allAlarmsContainer.firstChild);
748
+ }
749
+ this.totalWidth = 0;
750
+ this.pageWidths = [];
751
+ this.renderNewPage(newItems);
752
+ // 更新状态
753
+ this.displayedItems = newItems;
754
+ this.hasMoreData = result.totalCount > newItems.length;
755
+ // 保持滚动位置不变,继续滚动
756
+ this.currentLeft = currentScrollPosition;
757
+ this.allAlarmsContainer.setAttribute('transform', `translate(${this.currentLeft}, 0)`);
758
+ }
759
+ });
760
+ }
761
+ }