@flexem/fc-gui 3.0.0-alpha.182 → 3.0.0-alpha.184

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.
@@ -32535,7 +32535,7 @@ const DefaultLocalization = {
32535
32535
  canNotBeNegative: 'Can not be negative',
32536
32536
  valueOutOfRange: 'Numerical value is out of range',
32537
32537
  timeout: 'timeout',
32538
- confirmOperationPrompt: 'Are you sure you want to perform this operation?',
32538
+ confirmOperationPrompt: 'Confirm action?',
32539
32539
  confirm: 'Yes',
32540
32540
  cancel: 'Cancel',
32541
32541
  characterInputRequired: 'Character input can\'t be empty',
@@ -32657,7 +32657,7 @@ VerifyPasswordModalComponent = __decorate([
32657
32657
  Object(_angular_core__WEBPACK_IMPORTED_MODULE_0__["Component"])({
32658
32658
  selector: 'verify-password',
32659
32659
  template: "<form #verifyPasswordForm=\"ngForm\" novalidate (ngSubmit)=\"checkSecurity()\" bs-modal-drag> <div class=\"modal-header\"> <h4 class=\"modal-title\"> <span>{{localization.passwordVerify}}</span> </h4> <button type=\"button\" class=\"close pull-right\" aria-label=\"Close\" (click)=\"close()\"> <span aria-hidden=\"true\">&times;</span> </button> </div> <div class=\"modal-body\"> <span class=\"password-span\">{{localization.password}}: </span> <input type=\"password\" name=\"password\" autoFocus [class.verify-password-input]=\"!isMobileMode\" class=\"form-control element-password\" [(ngModel)]=\"password\" (keyup)=\"validate()\" autocomplete=\"off\"> <span class=\"help-block text-danger\" [hidden]=\"!passwordError\">{{validationErrorText}}</span> </div> <div class=\"modal-footer\" [class.verify-password-footer]=\"!isMobileMode\"> <button type=\"button\" class=\"btn md-skip btn-cancel\" (click)=\"close()\">{{localization.cancel}} </button> <button type=\"submit\" class=\"btn md-skip btn-primary\" [disabled]=\"passwordError\">{{localization.submit}}</button> </div> </form> ",
32660
- styles: [".modal-header { padding: 10px; border-color: transparent; } .modal-body { position: relative; padding: 0px 5px !important; padding-top: 10px !important; } .modal-title { display: unset; margin-left: -6px; font-size: 16px !important; } .form-control { margin: 0 5%; float: none; width: 90%; } .modal-footer { padding: 10px 4px 20px 5px !important; text-align: right; border-color: transparent; color: #fff; } .text-danger { display: block; color: #ed6b75; font-size: 14px; margin: 5px 0px 0px 15px; } .btn-primary { color: #fff; height: 32px; min-width: 71px; margin-right: 10px; line-height: 0px } .password-span { color: #7f939e; font-size: 14px; margin-left: 15px; } .btn-cancel { color: #fff; height: 32px; min-width: 71px; margin-right: 6px; background-color: #dddddd; line-height: 0px } .element-password { padding-left: 8px; height: 32px; min-width: 71px; margin-left: 16px; } .verify-password-input { width: 600px; } .verify-password-footer { padding-right: 18px !important; }"]
32660
+ styles: [".modal-header { padding: 10px; border-color: transparent; } .modal-body { position: relative; padding: 0px 5px !important; padding-top: 10px !important; } .modal-title { display: unset; margin-left: -6px; font-size: 16px !important; } .form-control { margin: 0 5%; float: none; width: 90%; } .modal-footer { padding: 10px 4px 20px 5px !important; text-align: right; border-color: transparent; color: #fff; } .text-danger { display: block; color: #ed6b75; font-size: 14px; margin: 5px 0px 0px 15px; } .btn-primary { color: #fff; height: 32px; min-width: 71px; margin-right: 10px; line-height: 0px } .password-span { color: #7f939e; font-size: 14px; margin-left: 15px; } .btn-cancel { color: #fff; height: 32px; min-width: 71px; margin-right: 6px; background-color: #dddddd; line-height: 0px } .element-password { padding-left: 8px; height: 32px; min-width: 71px; margin-left: 16px; } .verify-password-input { width: 600px; } .verify-password-footer { padding-right: 18px !important; } "]
32661
32661
  }),
32662
32662
  __param(0, Object(_angular_core__WEBPACK_IMPORTED_MODULE_0__["Inject"])(_localization__WEBPACK_IMPORTED_MODULE_3__[/* LOCALIZATION */ "b"]))
32663
32663
  ], VerifyPasswordModalComponent);
@@ -32705,8 +32705,8 @@ let ConfirmOperationModalComponent = class ConfirmOperationModalComponent {
32705
32705
  ConfirmOperationModalComponent = __decorate([
32706
32706
  Object(_angular_core__WEBPACK_IMPORTED_MODULE_0__["Component"])({
32707
32707
  selector: 'confirmOperationModal',
32708
- template: "<form novalidate (ngSubmit)=\"confirm()\" bs-modal-drag> <div class=\"modal-body\"> <div style=\"text-align:center;margin-top:20px;\"> <div class=\"alertIcon\"></div> <h3 class=\"confirm-h3\">{{localization.confirmOperationPrompt}}</h3> </div> </div> <div class=\"modal-footer form-group confirm-btn-group\"> <button type=\"button\" class=\"sweet-alert-btn\" (click)=\"close()\"> {{localization.cancel}} </button> <button type=\"submit\" class=\"sweet-alert-btn\"> {{localization.confirm}} </button> </div> </form> ",
32709
- styles: [".modal-body { padding: 17px; position: relative; text-align: center; } .modal-footer { padding: 10px 5px; text-align: center; border-color: transparent; } .confirm-modal { max-width: 478px; } .confirm-modal>.modal-content { padding: 17px; } h3.confirm-h3 { font-size: 20px; font-weight: bold; } .confirm-btn-group { padding: 0; margin-bottom: 28px; margin-right: 50px; } .confirm-btn-group button { display: inline-block; } .confirm-btn-group button:last-child { background-color: rgb(221, 107, 85); box-shadow: rgba(221, 107, 85, 0.8) 0px 0px 2px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px; } .alertIcon { background: url(assets/img/alert.svg); width: 88px; height: 88px; display: inline-block; } .sweet-alert-btn { color: #fff; border: none; background: #ddd; font-size: 17px; font-weight: 500; border-radius: 5px; padding: 10px 32px; margin: 26px 5px 0; cursor: pointer } "]
32708
+ template: "<form novalidate (ngSubmit)=\"confirm()\" bs-modal-drag class=\"confirm-modal\"> <div class=\"modal-body\"> <div style=\"text-align:center;\"> <div class=\"alertIcon\"></div> <h3 class=\"confirm-h3\">{{localization.confirmOperationPrompt}}</h3> </div> </div> <div class=\"modal-footer form-group confirm-btn-group\"> <button type=\"button\" class=\"sweet-alert-btn\" (click)=\"close()\"> {{localization.cancel}} </button> <button type=\"submit\" class=\"sweet-alert-btn\"> {{localization.confirm}} </button> </div> </form> ",
32709
+ styles: [".modal-body { padding: 17px; position: relative; text-align: center; } .modal-footer { padding: 10px 5px; text-align: center; border-color: transparent; } .confirm-modal { max-width: 478px; } .confirm-modal>.modal-content { padding: 17px; } h3.confirm-h3 { font-size: 20px; font-weight: bold; margin-top: 10px; } .confirm-modal .confirm-btn-group { padding: 0 !important; margin-bottom: 28px; justify-content: center; margin-right: 0; } .confirm-btn-group button { display: inline-block; } .confirm-btn-group button:last-child { background-color: rgb(221, 107, 85); box-shadow: rgba(221, 107, 85, 0.8) 0px 0px 2px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px; } .alertIcon { background: url(assets/img/alert.svg); width: 44px; height: 44px; display: inline-block; } .sweet-alert-btn { color: #fff; border: none; background: #ddd; font-size: 17px; font-weight: 500; border-radius: 5px; padding: 10px 0; width: 100px; margin: 0 5px 0; cursor: pointer } "]
32710
32710
  }),
32711
32711
  __param(0, Object(_angular_core__WEBPACK_IMPORTED_MODULE_0__["Inject"])(_localization_localization_service__WEBPACK_IMPORTED_MODULE_2__[/* LOCALIZATION */ "b"]))
32712
32712
  ], ConfirmOperationModalComponent);
@@ -32753,7 +32753,7 @@ AlertModalComponent = __decorate([
32753
32753
  Object(_angular_core__WEBPACK_IMPORTED_MODULE_0__["Component"])({
32754
32754
  selector: 'alertModal',
32755
32755
  template: "<div class=\"modal-body\"> <div style=\"text-align:center\"> <div class=\"alertIcon\"></div> <h3 class=\"alert-h3\">{{alermMessage}}</h3> </div> </div> <div class=\"modal-footer form-group confirm-btn\"> <button type=\"button\" class=\"sweet-alert-btn\" (click)=\"close()\"> {{localization.confirm}} </button> </div> ",
32756
- styles: [".modal-header { padding: 17px; border-color: transparent; } .modal-body { padding: 17px; position: relative; text-align: center; } .modal-footer { padding: 10px 5px; text-align: center; border-color: transparent; } .alert-modal { max-width: 478px; } .alert-modal>.modal-content { padding: 17px; } h3.alert-h3 { font-size: 20px; font-weight: bold; } .alert-btn { padding: 0; margin-bottom: 0; } .alert-btn button { display: inline-block; } .alertIcon { background: url(assets/img/alert.svg); width: 88px; height: 88px; display: inline-block; } .sweet-alert-btn { color: #fff; border: none; background: #ddd; font-size: 17px; font-weight: 500; border-radius: 5px; padding: 10px 32px; margin: 26px 5px 0; cursor: point; background-color: rgb(221, 107, 85); box-shadow: rgba(221, 107, 85, 0.8) 0px 0px 2px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px; } "]
32756
+ styles: [".modal-header { padding: 17px; border-color: transparent; } .modal-body { padding: 17px; position: relative; text-align: center; } .modal-footer { padding: 10px 5px; text-align: center; border-color: transparent; } .alert-modal { max-width: 478px; } .alert-modal>.modal-content { padding: 17px; } h3.alert-h3 { font-size: 20px; font-weight: bold; margin-top: 10px; } .alert-btn { padding: 0; margin-bottom: 0; } .alert-btn button { display: inline-block; } .alertIcon { background: url(assets/img/alert.svg); width: 44px; height: 44px; display: inline-block; } .sweet-alert-btn { color: #fff; border: none; background: #ddd; font-size: 17px; font-weight: 500; border-radius: 5px; padding: 10px 0; width: 100px; margin: 0 5px 0; cursor: pointer; background-color: rgb(221, 107, 85); box-shadow: rgba(221, 107, 85, 0.8) 0px 0px 2px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px; } .form-group { padding: 0 !important; justify-content: center; margin-bottom: 28px; } "]
32757
32757
  }),
32758
32758
  __param(0, Object(_angular_core__WEBPACK_IMPORTED_MODULE_0__["Inject"])(_localization_localization_service__WEBPACK_IMPORTED_MODULE_1__[/* LOCALIZATION */ "b"]))
32759
32759
  ], AlertModalComponent);
@@ -35839,9 +35839,6 @@ class text_element_TextElementModal {
35839
35839
  this.width = width;
35840
35840
  this.height = height;
35841
35841
  this.isVariableText = isVariableText;
35842
- if (/iPhone|iPod/i.test(navigator.userAgent)) {
35843
- width += 10;
35844
- }
35845
35842
  this._element = this.getforeignObjectElement(width, height);
35846
35843
  this._element.innerHTML = '';
35847
35844
  const text = this.createNewForeignObjectText(content, font, width, height, isVariableText);
@@ -35865,10 +35862,7 @@ class text_element_TextElementModal {
35865
35862
  // 清空现有内容
35866
35863
  this._element.innerHTML = '';
35867
35864
  // 创建新的文本元素
35868
- let width = this.width;
35869
- if (/iPhone|iPod/i.test(navigator.userAgent)) {
35870
- width += 10;
35871
- }
35865
+ const width = this.width;
35872
35866
  const text = this.createNewForeignObjectText(content, this.font, width, this.height, this.isVariableText);
35873
35867
  this._element.appendChild(text);
35874
35868
  }
@@ -42549,14 +42543,102 @@ class alarm_element_AlarmElement extends conditional_display_element_Conditional
42549
42543
  this.totalCount = 0;
42550
42544
  this.pageSize = 5;
42551
42545
  this.maxVisibleRows = 15;
42546
+ this.scrollWindowStart = 0;
42552
42547
  this.rowHeight = 0;
42548
+ this.touchStartY = 0;
42549
+ this.touchScrollAccumulator = 0;
42550
+ this.thumbDragStartY = 0;
42551
+ this.thumbDragStartWindow = 0;
42552
+ this.thumbDragging = false;
42553
42553
  this.isDisposed = false;
42554
+ this.handleScrollWheel = (event) => {
42555
+ if (!this.rowHeight)
42556
+ return;
42557
+ event.preventDefault();
42558
+ this.touchScrollAccumulator += event.deltaY;
42559
+ const rowDelta = Math.trunc(this.touchScrollAccumulator / this.rowHeight);
42560
+ if (rowDelta !== 0) {
42561
+ this.touchScrollAccumulator -= rowDelta * this.rowHeight;
42562
+ this.moveScrollWindowBy(rowDelta);
42563
+ }
42564
+ };
42565
+ this.handleScrollTouchStart = (event) => {
42566
+ this.touchStartY = event.touches[0].clientY;
42567
+ this.touchScrollAccumulator = 0;
42568
+ };
42569
+ this.handleScrollTouchMove = (event) => {
42570
+ if (!this.rowHeight)
42571
+ return;
42572
+ event.preventDefault();
42573
+ const currentY = event.touches[0].clientY;
42574
+ const deltaY = this.touchStartY - currentY;
42575
+ this.touchStartY = currentY;
42576
+ this.touchScrollAccumulator += deltaY;
42577
+ const rowDelta = Math.trunc(this.touchScrollAccumulator / this.rowHeight);
42578
+ if (rowDelta !== 0) {
42579
+ this.touchScrollAccumulator -= rowDelta * this.rowHeight;
42580
+ this.moveScrollWindowBy(rowDelta);
42581
+ }
42582
+ };
42554
42583
  this.handleScrollMouseEnter = () => {
42555
42584
  clearInterval(this.scrollIntervalId);
42556
42585
  };
42557
42586
  this.handleScrollMouseLeave = () => {
42587
+ if (!this.thumbDragging)
42588
+ this.startAutoScroll();
42589
+ };
42590
+ // 拖拽滑块时挂全局 mousemove/mouseup,允许拖出滚动条区域也能继续跟手,松开时才解绑
42591
+ this.handleThumbDragStart = (event) => {
42592
+ event.preventDefault();
42593
+ event.stopPropagation();
42594
+ this.thumbDragging = true;
42595
+ clearInterval(this.scrollIntervalId);
42596
+ this.thumbDragStartY = this.getClientY(event);
42597
+ this.thumbDragStartWindow = this.scrollWindowStart;
42598
+ document.addEventListener('mousemove', this.handleThumbDragMove);
42599
+ document.addEventListener('mouseup', this.handleThumbDragEnd);
42600
+ document.addEventListener('touchmove', this.handleThumbDragMove, { passive: false });
42601
+ document.addEventListener('touchend', this.handleThumbDragEnd);
42602
+ };
42603
+ this.handleThumbDragMove = (event) => {
42604
+ if (!this.thumbDragging || !this.scrollTrack)
42605
+ return;
42606
+ event.preventDefault();
42607
+ const maxWindowStart = Math.max(0, this.pageSize - this.maxVisibleRows);
42608
+ if (maxWindowStart <= 0)
42609
+ return;
42610
+ const trackHeight = this.scrollTrack.clientHeight;
42611
+ const thumbHeightRatio = Math.min(1, this.maxVisibleRows / this.pageSize);
42612
+ const thumbHeight = Math.max(20, trackHeight * thumbHeightRatio);
42613
+ const maxThumbTop = trackHeight - thumbHeight;
42614
+ if (maxThumbTop <= 0)
42615
+ return;
42616
+ const deltaY = this.getClientY(event) - this.thumbDragStartY;
42617
+ const deltaWindow = Math.round((deltaY / maxThumbTop) * maxWindowStart);
42618
+ const nextStart = Math.min(maxWindowStart, Math.max(0, this.thumbDragStartWindow + deltaWindow));
42619
+ if (nextStart === this.scrollWindowStart)
42620
+ return;
42621
+ this.scrollWindowStart = nextStart;
42622
+ this.renderScrollWindow(this.scrollWindowStart);
42623
+ };
42624
+ this.handleThumbDragEnd = () => {
42625
+ this.thumbDragging = false;
42626
+ document.removeEventListener('mousemove', this.handleThumbDragMove);
42627
+ document.removeEventListener('mouseup', this.handleThumbDragEnd);
42628
+ document.removeEventListener('touchmove', this.handleThumbDragMove);
42629
+ document.removeEventListener('touchend', this.handleThumbDragEnd);
42558
42630
  this.startAutoScroll();
42559
42631
  };
42632
+ // 点击轨道空白处(非滑块)时整页跳转,方向取决于点击位置在滑块上方还是下方
42633
+ this.handleTrackClick = (event) => {
42634
+ if (event.target !== this.scrollTrack || !this.scrollTrack)
42635
+ return;
42636
+ const trackRect = this.scrollTrack.getBoundingClientRect();
42637
+ const clickTop = event.clientY - trackRect.top;
42638
+ const thumbTop = parseFloat(this.scrollThumb.style.top) || 0;
42639
+ const rowDelta = clickTop < thumbTop ? -this.maxVisibleRows : this.maxVisibleRows;
42640
+ this.moveScrollWindowBy(rowDelta);
42641
+ };
42560
42642
  this.rootElement.selectAll('*').remove();
42561
42643
  this.setStatusAsLoading();
42562
42644
  this.logger = injector.get(logger["b" /* LOGGER_SERVICE_TOKEN */]);
@@ -42787,10 +42869,8 @@ class alarm_element_AlarmElement extends conditional_display_element_Conditional
42787
42869
  }, 500);
42788
42870
  }
