@flexem/fc-gui 3.0.0-alpha.122 → 3.0.0-alpha.124

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 (62) hide show
  1. package/bundles/@flexem/fc-gui.umd.js +2026 -1209
  2. package/bundles/@flexem/fc-gui.umd.js.map +1 -1
  3. package/bundles/@flexem/fc-gui.umd.min.js +4 -4
  4. package/bundles/@flexem/fc-gui.umd.min.js.map +1 -1
  5. package/config/alarm/get-alarms-args.d.ts +4 -1
  6. package/config/alarm/get-alarms-args.js +5 -1
  7. package/config/alarm/get-alarms-args.metadata.json +1 -1
  8. package/config/history-data/get-history-data-args.d.ts +14 -3
  9. package/config/history-data/get-history-data-args.js +5 -3
  10. package/config/history-data/get-history-data-args.metadata.json +1 -1
  11. package/elements/alarm/alarm-element.d.ts +13 -3
  12. package/elements/alarm/alarm-element.js +56 -14
  13. package/elements/alarm/alarm-element.metadata.json +1 -1
  14. package/elements/historical-curve/historical-curve.element.d.ts +9 -2
  15. package/elements/historical-curve/historical-curve.element.js +120 -34
  16. package/elements/historical-curve/historical-curve.element.metadata.json +1 -1
  17. package/elements/main-element.js +10 -6
  18. package/elements/main-element.metadata.json +1 -1
  19. package/elements/scroll-alarm/scroll-alarm-element.d.ts +54 -0
  20. package/elements/scroll-alarm/scroll-alarm-element.js +517 -0
  21. package/elements/scroll-alarm/scroll-alarm-element.metadata.json +1 -0
  22. package/elements/shared/graph/graph-state-element.d.ts +0 -1
  23. package/elements/shared/graph/graph-state-element.js +0 -18
  24. package/elements/shared/graph/graph-state-element.metadata.json +1 -1
  25. package/elements/shared/text/text-state-element.d.ts +12 -1
  26. package/elements/shared/text/text-state-element.js +40 -2
  27. package/elements/shared/text/text-state-element.metadata.json +1 -1
  28. package/elements/static-elements/hyperlink-element.d.ts +10 -2
  29. package/elements/static-elements/hyperlink-element.js +41 -2
  30. package/elements/static-elements/hyperlink-element.metadata.json +1 -1
  31. package/elements/static-elements/text-element.d.ts +10 -2
  32. package/elements/static-elements/text-element.js +41 -2
  33. package/elements/static-elements/text-element.metadata.json +1 -1
  34. package/elements/switch-indicator-light/switch-indicator-light-element.d.ts +4 -4
  35. package/elements/switch-indicator-light/switch-indicator-light-element.js +5 -27
  36. package/elements/switch-indicator-light/switch-indicator-light-element.metadata.json +1 -1
  37. package/gui/gui-context.d.ts +4 -1
  38. package/model/base/font-setting-model.d.ts +6 -0
  39. package/model/base/font-setting-model.metadata.json +1 -1
  40. package/model/historical-curve/historical-curve.data-settings.d.ts +18 -1
  41. package/model/historical-curve/historical-curve.data-settings.metadata.json +1 -1
  42. package/model/scroll-alarm/scroll-alarm.model.d.ts +21 -0
  43. package/model/scroll-alarm/scroll-alarm.model.js +0 -0
  44. package/model/scroll-alarm/scroll-alarm.model.metadata.json +1 -0
  45. package/model/switch-indicator-light/switch-indicator-light.d.ts +2 -0
  46. package/package.json +1 -1
  47. package/public_api.js +1 -0
  48. package/service/index.d.ts +3 -0
  49. package/service/index.js +1 -0
  50. package/service/index.metadata.json +1 -1
  51. package/service/language.service.d.ts +37 -0
  52. package/service/language.service.js +0 -0
  53. package/service/language.service.metadata.json +1 -0
  54. package/service/system-text-library.service.d.ts +76 -0
  55. package/service/system-text-library.service.js +28 -0
  56. package/service/system-text-library.service.metadata.json +1 -0
  57. package/service/text-library.service.d.ts +49 -0
  58. package/service/text-library.service.js +0 -0
  59. package/service/text-library.service.metadata.json +1 -0
  60. package/shared/gui-consts.d.ts +1 -0
  61. package/shared/gui-consts.js +2 -1
  62. package/shared/gui-consts.metadata.json +1 -1
