@flexem/fc-gui 3.0.0-alpha.168 → 3.0.0-alpha.169

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.
@@ -42842,7 +42842,6 @@ class alarm_element_AlarmElement extends conditional_display_element_Conditional
42842
42842
 
42843
42843
 
42844
42844
 
42845
-
42846
42845
  var ScrollAlarmElementStatus;
42847
42846
  (function (ScrollAlarmElementStatus) {
42848
42847
  ScrollAlarmElementStatus[ScrollAlarmElementStatus["Normal"] = 0] = "Normal";
@@ -42855,7 +42854,7 @@ class scroll_alarm_element_ScrollAlarmElement extends conditional_dynamic_displa
42855
42854
  this.alarmsStore = alarmsStore;
42856
42855
  this.elementStatus = ScrollAlarmElementStatus.Loading;
42857
42856
  this.alarmNames = [];
42858
- this.isWaitingForAlarmRefresh = false; // 是否正在等待刷新(节流期间)
42857
+ this.isReplacingContent = false; // 标记是否正在替换内容(防止并发执行)
42859
42858
  this.isScrolling = false;
42860
42859
  this.isDisposed = false;
42861
42860
  this.isRefreshingCurrentPage = false; // 标记是否正在刷新当前页(而不是加载新页)
@@ -42871,6 +42870,7 @@ class scroll_alarm_element_ScrollAlarmElement extends conditional_dynamic_displa
42871
42870
  this.isLoadingNextPage = false;
42872
42871
  this.hasTriedFirstPage = false; // 是否已经尝试加载过第一页
42873
42872
  this.isSimulateMode = false; // 是否是模拟运行模式
42873
+ this._canvasCtx = null;
42874
42874
  this.rootElement.selectAll('*').remove();
42875
42875
  this.setStatusAsLoading();
42876
42876
  this.logger = injector.get(logger["b" /* LOGGER_SERVICE_TOKEN */]);
@@ -42894,26 +42894,16 @@ class scroll_alarm_element_ScrollAlarmElement extends conditional_dynamic_displa
42894
42894
  else {
42895
42895
  // 真实运行模式:使用原有的数据查询逻辑
42896
42896
  this.updateQueryTimeRange();
42897
- // 监听告警更新 (带节流,1秒内只执行一次)
42898
- variableCommunicator.subscribeUserDeviceAlarms(signalRAppId).subscribe(alarms => {
42899
- alarms.forEach(alarm => {
42900
- if (this.alarmNames.indexOf(alarm.name) !== -1) {
42901
- // 如果已经在等待刷新,直接返回,避免重复清理和创建定时器
42902
- if (this.isWaitingForAlarmRefresh) {
42903
- return;
42904
- }
42905
- // 标记为等待刷新状态
42906
- this.isWaitingForAlarmRefresh = true;
42907
- // 设置节流定时器,1秒后执行
42908
- this.alarmRefreshThrottleId = setTimeout(() => {
42909
- // 收到新告警消息,实时替换当前滚动内容,不影响滚动
42910
- this.replaceCurrentScrollingContent();
42911
- // 重置等待刷新标志
42912
- this.isWaitingForAlarmRefresh = false;
42913
- }, 1000);
42914
- return;
42915
- }
42916
- });
42897
+ // 监听单设备告警更新(防抖:连续推送只取最后一次)
42898
+ // 保存订阅对象,dispose 时取消,防止组件销毁后仍触发回调
42899
+ this.signalRSubscription = variableCommunicator.subscribeUserOneDeviceAlarms(signalRAppId).subscribe((alarmWithAppId) => {
42900
+ const alarm = alarmWithAppId.alarm;
42901
+ if (this.alarmNames.indexOf(alarm.name) !== -1) {
42902
+ clearTimeout(this.alarmRefreshThrottleId);
42903
+ this.alarmRefreshThrottleId = setTimeout(() => {
42904
+ this.replaceCurrentScrollingContent();
42905
+ }, 300);
42906
+ }
42917
42907
  });
42918
42908
  // 初始化显示容器
42919
42909
  this.initDisplayContainer();
@@ -42942,10 +42932,19 @@ class scroll_alarm_element_ScrollAlarmElement extends conditional_dynamic_displa
42942
42932
  clearInterval(this.scrollIntervalId);
42943
42933
  clearTimeout(this.getAlarmDataId);
42944
42934
  clearTimeout(this.alarmRefreshThrottleId);
42935
+ // 取消 HTTP 请求订阅,防止 dispose 后回调仍然操作已卸载的 DOM
42936
+ if (this.alarmRefreshSubscription) {
42937
+ this.alarmRefreshSubscription.unsubscribe();
42938
+ this.alarmRefreshSubscription = null;
42939
+ }
42940
+ // 取消 SignalR 推送订阅,防止 dispose 后继续触发 replaceCurrentScrollingContent
42941
+ if (this.signalRSubscription) {
42942
+ this.signalRSubscription.unsubscribe();
42943
+ this.signalRSubscription = null;
42944
+ }
42945
42945
  if (this.element && this.element.tooltip) {
42946
42946
  this.element.tooltip.hidden(true);
42947
42947
  }
42948
- this.logger.debug(`[GUI]Dispose Scroll Alarm Refresh Interval:${d3["time"].format('%x %X')(new Date())}`);
42949
42948
  // 重置所有状态
42950
42949
  this.isScrolling = false;
42951
42950
  }
@@ -43163,14 +43162,35 @@ class scroll_alarm_element_ScrollAlarmElement extends conditional_dynamic_displa
43163
43162
  }
43164
43163
  return g;
43165
43164
  }
43165
+ _measureTextWithCanvas(textContent, fontSize, fontFamily, isBold, isItalic) {
43166
+ if (!this._canvasCtx) {
43167
+ const canvas = document.createElement('canvas');
43168
+ this._canvasCtx = canvas.getContext('2d');
43169
+ }
43170
+ if (!this._canvasCtx) {
43171
+ return 0;
43172
+ }
43173
+ const weight = isBold ? 'bold' : 'normal';
43174
+ const style = isItalic ? 'italic' : 'normal';
43175
+ this._canvasCtx.font = `${style} ${weight} ${fontSize}px ${fontFamily}`;
43176
+ return this._canvasCtx.measureText(textContent).width;
43177
+ }
43166
43178
  _getSvgTextWidth(g) {
43179
+ var _a, _b, _c, _d;
43167
43180
  const text = g.querySelector('text');
43168
43181
  if (!text) {
43169
43182
  return 100;
43170
43183
  }
43171
- return text.getComputedTextLength
43172
- ? text.getComputedTextLength()
43173
- : (text.getBBox ? text.getBBox().width : 100);
43184
+ const len = (_b = (_a = text).getComputedTextLength) === null || _b === void 0 ? void 0 : _b.call(_a);
43185
+ if (len > 0)
43186
+ return len;
43187
+ const bbox = (_d = (_c = text).getBBox) === null || _d === void 0 ? void 0 : _d.call(_c);
43188
+ if ((bbox === null || bbox === void 0 ? void 0 : bbox.width) > 0)
43189
+ return bbox.width;
43190
+ // SVG 节点游离于 DOM 外时 getComputedTextLength/getBBox 均返回 0,
43191
+ // 改用 Canvas measureText 离屏计算
43192
+ const fa = this._buildFontAttrs();
43193
+ return this._measureTextWithCanvas(text.textContent || '', fa.fontSize, fa.fontFamily, fa.isBold, fa.isItalic);
43174
43194
  }
43175
43195
  _updateUnderlineWidth(g, width) {
43176
43196
  const underline = g.querySelector('line.text-underline');
@@ -43218,7 +43238,7 @@ class scroll_alarm_element_ScrollAlarmElement extends conditional_dynamic_displa
43218
43238
  this.pageWidths.push(pageWidth);
43219
43239
  this.totalWidth += (this.totalWidth > 0 && pageWidth > 0 ? GAP : 0) + pageWidth;
43220
43240
  }
43221
- _rebaseTextNodes() {
43241
+ _rebaseTextNodes(onDone) {
43222
43242
  const GAP = 80;
43223
43243
  let x = 0;
43224
43244
  Array.from(this.allAlarmsContainer.children).forEach(node => {
@@ -43230,6 +43250,8 @@ class scroll_alarm_element_ScrollAlarmElement extends conditional_dynamic_displa
43230
43250
  const newTotal = x > 0 ? x - GAP : 0;
43231
43251
  this.totalWidth = newTotal;
43232
43252
  this.pageWidths = [newTotal];
43253
+ if (onDone)
43254
+ onDone();
43233
43255
  }
43234
43256
  removeOldestPage() {
43235
43257
  // 移除最旧的一页数据
@@ -43287,8 +43309,9 @@ class scroll_alarm_element_ScrollAlarmElement extends conditional_dynamic_displa
43287
43309
  }
43288
43310
  else {
43289
43311
  // 单页循环:重排 x 从 0 开始,currentLeft 重置到右侧
43290
- this._rebaseTextNodes();
43291
- this.currentLeft = this.model.size.width;
43312
+ this._rebaseTextNodes(() => {
43313
+ this.currentLeft = this.model.size.width;
43314
+ });
43292
43315
  }
43293
43316
  }
43294
43317
  }
@@ -43518,6 +43541,17 @@ class scroll_alarm_element_ScrollAlarmElement extends conditional_dynamic_displa
43518
43541
  */
43519
43542
  replaceCurrentScrollingContent() {
43520
43543
  var _a, _b;
43544
+ // 防止并发执行:如果正在替换内容,直接返回
43545
+ if (this.isReplacingContent) {
43546
+ return;
43547
+ }
43548
+ this.isReplacingContent = true;
43549
+ // 暂停滚动定时器,防止在内容更新过程中 scrollContent 读取到中间状态导致重叠
43550
+ clearInterval(this.scrollIntervalId);
43551
+ if (this.alarmRefreshSubscription) {
43552
+ this.alarmRefreshSubscription.unsubscribe();
43553
+ this.alarmRefreshSubscription = null;
43554
+ }
43521
43555
  // 更新查询时间范围
43522
43556
  this.updateQueryTimeRange();
43523
43557
  // 查询所有当前应该显示的数据(查询足够多的数据以确保包含所有新增/删除)
@@ -43539,7 +43573,7 @@ class scroll_alarm_element_ScrollAlarmElement extends conditional_dynamic_displa
43539
43573
  : undefined;
43540
43574
  const input = new _tmp_config["c" /* GetAlarmsArgs */](this.alarmNames, this.startTime, this.endTime, queryCount, 0, // skipCount
43541
43575
  stateFilter, sorting);
43542
- this.alarmsStore.getHistoryAlarms(input).subscribe(result => {
43576
+ this.alarmRefreshSubscription = this.alarmsStore.getHistoryAlarms(input).subscribe(result => {
43543
43577
  if (!result.error && result.items) {
43544
43578
  const newItems = result.items;
43545
43579
  // 如果没有数据,清空显示
@@ -43558,6 +43592,9 @@ class scroll_alarm_element_ScrollAlarmElement extends conditional_dynamic_displa
43558
43592
  this.currentLeft = this.model.size.width;
43559
43593
  this.allAlarmsContainer.setAttribute('transform', `translate(${this.currentLeft}, 0)`);
43560
43594
  }
43595
+ this.isReplacingContent = false;
43596
+ // 无数据时停止滚动(scrollContent 会因 displayedItems.length === 0 直接返回)
43597
+ this.isScrolling = false;
43561
43598
  return;
43562
43599
  }
43563
43600
  // 如果是首次加载或当前没有数据,直接渲染并启动滚动
@@ -43574,10 +43611,13 @@ class scroll_alarm_element_ScrollAlarmElement extends conditional_dynamic_displa
43574
43611
  this.currentLeft = this.model.size.width;
43575
43612
  this.allAlarmsContainer.setAttribute('transform', `translate(${this.currentLeft}, 0)`);
43576
43613
  this.renderNewPage(newItems);
43577
- // 启动滚动
43578
- if (!this.isScrolling) {
43579
- this.initScrolling();
43580
- }
43614
+ requestAnimationFrame(() => {
43615
+ this._rebaseTextNodes(() => {
43616
+ this.isReplacingContent = false;
43617
+ this.isScrolling = false;
43618
+ this.initScrolling();
43619
+ });
43620
+ });
43581
43621
  return;
43582
43622
  }
43583
43623
  // 保存当前的滚动位置
@@ -43592,10 +43632,30 @@ class scroll_alarm_element_ScrollAlarmElement extends conditional_dynamic_displa
43592
43632
  // 更新状态
43593
43633
  this.displayedItems = newItems;
43594
43634
  this.hasMoreData = result.totalCount > newItems.length;
43595
- // 保持滚动位置不变,继续滚动
43596
- this.currentLeft = currentScrollPosition;
43597
- this.allAlarmsContainer.setAttribute('transform', `translate(${this.currentLeft}, 0)`);
43635
+ // renderNewPage 里 getComputedTextLength 在刚 append 时可能返回 0,
43636
+ // 延迟一帧让浏览器完成布局后用 _rebaseTextNodes 修正所有节点位置
43637
+ requestAnimationFrame(() => {
43638
+ this._rebaseTextNodes(() => {
43639
+ // 保持滚动位置不变,内容更新完成后重新启动滚动
43640
+ this.currentLeft = currentScrollPosition;
43641
+ this.allAlarmsContainer.setAttribute('transform', `translate(${this.currentLeft}, 0)`);
43642
+ this.isReplacingContent = false;
43643
+ this.isScrolling = false;
43644
+ this.initScrolling();
43645
+ });
43646
+ });
43647
+ return;
43598
43648
  }
43649
+ // 重置标志,内容更新完成后重新启动滚动
43650
+ this.isReplacingContent = false;
43651
+ this.isScrolling = false;
43652
+ this.initScrolling();
43653
+ }, (error) => {
43654
+ // 错误处理:重置标志,恢复滚动
43655
+ this.isReplacingContent = false;
43656
+ this.isScrolling = false;
43657
+ this.initScrolling();
43658
+ this.logger.error('Failed to replace scrolling content:', error);
43599
43659
  });
43600
43660
  }
43601
43661
  }
@@ -44032,6 +44092,12 @@ class per_view_variable_communicator_PerViewVariableCommunicator {
44032
44092
  this.alarmSubscription.push(subscription);
44033
44093
  });