42789
42871
  refreshCurrentPageData() {
42790
- var _a;
42791
42872
  if (this.isDisposed)
42792
42873
  return;
42793
- const currentScrollTop = ((_a = this.scrollContainer) === null || _a === void 0 ? void 0 : _a.scrollTop) || 0;
42794
42874
  if (this.model.filterSetting.alarmType) {
42795
42875
  this.updateQueryTimeRange();
42796
42876
  const input = new _tmp_config["c" /* GetAlarmsArgs */](this.alarmNames, this.startTime, this.endTime, this.pageSize, this.page * this.pageSize);
@@ -42806,9 +42886,6 @@ class alarm_element_AlarmElement extends conditional_display_element_Conditional
42806
42886
  item.triggeredTime.format('YYYY-MM-DD HH:mm:ss')
42807
42887
  ]);
42808
42888
  this.updateTableContent();
42809
- if (this.scrollContainer) {
42810
- this.scrollContainer.scrollTop = currentScrollTop;
42811
- }
42812
42889
  }
42813
42890
  });
42814
42891
  }
@@ -42832,9 +42909,6 @@ class alarm_element_AlarmElement extends conditional_display_element_Conditional
42832
42909
  item.triggeredTime.format('YYYY-MM-DD HH:mm:ss')
42833
42910
  ]);