@@ -0,0 +1,54 @@
1
+ import { Injector } from '@angular/core';
2
+ import { PermissionChecker } from '../../service';
3
+ import { VariableCommunicator } from '../../communication';
4
+ import { ConditionalDynamicDisplayElement } from '../base/conditional-dynamic-display-element';
5
+ import { ScrollAlarmModel } from '../../model/scroll-alarm/scroll-alarm.model';
6
+ import { AlarmsStore, VariableStore } from '../../config';
7
+ export declare class ScrollAlarmElement extends ConditionalDynamicDisplayElement {
8
+ private readonly alarmsStore;
9
+ readonly model: ScrollAlarmModel;
10
+ private readonly logger;
11
+ readonly variableCommunicator: VariableCommunicator;
12
+ private elementStatus;
13
+ private element;
14
+ private scrollIntervalId;
15
+ private alarmNames;
16
+ private startTime;
17
+ private endTime;
18
+ private getAlarmDataId;
19
+ private alarmRefreshThrottleId;
20
+ private isWaitingForAlarmRefresh;
21
+ private isScrolling;
22
+ private isDisposed;
23
+ private isRefreshingCurrentPage;
24
+ private currentPage;
25
+ private maxResultCount;
26
+ private displayedItems;
27
+ private pageWidths;
28
+ private totalWidth;
29
+ private container;
30
+ private allAlarmsContainer;
31
+ private currentLeft;
32
+ private autoCycle;
33
+ private hasMoreData;
34
+ private isLoadingNextPage;
35
+ private headerFont;
36
+ private fontString;
37
+ private hasTriedFirstPage;
38
+ constructor(element: HTMLElement, injector: Injector, permissionChecker: PermissionChecker, variableCommunicator: VariableCommunicator, variableStore: VariableStore, alarmsStore: AlarmsStore, signalRAppId: string);
39
+ dispose(): void;
40
+ private getAlarmData;
41
+ private initDisplayContainer;
42
+ private renderNewPage;
43
+ private removeOldestPage;
44
+ private scrollContent;
45
+ private initScrolling;
46
+ private pauseScroll;
47
+ private resumeScroll;
48
+ private resetToFirstPage;
49
+ private updateQueryTimeRange;
50
+ private setStatusAsNormal;
51
+ private setStatusAsLoading;
52
+ private renderStatus;
53
+ private clearStatus;
54
+ }
@@ -0,0 +1,517 @@
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;
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.rootElement.selectAll('*').remove();
34
+ this.setStatusAsLoading();
35
+ this.logger = injector.get(LOGGER_SERVICE_TOKEN);
36
+ this.variableCommunicator = variableCommunicator;
37
+ this.autoCycle = (_b = (_a = this.model.generalSetting) === null || _a === void 0 ? void 0 : _a.autoCycle) !== null && _b !== void 0 ? _b : true;
38
+ this.maxResultCount = (_d = (_c = this.model.generalSetting) === null || _c === void 0 ? void 0 : _c.pageSize) !== null && _d !== void 0 ? _d : 500;
39
+ if (this.model.filterSetting) {
40
+ this.alarmNames = this.model.filterSetting.detailsData.map(item => item.name);
41
+ this.updateQueryTimeRange();
42
+ // 监听告警更新 (带节流,1秒内只执行一次)
43
+ variableCommunicator.subscribeUserDeviceAlarms(signalRAppId).subscribe(alarms => {
44
+ alarms.forEach(alarm => {
45
+ if (this.alarmNames.indexOf(alarm.name) !== -1) {
46
+ // 如果已经在等待刷新,直接返回,避免重复清理和创建定时器
47
+ if (this.isWaitingForAlarmRefresh) {
48
+ return;
49
+ }
50
+ // 标记为等待刷新状态
51
+ this.isWaitingForAlarmRefresh = true;
52
+ // 设置节流定时器,1秒后执行
53
+ this.alarmRefreshThrottleId = setTimeout(() => {
54
+ // 收到新告警消息,重置到第一页并重新开始滚动
55
+ this.resetToFirstPage();
56
+ // 如果非自动循环模式,需要重新启动滚动
57
+ if (!this.autoCycle && !this.isScrolling) {
58
+ this.initScrolling();
59
+ }
60
+ // 重置等待刷新标志
61
+ this.isWaitingForAlarmRefresh = false;
62
+ }, 1000);
63
+ return;
64
+ }
65
+ });
66
+ });
67
+ // 初始化显示容器
68
+ this.initDisplayContainer();
69
+ // 获取字体设置
70
+ this.headerFont = ((_e = this.model.generalSetting) === null || _e === void 0 ? void 0 : _e.headerFont) || {};
71
+ const fontStyle = [];
72
+ if (this.headerFont.isBold)
73
+ fontStyle.push('bold');
74
+ if (this.headerFont.isItalic)
75
+ fontStyle.push('italic');
76
+ if (this.headerFont.isUnderline)
77
+ fontStyle.push('underline');
78
+ fontStyle.push(`${this.headerFont.fontSize || '16px'}`);
79
+ fontStyle.push(`${this.headerFont.fontFamily || 'Microsoft YaHei'}`);
80
+ this.fontString = fontStyle.join(' ');
81
+ this.getAlarmData();
82
+ }
83
+ else {
84
+ this.displayedItems = [];
85
+ if (this.allAlarmsContainer) {
86
+ this.allAlarmsContainer.innerHTML = '';
87
+ this.totalWidth = 0;
88
+ this.pageWidths = [];
89
+ }
90
+ }
91
+ }
92
+ dispose() {
93
+ this.isDisposed = true;
94
+ clearInterval(this.scrollIntervalId);
95
+ clearTimeout(this.getAlarmDataId);
96
+ clearTimeout(this.alarmRefreshThrottleId);
97
+ if (this.element && this.element.tooltip) {
98
+ this.element.tooltip.hidden(true);
99
+ }
100
+ this.logger.debug(`[GUI]Dispose Scroll Alarm Refresh Interval:${d3.time.format('%x %X')(new Date())}`);
101
+ // 重置所有状态
102
+ this.isScrolling = false;
103
+ }
104
+ getAlarmData() {
105
+ if (this.isDisposed || this.isLoadingNextPage)
106
+ return;
107
+ this.isLoadingNextPage = true;
108
+ // Only show loading for the first page load
109
+ if (this.currentPage === 1 && this.displayedItems.length === 0) {
110
+ this.setStatusAsLoading();
111
+ }
112
+ clearTimeout(this.getAlarmDataId);
113
+ this.getAlarmDataId = setTimeout(() => {
114
+ var _a, _b;
115
+ this.updateQueryTimeRange();
116
+ const skipCount = (this.currentPage - 1) * this.maxResultCount;
117
+ // 从配置中获取状态和排序设置
118
+ const selectedStates = (_a = this.model.generalSetting) === null || _a === void 0 ? void 0 : _a.selectState; // 前端使用的字段名
119
+ const timeSort = (_b = this.model.generalSetting) === null || _b === void 0 ? void 0 : _b.timeSort; // 前端使用的字段名
120
+ // 构建排序字符串 - API 字段名是 sorting
121
+ let sorting;
122
+ if (timeSort === '0') {
123
+ sorting = 'TriggeredTime ASC'; // 升序
124
+ }
125
+ else if (timeSort === '1') {
126
+ sorting = 'TriggeredTime DESC'; // 降序
127
+ }
128
+ // 后端 API 的 state 参数现已支持数组,直接传递 selectedStates
129
+ // 兼容处理:如果 selectedStates 为空数组或 undefined,传 undefined(不过滤)
130
+ const stateFilter = (selectedStates && Array.isArray(selectedStates) && selectedStates.length > 0)
131
+ ? selectedStates
132
+ : undefined;
133
+ const input = new GetAlarmsArgs(this.alarmNames, this.startTime, this.endTime, this.maxResultCount, skipCount, stateFilter, // 传递状态数组到后端,后端已支持数组过滤
134
+ sorting // 传递排序参数
135
+ );
136
+ this.alarmsStore.getHistoryAlarms(input).subscribe(result => {
137
+ this.isLoadingNextPage = false;
138
+ if (!result.error && result.items && result.items.length > 0) {
139
+ const newPage = result.items;
140
+ // 检查是否还有更多数据
141
+ // 如果是第一页且数据量少于 maxResultCount,说明总数据不超过一页
142
+ if (this.currentPage === 1 && newPage.length < this.maxResultCount) {
143
+ // 总数据不超过一页,不需要加载更多数据
144
+ this.hasMoreData = false;
145
+ }
146
+ else if (!this.autoCycle && newPage.length < this.maxResultCount) {
147
+ // 非自动循环模式,且当前页数据不足,说明没有更多数据
148
+ this.hasMoreData = false;
149
+ }
150
+ else {
151
+ this.hasMoreData = true;
152
+ }
153
+ if (this.isRefreshingCurrentPage) {
154
+ // 刷新当前页模式:替换最后一页的数据,保持滚动位置
155
+ const itemsPerPage = this.maxResultCount;
156
+ const startIndex = (this.currentPage - 1) * itemsPerPage;
157
+ // 移除当前页的旧数据
158
+ this.displayedItems.splice(startIndex, itemsPerPage);
159
+ // 移除当前页的DOM元素
160
+ const oldPageWidth = this.pageWidths.pop() || 0;
161
+ this.totalWidth -= oldPageWidth;
162
+ const elementsToRemove = Array.from(this.allAlarmsContainer.children).slice(startIndex, startIndex + itemsPerPage);
163
+ elementsToRemove.forEach(element => {
164
+ this.allAlarmsContainer.removeChild(element);
165
+ });
166
+ // 插入新的当前页数据
167
+ this.displayedItems.splice(startIndex, 0, ...newPage);
168
+ // 渲染新页(会追加到末尾)
169
+ this.renderNewPage(newPage);
170
+ // 更新页码
171
+ this.currentPage++;
172
+ // 重置标志
173
+ this.isRefreshingCurrentPage = false;
174
+ }
175
+ else {
176
+ // 正常加载新页模式:追加数据
177
+ this.displayedItems = this.displayedItems.concat(newPage);
178
+ // 渲染新页
179
+ this.renderNewPage(newPage);
180
+ // 更新页码
181
+ this.currentPage++;
182
+ }
183
+ // 检查是否需要删除旧页(保持最多3页)
184
+ if (this.currentPage > 4) {
185
+ this.removeOldestPage();
186
+ }
187
+ // 如果是首次加载或还没有开始滚动,初始化滚动
188
+ if (!this.isScrolling && this.displayedItems.length > 0) {
189
+ this.initScrolling();
190
+ }
191
+ this.setStatusAsNormal();
192
+ this.hasTriedFirstPage = true;
193
+ }
194
+ else {
195
+ // 如果没有数据
196
+ if (this.currentPage === 1 && !this.hasTriedFirstPage) {
197
+ // 第一次加载就没有数据,设置为正常状态,停止加载
198
+ this.hasTriedFirstPage = true;
199
+ this.hasMoreData = false;
200
+ this.setStatusAsNormal();
201
+ // 清空显示
202
+ this.displayedItems = [];
203
+ if (this.allAlarmsContainer) {
204
+ this.allAlarmsContainer.innerHTML = '';
205
+ this.totalWidth = 0;
206
+ this.pageWidths = [];
207
+ }
208
+ }
209
+ else if (this.autoCycle && this.hasTriedFirstPage && this.displayedItems.length > 0) {
210
+ // 不是第一页,且之前有数据,自动循环开启,循环回到第一页
211
+ this.currentPage = 1;
212
+ this.getAlarmData();
213
+ }
214
+ else {
215
+ // 其他情况:停止加载
216
+ this.hasMoreData = false;
217
+ this.setStatusAsNormal();
218
+ }
219
+ }
220
+ }, error => {
221
+ this.isLoadingNextPage = false;
222
+ this.setStatusAsNormal();
223
+ this.logger.error('Failed to get alarm data:', error);
224
+ });
225
+ }, 500);
226
+ }
227
+ initDisplayContainer() {
228
+ var _a;
229
+ const elementHeight = this.model.size.height;
230
+ const elementWidth = this.model.size.width;
231
+ // 创建foreignObject作为容器
232
+ this.element = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject');
233
+ this.element.setAttribute('width', elementWidth.toString());
234
+ this.element.setAttribute('height', elementHeight.toString());
235
+ this.$element.append(this.element);
236
+ // 创建背景容器,应用headerFillColor背景色
237
+ this.container = document.createElement('div');
238
+ this.container.style.cssText = `
239
+ height: ${elementHeight}px;
240
+ width: 100%;
241
+ background-color: ${((_a = this.model.generalSetting) === null || _a === void 0 ? void 0 : _a.headerFillColor) || '#ffffff'};
242
+ display: flex;
243
+ align-items: center;
244
+ overflow: hidden;
245
+ position: relative;
246
+ border: 1px solid #8ea0aa;
247
+ `;
248
+ this.element.appendChild(this.container);
249
+ // 创建一个大容器来包含所有告警项,初始位置在容器右侧(从右边开始滚入)
250
+ this.allAlarmsContainer = document.createElement('div');
251
+ this.allAlarmsContainer.style.cssText = `
252
+ position: absolute;
253
+ left: ${elementWidth}px;
254
+ top: 50%;
255
+ transform: translateY(-50%);
256
+ display: flex;
257
+ align-items: center;
258
+ gap: 80px;
259
+ `;
260
+ this.container.appendChild(this.allAlarmsContainer);
261
+ // 设置初始位置为容器宽度,让内容从右侧滚入
262
+ this.currentLeft = elementWidth;
263
+ // 添加鼠标悬停暂停和恢复滚动功能
264
+ this.container.addEventListener('mouseenter', () => this.pauseScroll());
265
+ this.container.addEventListener('mouseleave', () => this.resumeScroll());
266
+ }
267
+ renderNewPage(pageData) {
268
+ let pageWidth = 0;
269
+ // 为每个告警数据创建文本容器
270
+ pageData.forEach((alarm) => {
271
+ var _a, _b, _c, _d, _e, _f, _g, _h;
272
+ // 创建告警文本容器
273
+ const textContainer = document.createElement('div');
274
+ textContainer.style.cssText = `
275
+ font: ${this.fontString};
276
+ color: ${this.headerFont.color || '#E95F5F'};
277
+ white-space: nowrap;
278
+ overflow: visible;
279
+ `;
280
+ // 构建告警信息内容,根据配置决定显示哪些字段
281
+ const contentParts = [];
282
+ // 显示触发时间
283
+ if (((_a = this.model.generalSetting) === null || _a === void 0 ? void 0 : _a.showTriggerTime) && alarm.triggeredTime) {
284
+ const formattedTime = moment(alarm.triggeredTime).format('YYYY/MM/DD HH:mm:ss');
285
+ contentParts.push(formattedTime);
286
+ }
287
+ // 获取当前语言环境
288
+ 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';
289
+ const isChinese = language === 'zh-Hans' || language === 'zh';
290
+ // 显示告警等级
291
+ if (((_e = this.model.generalSetting) === null || _e === void 0 ? void 0 : _e.showAlarmLevel) && alarm.alarmLevel !== undefined) {
292
+ const levelMap = isChinese ? ['警告', '次要', '主要', '严重'] : ['Warning', 'Minor', 'Major', 'Critical'];
293
+ const alarmLevel = levelMap[alarm.alarmLevel] || (isChinese ? '警告' : 'Warning');
294
+ contentParts.push(alarmLevel);
295
+ }
296
+ // 显示告警名称
297
+ if (((_f = this.model.generalSetting) === null || _f === void 0 ? void 0 : _f.showAlarmName) && alarm.name) {
298
+ contentParts.push(alarm.name);
299
+ }
300
+ // 显示告警状态
301
+ if (((_g = this.model.generalSetting) === null || _g === void 0 ? void 0 : _g.showAlarmState) && alarm.state !== undefined) {
302
+ const stateMap = isChinese ? ['触发/未确认', '触发/已确认', '恢复/未确认', '恢复/已确认'] : ['Triggered/Unconfirmed', 'Triggered/Confirmed', 'Restored/Unconfirmed', 'Restored/Confirmed'];
303
+ const alarmState = stateMap[alarm.state] || (isChinese ? '触发/未确认' : 'Triggered/Unconfirmed');
304
+ contentParts.push(alarmState);
305
+ }
306
+ // 显示告警内容
307
+ if (((_h = this.model.generalSetting) === null || _h === void 0 ? void 0 : _h.showAlarmContent) && alarm.message) {
308
+ contentParts.push(alarm.message);
309
+ }
310
+ // 将所有显示字段用空格连接,确保一行显示
311
+ const textContent = contentParts.join(' ');
312
+ textContainer.textContent = textContent;
313
+ this.allAlarmsContainer.appendChild(textContainer);
314
+ // 计算文本宽度
315
+ const textWidth = textContainer.offsetWidth;
316
+ pageWidth += textWidth + 80; // 80是gap大小
317
+ });
318
+ // 移除最后一个gap
319
+ if (pageWidth > 0) {
320
+ pageWidth -= 80;
321
+ }
322
+ // 保存页宽度
323
+ this.pageWidths.push(pageWidth);
324
+ this.totalWidth += pageWidth;
325
+ }
326
+ removeOldestPage() {
327
+ // 移除最旧的一页数据
328
+ this.displayedItems.splice(0, this.maxResultCount);
329
+ const oldPageWidth = this.pageWidths.shift() || 0;
330
+ // 更新总宽度
331
+ this.totalWidth -= oldPageWidth;
332
+ // 从DOM中移除最旧的元素
333
+ const elementsToRemove = Array.from(this.allAlarmsContainer.children).slice(0, this.maxResultCount);
334
+ elementsToRemove.forEach(element => {
335
+ this.allAlarmsContainer.removeChild(element);
336
+ });
337
+ // 调整当前left位置(保持视觉位置不变)- 这里需要等DOM更新完成后再调整
338
+ requestAnimationFrame(() => {
339
+ this.currentLeft += oldPageWidth;
340
+ this.allAlarmsContainer.style.left = `${this.currentLeft}px`;
341
+ });
342
+ }
343
+ scrollContent() {
344
+ if (this.displayedItems.length <= 0)
345
+ return;
346
+ // 每次滚动的距离
347
+ const scrollStep = 2; // 减小滚动步长,使滚动更平滑
348
+ // 更新位置 - 从右往左滚动
349
+ this.currentLeft -= scrollStep;
350
+ // 检查是否需要加载下一页(只有在自动循环模式下才需要预加载)
351
+ if (this.autoCycle && this.hasMoreData && !this.isLoadingNextPage) {
352
+ const currentPosition = Math.abs(this.currentLeft);
353
+ const loadedWidth = this.totalWidth - (this.pageWidths[this.pageWidths.length - 1] || 0);
354
+ // 当滚动到已加载内容的60%时,加载下一页(提前预加载,确保无缝衔接)
355
+ if (currentPosition > loadedWidth * 0.6) {
356
+ this.getAlarmData();
357
+ }
358
+ }
359
+ // 当内容完全滚出容器左侧时(currentLeft + totalWidth < 0)
360
+ // 说明所有内容都已经滚出视图,需要重置或停止
361
+ if (this.currentLeft + this.totalWidth < 0) {
362
+ if (this.autoCycle) {
363
+ // 自动循环模式:无缝衔接回到容器右侧
364
+ if (this.hasMoreData) {
365
+ // 有更多数据:继续加载(但实际上已经在上面预加载了)
366
+ // 重置到容器宽度,让内容从右侧重新滚入
367
+ this.currentLeft = this.container.clientWidth;
368
+ }
369
+ else {
370
+ // 没有更多数据了:重置
371
+ if (this.currentPage > 2) {
372
+ // 数据总数超过一页:重置到第一页并重新查询
373
+ this.resetToFirstPage();
374
+ }
375
+ else {
376
+ // 数据总数只有一页:直接重置到容器右侧,循环滚动当前数据
377
+ this.currentLeft = this.container.clientWidth;
378
+ }
379
+ }
380
+ }
381
+ else {
382
+ // 非循环滚动:停止滚动
383
+ clearInterval(this.scrollIntervalId);
384
+ this.isScrolling = false;
385
+ return; // 提前返回,不再更新位置
386
+ }
387
+ }
388
+ // 更新显示位置
389
+ this.allAlarmsContainer.style.left = `${this.currentLeft}px`;
390
+ }
391
+ initScrolling() {
392
+ clearInterval(this.scrollIntervalId);
393
+ // 如果没有数据,则不启动滚动
394
+ if (this.displayedItems.length === 0) {
395
+ return;
396
+ }
397
+ // 确保初始位置在容器右侧(从右边滚入)
398
+ if (this.currentPage === 1 && this.displayedItems.length <= this.maxResultCount) {
399
+ // 第一页初始加载时,确保从右侧开始
400
+ this.currentLeft = this.container.clientWidth;
401
+ this.allAlarmsContainer.style.left = `${this.currentLeft}px`;
402
+ }
403
+ const scrollInterval = 100; // 默认100ms,滚动更流畅
404
+ // 延迟启动滚动确保内容渲染完成
405
+ setTimeout(() => {
406
+ this.isScrolling = true;
407
+ this.scrollIntervalId = setInterval(() => {
408
+ if (this.isScrolling) {
409
+ this.scrollContent();
410
+ }
411
+ }, scrollInterval);
412
+ }, 500);
413
+ }
414
+ pauseScroll() {
415
+ this.isScrolling = false;
416
+ }
417
+ resumeScroll() {
418
+ this.isScrolling = true;
419
+ }
420
+ resetToFirstPage() {
421
+ // 重置到第一页并重新查询数据
422
+ this.currentPage = 1;
423
+ this.hasMoreData = true;
424
+ this.hasTriedFirstPage = false;
425
+ this.displayedItems = [];
426
+ if (this.allAlarmsContainer) {
427
+ this.allAlarmsContainer.innerHTML = '';
428
+ this.totalWidth = 0;
429
+ this.pageWidths = [];
430
+ }
431
+ // 重置到容器右侧,让内容从右边滚入
432
+ this.currentLeft = this.container.clientWidth;
433
+ this.allAlarmsContainer.style.left = `${this.currentLeft}px`;
434
+ this.getAlarmData();
435
+ }
436
+ updateQueryTimeRange() {
437
+ this.endTime = moment();
438
+ switch (this.model.generalSetting.displayPeriod) {
439
+ case 1:
440
+ this.startTime = moment().subtract(1, 'hours');
441
+ break;
442
+ case 3:
443
+ this.startTime = moment().subtract(7, 'days');
444
+ break;
445
+ case 4:
446
+ this.startTime = moment().subtract(30, 'days');
447
+ break;
448
+ case 5:
449
+ this.startTime = moment().subtract(6, 'months');
450
+ break;
451
+ case 6:
452
+ this.startTime = moment().subtract(30, 'minutes');
453
+ break;
454
+ case 7:
455
+ this.startTime = moment().subtract(8, 'hours');
456
+ break;
457
+ default:
458
+ this.startTime = moment().subtract(1, 'days');
459
+ }
460
+ }
461
+ setStatusAsNormal() {
462
+ this.elementStatus = ScrollAlarmElementStatus.Normal;
463
+ this.clearStatus();
464
+ }
465
+ setStatusAsLoading() {
466
+ this.elementStatus = ScrollAlarmElementStatus.Loading;
467
+ this.renderStatus('assets/img/loading.svg', '#226abc');
468
+ }
469
+ renderStatus(icon, stroke) {
470
+ if (this.elementStatus === ScrollAlarmElementStatus.Normal) {
471
+ this.clearStatus();
472
+ return;
473
+ }
474
+ // 确保宽度和高度为正值,避免 SVG 错误
475
+ const width = Math.max(this.model.size.width || 100, 0);
476
+ const height = Math.max(this.model.size.height || 50, 0);
477
+ this.rootElement.append('rect').attr('id', 'StateFrame').attr('fill', 'transparent')
478
+ .attr('width', width)
479
+ .attr('height', height);
480
+ const document = this.$element[0].ownerDocument;
481
+ const currentRect = this.$element.find('rect#StateFrame').first();
482
+ if (!currentRect.length) {
483
+ return;
484
+ }
485
+ this.$element.find('image#StateImage').remove();
486
+ const imgObj = document.createElementNS('http://www.w3.org/2000/svg', 'image');
487
+ if (imgObj) {
488
+ let x = Number(currentRect[0].getAttribute('width')) - 20;
489
+ const currentRectX = currentRect[0].getAttribute('x');
490
+ if (currentRectX !== null) {
491
+ x += Number(currentRectX);
492
+ }
493
+ currentRect[0].setAttribute('stroke', stroke);
494
+ currentRect[0].setAttribute('stroke-opacity', '0.5');
495
+ imgObj.href.baseVal = icon;
496
+ imgObj.setAttributeNS(null, 'id', 'StateImage');
497
+ imgObj.setAttributeNS(null, 'x', x.toString());
498
+ imgObj.setAttributeNS(null, 'y', '0');
499
+ imgObj.setAttributeNS(null, 'height', '20px');
500
+ imgObj.setAttributeNS(null, 'width', '20px');
501
+ const titleElement = document.createElementNS('http://www.w3.org/2000/svg', 'title');
502
+ imgObj.appendChild(titleElement);
503
+ this.$element.append(imgObj);
504
+ }
505
+ }
506
+ clearStatus() {
507
+ const currentRect = this.$element.find('rect#StateFrame').first();
508
+ if (!currentRect.length) {
509
+ return;
510
+ }
511
+ const stroke = currentRect[0].getAttribute('stroke');
512
+ if (stroke) {
513
+ currentRect[0].removeAttribute('stroke');
514
+ }
515
+ this.$element.find('image#StateImage').remove();
516
+ }
517
+ }
@@ -0,0 +1 @@
1
+ [{"__symbolic":"module","version":4,"metadata":{"ScrollAlarmElement":{"__symbolic":"class","extends":{"__symbolic":"reference","module":"../base/conditional-dynamic-display-element","name":"ConditionalDynamicDisplayElement","line":17,"character":40},"members":{"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"error","message":"Could not resolve type","line":51,"character":25,"context":{"typeName":"HTMLElement"}},{"__symbolic":"reference","module":"@angular/core","name":"Injector","line":52,"character":18},{"__symbolic":"reference","module":"../../service","name":"PermissionChecker","line":53,"character":27},{"__symbolic":"reference","module":"../../communication","name":"VariableCommunicator","line":54,"character":30},{"__symbolic":"reference","module":"../../config","name":"VariableStore","line":55,"character":23},{"__symbolic":"reference","module":"../../config","name":"AlarmsStore","line":56,"character":38},{"__symbolic":"reference","name":"string"}]}],"dispose":[{"__symbolic":"method"}],"getAlarmData":[{"__symbolic":"method"}],"initDisplayContainer":[{"__symbolic":"method"}],"renderNewPage":[{"__symbolic":"method"}],"removeOldestPage":[{"__symbolic":"method"}],"scrollContent":[{"__symbolic":"method"}],"initScrolling":[{"__symbolic":"method"}],"pauseScroll":[{"__symbolic":"method"}],"resumeScroll":[{"__symbolic":"method"}],"resetToFirstPage":[{"__symbolic":"method"}],"updateQueryTimeRange":[{"__symbolic":"method"}],"setStatusAsNormal":[{"__symbolic":"method"}],"setStatusAsLoading":[{"__symbolic":"method"}],"renderStatus":[{"__symbolic":"method"}],"clearStatus":[{"__symbolic":"method"}]}}}}]
@@ -24,5 +24,4 @@ export declare class GraphStateElement {
24
24
  private removeImageElement;
25
25
  private doFaultFlicker;
26
26
  private clearFlickerInterval;
27
- dispose(): void;
28
27
  }
@@ -107,22 +107,4 @@ export class GraphStateElement {
107
107
  }
108
108
  }
109
109
  }
110
- dispose() {
111
- // 清理定时器
112
- if (this.faultFlickerInterval) {
113
- clearInterval(this.faultFlickerInterval);
114
- this.faultFlickerInterval = undefined;
115
- }
116
- // 移除图片元素
117
- this.removeImageElement();
118
- // 清理图形缓存
119
- if (this.graphs) {
120
- this.graphs.clear();
121
- }
122
- // 清理 SVG 元素
123
- if (this._element && this._element.parentNode) {
124
- this._element.parentNode.removeChild(this._element);
125
- }
126
- this._element = null;
127
- }
128
110
  }
@@ -1 +1 @@
1
- [{"__symbolic":"module","version":4,"metadata":{"GraphStateElement":{"__symbolic":"class","members":{"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","module":"../../../model","name":"GraphSetting","line":21,"character":47},{"__symbolic":"reference","name":"number"},{"__symbolic":"reference","name":"number"},{"__symbolic":"reference","module":"../../../config","name":"GraphStore","line":24,"character":37},{"__symbolic":"reference","module":"../../../logger","name":"LoggerService","line":25,"character":33},{"__symbolic":"reference","name":"number"},{"__symbolic":"reference","name":"Array","arguments":[{"__symbolic":"reference","module":"../../../model/switch-indicator-light/indicator-light-fault-flicker","name":"IndicatorLightFaultFlicker","line":27,"character":41}]}]}],"switchToState":[{"__symbolic":"method"}],"changeGraph":[{"__symbolic":"method"}],"getImageElement":[{"__symbolic":"method"}],"removeImageElement":[{"__symbolic":"method"}],"doFaultFlicker":[{"__symbolic":"method"}],"clearFlickerInterval":[{"__symbolic":"method"}],"dispose":[{"__symbolic":"method"}]}}}}]
1
+ [{"__symbolic":"module","version":4,"metadata":{"GraphStateElement":{"__symbolic":"class","members":{"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","module":"../../../model","name":"GraphSetting","line":21,"character":47},{"__symbolic":"reference","name":"number"},{"__symbolic":"reference","name":"number"},{"__symbolic":"reference","module":"../../../config","name":"GraphStore","line":24,"character":37},{"__symbolic":"reference","module":"../../../logger","name":"LoggerService","line":25,"character":33},{"__symbolic":"reference","name":"number"},{"__symbolic":"reference","name":"Array","arguments":[{"__symbolic":"reference","module":"../../../model/switch-indicator-light/indicator-light-fault-flicker","name":"IndicatorLightFaultFlicker","line":27,"character":41}]}]}],"switchToState":[{"__symbolic":"method"}],"changeGraph":[{"__symbolic":"method"}],"getImageElement":[{"__symbolic":"method"}],"removeImageElement":[{"__symbolic":"method"}],"doFaultFlicker":[{"__symbolic":"method"}],"clearFlickerInterval":[{"__symbolic":"method"}]}}}}]
@@ -1,5 +1,7 @@
1
1
  import { LoggerService } from '../../../logger';