44034
44094
  }
44095
+ subscribeUserOneDeviceAlarms(appId = '') {
44096
+ return new Observable["a" /* Observable */](subscriber => {
44097
+ const subscription = this._rawVariableCommunicator.subscribeUserOneDeviceAlarms(appId).subscribe((value) => subscriber.next(value), (error) => subscriber.error(error), () => subscriber.complete());
44098
+ this.alarmSubscription.push(subscription);
44099
+ });
44100
+ }
44035
44101
  dispose() {
44036
44102
  Object(lodash["forEach"])(this.variableValuesChangedSubscriptions, s => s.unsubscribe());
44037
44103
  Object(lodash["forEach"])(this.variableStatesChangedSubscription, s => s.unsubscribe());
@@ -66947,6 +67013,16 @@ class remote_variable_communicator_RemoteVariableCommunicator {
66947
67013
  };
66948
67014
  });
66949
67015
  }
67016
+ subscribeUserOneDeviceAlarms(appId = '') {
67017
+ return new Observable["a" /* Observable */](observer => {
67018
+ const subscription = this.remoteVariableProtocol.historyAlarmChanged.subscribe(value => observer.next(value), error => observer.error(error), () => observer.complete());
67019
+ this.remoteVariableProtocol.subscribeUserOneDeviceAlarms(appId);
67020
+ return () => {
67021
+ subscription.unsubscribe();
67022
+ this.remoteVariableProtocol.unsubscribeUserOneDeviceAlarms(appId);
67023
+ };
67024
+ });
67025
+ }
66950
67026
  /**
66951
67027
  * 注册系统变量处理器
66952
67028
  * @param variableName 变量名(如"当前语种ID")