42834
42911
  this.updateTableContent();
42835
- if (this.scrollContainer) {
42836
- this.scrollContainer.scrollTop = currentScrollTop;
42837
- }
42838
42912
  }
42839
42913
  });
42840
42914
  }
@@ -42843,37 +42917,63 @@ class alarm_element_AlarmElement extends conditional_display_element_Conditional
42843
42917
  if (!this.element)
42844
42918
  return;
42845
42919
  const useScroll = this.pageSize > this.maxVisibleRows;
42846
- const dataRowHeight = this.model.heights[1] || this.model.heights[0];
42847
- // 查找表格主体
42848
- let tbody;
42849
42920
  if (useScroll) {
42850
- const bodyContainer = this.element.querySelector('div > div:nth-child(2)');
42851
- if (!bodyContainer)
42852
- return;
42853
- const bodyTable = bodyContainer.querySelector('table');
42854
- if (!bodyTable)
42921
+ if (!this.scrollContent)
42855
42922
  return;
42856
- tbody = bodyTable;
42857
- }
42858
- else {
42859
- const table = this.element.querySelector('table');
42860
- if (!table)
42861
- return;
42862
- tbody = table;
42923
+ this.scrollWindowStart = Math.min(this.scrollWindowStart, Math.max(0, this.pageSize - this.maxVisibleRows));
42924
+ this.renderScrollWindow(this.scrollWindowStart);
42925
+ return;
42863
42926
  }