2
+ import { TextLibrarySetting } from '../../../model/base/font-setting-model';
2
3
  import { IndicatorLightFaultFlicker } from '../../../model/switch-indicator-light/indicator-light-fault-flicker';
4
+ import { TextLibraryService, LanguageService } from '../../../service';
3
5
  import { TextState } from './text-state.model';
4
6
  export declare class TextStateElement {
5
7
  private readonly textStates;
@@ -8,15 +10,24 @@ export declare class TextStateElement {
8
10
  private readonly logger;
9
11
  private readonly version?;
10
12
  private readonly faultFlickers?;
13
+ private readonly textLibrarySetting?;
14
+ private readonly textLibraryService?;
15
+ private readonly languageService?;
11
16
  private textElement;
12
17
  private _element;
13
18
  get Element(): SVGElement;
14
19
  private faultFlickerStatus;
15
20
  private faultFlickerInterval;
16
- constructor(textStates: TextState[], width: number, height: number, logger: LoggerService, version?: number, faultFlickers?: IndicatorLightFaultFlicker[]);
21
+ constructor(textStates: TextState[], width: number, height: number, logger: LoggerService, version?: number, faultFlickers?: IndicatorLightFaultFlicker[], textLibrarySetting?: TextLibrarySetting, textLibraryService?: TextLibraryService, languageService?: LanguageService);
17
22
  switchToState(stateId: number): void;
18
23
  private getforeignObjectElement;
19
24
  private removeForeignObjectlement;
20
25
  private doFaultFlicker;
21
26
  private clearFlickerInterval;
27
+ /**
28
+ * 获取显示文本
29
+ * 如果配置了文本库,则根据状态ID从文本库中获取对应语种的文本
30
+ * 否则返回默认文本
31
+ */
32
+ private getDisplayText;
22
33
  }