42927
+ const table = this.element.querySelector('table');
42928
+ if (!table)
42929
+ return;
42864
42930
  // 清除现有数据行(保留表头)
42865
- const rows = Array.from(tbody.querySelectorAll('tr'));
42931
+ const rows = Array.from(table.querySelectorAll('tr'));
42866
42932
  rows.forEach((row, index) => {
42867
- if (index > 0 || useScroll) { // 对于 scrollable 模式,表头在另一个 table,所以这里全部清除
42933
+ if (index > 0) {
42868
42934
  row.remove();
42869
42935
  }
42870
42936
  });
42871
42937
  // 重新生成数据行
42872
42938
  for (let row = 0; row < this.pageSize; row++) {
42873
- const rowHeight = useScroll ? dataRowHeight : this.model.heights[row + 1];
42874
- tbody.appendChild(this.buildDataRow(row, rowHeight));
42939
+ table.appendChild(this.buildDataRow(row, this.model.heights[row + 1]));
42875
42940
  }
42876
42941
  }
42942
+ renderScrollWindow(startRow) {
42943
+ const table = this.scrollContent;
42944
+ if (!table)
42945
+ return;
42946
+ // 清除现有数据行(保留表头,即第一个 <tr>)
42947
+ const rows = Array.from(table.querySelectorAll('tr'));
42948
+ rows.forEach((row, index) => {
42949
+ if (index > 0) {
42950
+ row.remove();
42951
+ }
42952
+ });
42953
+ for (let i = 0; i < this.maxVisibleRows; i++) {
42954
+ table.appendChild(this.buildDataRow(startRow + i, this.rowHeight));
42955
+ }
42956
+ this.updateScrollThumb();
42957
+ }
42958
+ // 滑块高度按"可见行数/总行数"的比例算,位置按当前窗口起始行占可滚动范围的比例算,
42959
+ // 只用 top/height 两个数值属性更新,不涉及 transform,不会触发合成层
42960
+ updateScrollThumb() {
42961
+ if (!this.scrollTrack || !this.scrollThumb)
42962
+ return;
42963
+ const maxWindowStart = Math.max(0, this.pageSize - this.maxVisibleRows);
42964
+ if (maxWindowStart <= 0) {
42965
+ this.scrollThumb.style.display = 'none';
42966
+ return;
42967
+ }
42968
+ this.scrollThumb.style.display = 'block';
42969
+ const trackHeight = this.scrollTrack.clientHeight;
42970
+ const thumbHeightRatio = Math.min(1, this.maxVisibleRows / this.pageSize);
42971
+ const thumbHeight = Math.max(20, trackHeight * thumbHeightRatio);
42972
+ const maxThumbTop = trackHeight - thumbHeight;
42973
+ const thumbTop = maxThumbTop * (this.scrollWindowStart / maxWindowStart);
42974
+ this.scrollThumb.style.height = `${thumbHeight}px`;
42975
+ this.scrollThumb.style.top = `${thumbTop}px`;
42976
+ }
42877
42977
  getIconScale() {
42878
42978
  const initSize = this.model.initSize;
42879
42979
  if (!initSize || !initSize.width || !initSize.height) {
@@ -42996,7 +43096,8 @@ class alarm_element_AlarmElement extends conditional_display_element_Conditional
42996
43096
  return tr;
42997
43097
  }
42998
43098
  renderPlainTable(elementHeight, operationAreaReservedHeight) {
42999
- this.scrollContainer = null;
43099
+ this.scrollContent = null;
43100
+ this.scrollWindowStart = 0;
43000
43101
  const table = document.createElement('table');
43001
43102
  table.style.cssText = `border: 2px solid ${this.model.generalSetting.stroke};
43002
43103
  height: ${elementHeight - operationAreaReservedHeight}px;`;
@@ -43012,42 +43113,41 @@ class alarm_element_AlarmElement extends conditional_display_element_Conditional
43012
43113
  renderScrollableTable(elementHeight, operationAreaReservedHeight) {
43013
43114
  const dataRowHeight = this.model.heights[1] || this.model.heights[0];
43014
43115
  this.rowHeight = dataRowHeight;
43116
+ this.scrollWindowStart = 0;
43117
+ // 之前用"表头/表体分两个 table + flex 包裹容器 + overflow/transform 滚动"的结构,
43118
+ // 在 iOS WKWebView 上表体会明显放大错位,换了两种滚动实现方式(原生 overflow:auto、
43119
+ // JS transform 平移)都复现同样的问题,怀疑是这套多层 div + 双 table 结构本身的问题。
43120
+ // 这里改成和 renderPlainTable() 完全同构的单 table 结构(已验证在所有平台上都正常),
43121
+ // DOM 里始终只保留 maxVisibleRows 行数据,滚动时只增删 <tr> 内容,不做任何位移/滚动。
43122
+ // 自定义滚动条(轨道+滑块)只用 position + top/height 定位,不用 overflow/transform/
43123
+ // will-change,这些属性都不会触发 WebKit 合成层提升,不会重新引入 iOS 缩放错位问题。
43124
+ const scrollbarWidth = 6;
43015
43125
  const wrapper = document.createElement('div');
43016
- wrapper.style.cssText = `display: flex;
43017
- flex-direction: column;
43018
- box-sizing: border-box;
43019
- border: 2px solid ${this.model.generalSetting.stroke};
43126
+ wrapper.style.cssText = `position: relative; width: ${this.model.size.width}px;
43127
+ height: ${elementHeight - operationAreaReservedHeight}px;`;
43128
+ const table = document.createElement('table');
43129
+ table.style.cssText = `border: 2px solid ${this.model.generalSetting.stroke};
43020
43130
  height: ${elementHeight - operationAreaReservedHeight}px;
43021
- overflow: hidden;`;
43022
- const headerWrapper = document.createElement('div');
43023
- headerWrapper.style.cssText = 'flex: none; box-sizing: border-box; overflow: hidden;';
43024
- const headerTable = document.createElement('table');
43025
- headerTable.style.cssText = 'width: 100%; table-layout: fixed;';
43026
- headerTable.appendChild(this.buildHeaderRow());
43027
- headerWrapper.appendChild(headerTable);
43028
- const bodyContainer = document.createElement('div');
43029
- bodyContainer.style.cssText = `flex: none;
43030
- overflow-y: auto;
43031
- overflow-x: hidden;
43032
- height: ${dataRowHeight * this.maxVisibleRows}px;`;
43033
- const bodyTable = document.createElement('table');
43034
- bodyTable.style.cssText = 'width: 100%; table-layout: fixed;';
43035
- for (let row = 0; row < this.pageSize; row++) {
43036
- bodyTable.appendChild(this.buildDataRow(row, dataRowHeight));
43037
- }
43038
- bodyContainer.appendChild(bodyTable);
43039
- wrapper.appendChild(headerWrapper);
43040
- wrapper.appendChild(bodyContainer);
43131
+ width: calc(100% - ${scrollbarWidth}px);`;
43132
+ table.appendChild(this.buildHeaderRow());
43133
+ wrapper.appendChild(table);
43134
+ const track = document.createElement('div');
43135
+ track.style.cssText = `position: absolute; top: 0; right: 0; width: ${scrollbarWidth}px;
43136
+ height: 100%; background: rgba(0,0,0,0.06); border-radius: ${scrollbarWidth / 2}px;`;
43137
+ const thumb = document.createElement('div');
43138
+ thumb.style.cssText = `position: absolute; left: 0; width: 100%;
43139
+ background: rgba(0,0,0,0.35); border-radius: ${scrollbarWidth / 2}px; cursor: pointer;`;
43140
+ track.appendChild(thumb);
43141
+ wrapper.appendChild(track);
43041
43142
  this.element.appendChild(wrapper);
43042
- this.scrollContainer = bodyContainer;
43043
- requestAnimationFrame(() => {
43044
- const scrollbarWidth = bodyContainer.offsetWidth - bodyContainer.clientWidth;
43045
- if (scrollbarWidth > 0) {
43046
- headerWrapper.style.paddingRight = scrollbarWidth + 'px';
43047
- }
43048
- });
43049
- this.hoverTarget = bodyContainer;
43143
+ this.scrollContent = table;
43144
+ this.scrollTrack = track;
43145
+ this.scrollThumb = thumb;
43146
+ this.renderScrollWindow(0);
43147
+ this.updateScrollThumb();
43148
+ this.hoverTarget = table;
43050
43149
  this.bindScrollHoverEvents();
43150
+ this.bindScrollbarEvents();
43051
43151
  this.startAutoScroll();
43052
43152
  }
43053
43153
  bindScrollHoverEvents() {
@@ -43055,41 +43155,93 @@ class alarm_element_AlarmElement extends conditional_display_element_Conditional
43055
43155
  return;
43056
43156
  this.hoverTarget.addEventListener('mouseenter', this.handleScrollMouseEnter);
43057
43157
  this.hoverTarget.addEventListener('mouseleave', this.handleScrollMouseLeave);
43158
+ if (this.scrollContent) {
43159
+ this.hoverTarget.addEventListener('wheel', this.handleScrollWheel, { passive: false });
43160
+ this.hoverTarget.addEventListener('touchstart', this.handleScrollTouchStart, { passive: true });
43161
+ this.hoverTarget.addEventListener('touchmove', this.handleScrollTouchMove, { passive: false });
43162
+ }
43058
43163
  }
43059
43164
  unbindScrollHoverEvents() {
43165
+ this.unbindScrollbarEvents();
43060
43166
  if (!this.hoverTarget)
43061
43167
  return;
43062
43168
  this.hoverTarget.removeEventListener('mouseenter', this.handleScrollMouseEnter);
43063
43169
  this.hoverTarget.removeEventListener('mouseleave', this.handleScrollMouseLeave);
43170
+ this.hoverTarget.removeEventListener('wheel', this.handleScrollWheel);
43171
+ this.hoverTarget.removeEventListener('touchstart', this.handleScrollTouchStart);
43172
+ this.hoverTarget.removeEventListener('touchmove', this.handleScrollTouchMove);
43064
43173
  this.hoverTarget = null;
43065
43174
  }
43175
+ // 手动滚动只在当前可见窗口范围内移动(不触发翻页),到顶/到底停住,
43176
+ // 和自动滚动定时器(会在到底后自动翻页)职责区分开
43177
+ moveScrollWindowBy(rowDelta) {
43178
+ if (!this.scrollContent)
43179
+ return;
43180
+ const maxWindowStart = Math.max(0, this.pageSize - this.maxVisibleRows);
43181
+ const nextStart = Math.min(maxWindowStart, Math.max(0, this.scrollWindowStart + rowDelta));
43182
+ if (nextStart === this.scrollWindowStart)
43183
+ return;
43184
+ this.scrollWindowStart = nextStart;
43185
+ this.renderScrollWindow(this.scrollWindowStart);
43186
+ }
43187
+ bindScrollbarEvents() {
43188
+ if (!this.scrollTrack || !this.scrollThumb)
43189
+ return;
43190
+ this.scrollThumb.addEventListener('mousedown', this.handleThumbDragStart);
43191
+ this.scrollThumb.addEventListener('touchstart', this.handleThumbDragStart, { passive: true });
43192
+ this.scrollTrack.addEventListener('mousedown', this.handleTrackClick);
43193
+ }
43194
+ unbindScrollbarEvents() {
43195
+ if (this.scrollThumb) {
43196
+ this.scrollThumb.removeEventListener('mousedown', this.handleThumbDragStart);
43197
+ this.scrollThumb.removeEventListener('touchstart', this.handleThumbDragStart);
43198
+ }
43199
+ if (this.scrollTrack) {
43200
+ this.scrollTrack.removeEventListener('mousedown', this.handleTrackClick);
43201
+ }
43202
+ document.removeEventListener('mousemove', this.handleThumbDragMove);
43203
+ document.removeEventListener('mouseup', this.handleThumbDragEnd);
43204
+ document.removeEventListener('touchmove', this.handleThumbDragMove);
43205
+ document.removeEventListener('touchend', this.handleThumbDragEnd);
43206
+ this.scrollTrack = null;
43207
+ this.scrollThumb = null;
43208
+ }
43209
+ getClientY(event) {
43210
+ return 'touches' in event ? event.touches[0].clientY : event.clientY;
43211
+ }
43066
43212
  startAutoScroll() {
43067
43213
  clearInterval(this.scrollIntervalId);
43068
43214
  if (!this.model.generalSetting.autoScroll)
43069
43215
  return;
43070
43216
  const totalPages = Math.max(1, Math.ceil(this.totalCount / this.pageSize));
43071
- if (totalPages <= 1 && !this.scrollContainer)
43217
+ if (totalPages <= 1 && !this.scrollContent)
43072
43218
  return;
43073
43219
  const duration = (this.model.generalSetting.scrollDuration || 10) * 1000;
43074
43220
  this.scrollIntervalId = setInterval(() => this.scrollOneRow(), duration);
43075
43221
  }
43076
43222
  scrollOneRow() {
43077
- const el = this.scrollContainer;
43078
- if (!el) {
43223
+ if (!this.scrollContent) {
43079
43224
  this.autoNextPage();
43080
43225
  return;
43081
43226
  }
43082
- const maxScrollTop = el.scrollHeight - el.clientHeight;
43083
- if (maxScrollTop <= 1 || el.scrollTop >= maxScrollTop - 1) {
43227
+ const maxWindowStart = this.pageSize - this.maxVisibleRows;
43228
+ if (maxWindowStart <= 0 || this.scrollWindowStart >= maxWindowStart) {
43084
43229
  this.autoNextPage();
43085
43230
  return;
43086
43231
  }
43087
- el.scrollTop = Math.min(el.scrollTop + this.rowHeight, maxScrollTop);
43232
+ this.scrollWindowStart += 1;
43233
+ this.renderScrollWindow(this.scrollWindowStart);
43088
43234
  }
43089
43235
  autoNextPage() {
43090
43236
  const totalPages = Math.max(1, Math.ceil(this.totalCount / this.pageSize));
43091
- if (totalPages <= 1)
43237
+ if (totalPages <= 1) {
43238
+ // 只有一页数据:没有下一页可翻,循环回本页顶部继续滚动
43239
+ if (this.scrollContent) {
43240
+ this.scrollWindowStart = 0;
43241
+ this.renderScrollWindow(0);
43242
+ }
43092
43243
  return;
43244
+ }
43093
43245
  const lastPage = totalPages - 1;
43094
43246
  this.page = this.page < lastPage ? this.page + 1 : 0;
43095
43247
  this.getAlarmData();