@flexem/fc-gui 3.0.0-alpha.157 → 3.0.0-alpha.159

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 (27) hide show
  1. package/bundles/@flexem/fc-gui.umd.js +358 -28
  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/communication/variable/variable-state-enum.d.ts +1 -0
  6. package/communication/variable/variable-state-enum.js +1 -0
  7. package/communication/variable/variable-state-enum.metadata.json +1 -1
  8. package/config/variable/variable-store.d.ts +1 -0
  9. package/elements/base/state-control-element.js +17 -4
  10. package/elements/historical-curve/historical-curve.element.d.ts +8 -1
  11. package/elements/historical-curve/historical-curve.element.js +306 -22
  12. package/elements/historical-curve/historical-curve.element.metadata.json +1 -1
  13. package/elements/switch-indicator-light/switch-indicator-light-element.js +9 -2
  14. package/gui/gui-context.d.ts +7 -0
  15. package/gui/gui-view.js +16 -1
  16. package/localization/localization.service.d.ts +4 -0
  17. package/localization/localization.service.js +4 -0
  18. package/localization/localization.service.metadata.json +1 -1
  19. package/localization/localization.service.zh_CN.js +4 -0
  20. package/localization/localization.service.zh_CN.metadata.json +1 -1
  21. package/model/shared/state/state.d.ts +1 -0
  22. package/model/shared/state/state.js +1 -0
  23. package/model/shared/state/state.metadata.json +1 -1
  24. package/package.json +1 -1
  25. package/service/system-text-library.service.d.ts +1 -0
  26. package/service/system-text-library.service.js +2 -1
  27. package/service/system-text-library.service.metadata.json +1 -1
@@ -3,5 +3,6 @@ export declare enum VariableStateEnum {
3
3
  Offline = 1,
4
4
  Unbind = 3,
5
5
  DataNormal = 9,
6
+ InvalidMonitor = 10,
6
7
  Abnormal = 99
7
8
  }
@@ -4,5 +4,6 @@ export var VariableStateEnum;
4
4
  VariableStateEnum[VariableStateEnum["Offline"] = 1] = "Offline";
5
5
  VariableStateEnum[VariableStateEnum["Unbind"] = 3] = "Unbind";
6
6
  VariableStateEnum[VariableStateEnum["DataNormal"] = 9] = "DataNormal";
7
+ VariableStateEnum[VariableStateEnum["InvalidMonitor"] = 10] = "InvalidMonitor";
7
8
  VariableStateEnum[VariableStateEnum["Abnormal"] = 99] = "Abnormal";
8
9
  })(VariableStateEnum || (VariableStateEnum = {}));
@@ -1 +1 @@
1
- [{"__symbolic":"module","version":4,"metadata":{"VariableStateEnum":{"Normal":0,"Offline":1,"Unbind":3,"DataNormal":9,"Abnormal":99}}}]
1
+ [{"__symbolic":"module","version":4,"metadata":{"VariableStateEnum":{"Normal":0,"Offline":1,"Unbind":3,"DataNormal":9,"InvalidMonitor":10,"Abnormal":99}}}]
@@ -4,4 +4,5 @@ export interface VariableStore {
4
4
  * 老数据转换
5
5
  */
6
6
  getVariableName(input: GetVariableNameArgs): string;
7
+ getAllVariableNames?(): string[];
7
8
  }
@@ -65,26 +65,34 @@ export class StateControlElement extends ConditionalDynamicDisplayElement {
65
65
  case VariableStateEnum.Abnormal:
66
66
  currentState = State.Abnormal;
67
67
  break;
68
+ case VariableStateEnum.InvalidMonitor:
69
+ currentState = State.InvalidMonitor;
70
+ break;
68
71
  }
69
72
  return currentState;
70
73
  }
71
74
  changeState(state) {
72
75
  switch (state) {
76
+ case State.InvalidMonitor:
77
+ this.state = State.InvalidMonitor;
78
+ break;
73
79
  case State.Unbind:
74
- this.state = State.Unbind;
80
+ if (this.state !== State.InvalidMonitor) {
81
+ this.state = State.Unbind;
82
+ }
75
83
  break;
76
84
  case State.Offline:
77
- if (this.state !== State.Unbind) {
85
+ if (this.state !== State.Unbind && this.state !== State.InvalidMonitor) {
78
86
  this.state = State.Offline;
79
87
  }
80
88
  break;
81
89
  case State.Loading:
82
- if (this.state !== State.Unbind && this.state !== State.Offline) {
90
+ if (this.state !== State.Unbind && this.state !== State.Offline && this.state !== State.InvalidMonitor) {
83
91
  this.state = State.Loading;
84
92
  }
85
93
  break;
86
94
  case State.Abnormal:
87
- if (this.state !== State.Unbind && this.state !== State.Offline && this.state !== State.Loading) {
95
+ if (this.state !== State.Unbind && this.state !== State.Offline && this.state !== State.Loading && this.state !== State.InvalidMonitor) {
88
96
  this.state = State.Abnormal;
89
97
  }
90
98
  break;
@@ -123,6 +131,11 @@ export class StateControlElement extends ConditionalDynamicDisplayElement {
123
131
  title = this.localization.disable;
124
132
  stroke = '#ff4444';
125
133
  break;
134
+ case State.InvalidMonitor:
135
+ url = 'assets/img/loading.svg';
136
+ title = this.localization.invalidMonitor;
137
+ stroke = '#226abc';
138
+ break;
126
139
  default:
127
140
  url = 'assets/img/loading.svg';
128
141
  title = this.localization.loading;
@@ -33,6 +33,8 @@ export declare class HistoricalCurveElement extends ConditionalDisplayElement {
33
33
  */
34
34
  private currentEndTime;
35
35
  private currentTimePeriod;
36
+ private customStartTime;
37
+ private customEndTime;
36
38
  private refreshIntervalId;
37
39
  private elementStatus;
38
40
  private chartElement;
@@ -42,6 +44,8 @@ export declare class HistoricalCurveElement extends ConditionalDisplayElement {
42
44
  private resizeEventListener;
43
45
  private isAndroid;
44
46
  private needResize;
47
+ private dropdownListEl;
48
+ private dropdownTriggerEl;
45
49
  constructor(element: HTMLElement, injector: Injector, permissionChecker: PermissionChecker, variableCommunicator: VariableCommunicator, variableStore: VariableStore, historyDataStore: HistoryDataStore, signalRAppId: string, systemTextLibraryService?: SystemTextLibraryService, languageService?: LanguageService, guiContext?: GuiContext);
46
50
  dispose(): void;
47
51
  private initKeyboardListener;
@@ -60,7 +64,7 @@ export declare class HistoricalCurveElement extends ConditionalDisplayElement {
60
64
  private getCurrentCulture;
61
65
  private getValidTimePeriods;
62
66
  /**
63
- * 获取时间段文案(从系统文本库获取多语种翻译)
67
+ * 获取时间段文案(从系统文本库获取多语种翻译,fallback 到 localization)
64
68
  */
65
69
  private getTimePeriodText;
66
70
  private updateTimeRange;
@@ -75,6 +79,9 @@ export declare class HistoricalCurveElement extends ConditionalDisplayElement {
75
79
  private renderCommonProperty;
76
80
  private renderOperationArea;
77
81
  private timeFormat;
82
+ private fmtDatetimeLocal;
83
+ private showCustomTimeRangeModal;
84
+ private showCustomTimeRangeModalFallback;
78
85
  private loadFirstPage;
79
86
  private loadNextPage;
80
87
  private loadPreviousPage;
@@ -32,9 +32,13 @@ export class HistoricalCurveElement extends ConditionalDisplayElement {
32
32
  operationButtonHeight: 24,
33
33
  operationButtonMargin: 4
34
34
  };
35
+ this.customStartTime = null;
36
+ this.customEndTime = null;
35
37
  this.elementStatus = HistoricalCurveElementStatus.Loading;
36
38
  this.data = [];
37
39
  this.needResize = true;
40
+ this.dropdownListEl = null;
41
+ this.dropdownTriggerEl = null;
38
42
  this.setNeedResize = () => {
39
43
  this.needResize = false;
40
44
  setTimeout(() => this.needResize = true, 500);
@@ -118,21 +122,43 @@ export class HistoricalCurveElement extends ConditionalDisplayElement {
118
122
  * 更新语种相关的文案(时间段选择器)
119
123
  */
120
124
  updateLanguageTexts() {
121
- // 重新生成时间段数据
122
125
  const updatedTimePeriods = this.getValidTimePeriods();
123
126
  this.timePeriods = updatedTimePeriods;
124
- // select 在 foreignObject 内部的 HTML 命名空间中,D3 的 rootElement.select('select') 无法跨命名空间查找
125
- // 改用 jQuery 查找
126
- const nativeSelectEl = this.$element && this.$element.find('select');
127
- if (nativeSelectEl && nativeSelectEl.length > 0) {
128
- const options = nativeSelectEl.find('option');
129
- options.each(function (i) {
127
+ // 更新下拉列表各选项文字
128
+ if (this.dropdownListEl) {
129
+ const items = this.dropdownListEl.querySelectorAll('.hc-dropdown-item');
130
+ items.forEach((item, i) => {
130
131
  if (i < updatedTimePeriods.length) {
131
- this.text = updatedTimePeriods[i].name;
132
+ item.textContent = updatedTimePeriods[i].name;
132
133
  }
133
134
  });
135
+ // 文字更新后重新测量宽度,修正首次加载时文字为空导致宽度过小的问题
136
+ const prevDisplay = this.dropdownListEl.style.display;
137
+ const prevVisibility = this.dropdownListEl.style.visibility;
138
+ this.dropdownListEl.style.display = 'block';
139
+ this.dropdownListEl.style.visibility = 'hidden';
140
+ this.dropdownListEl.style.width = '';
141
+ const newWidth = this.dropdownListEl.offsetWidth;
142
+ this.dropdownListEl.style.display = prevDisplay;
143
+ this.dropdownListEl.style.visibility = prevVisibility;
144
+ this.dropdownListEl.style.width = newWidth + 'px';
145
+ if (this.dropdownTriggerEl) {
146
+ this.dropdownTriggerEl.style.width = newWidth + 'px';
147
+ }
148
+ }
149
+ // 更新触发按钮文字(显示当前选中项的最新语言文案)
150
+ if (this.dropdownTriggerEl) {
151
+ const selected = updatedTimePeriods.find(p => p.key === Number(this.currentTimePeriod));
152
+ if (selected) {
153
+ const textNode = Array.from(this.dropdownTriggerEl.childNodes).find(n => n.nodeType === Node.TEXT_NODE);
154
+ if (textNode) {
155
+ textNode.textContent = selected.name;
156
+ }
157
+ else {
158
+ this.dropdownTriggerEl.insertBefore(document.createTextNode(selected.name), this.dropdownTriggerEl.firstChild);
159
+ }
160
+ }
134
161
  }
135
- // 如果 select 元素不存在,timePeriods 已更新,下次 renderOperationArea 时会使用新的文本
136
162
  }
137
163
  /**
138
164
  * 获取当前语种的 culture 代码
@@ -168,12 +194,13 @@ export class HistoricalCurveElement extends ConditionalDisplayElement {
168
194
  timePeriods.push({ key: 3, name: this.getTimePeriodText(TIME_PERIOD_KEYS.LAST_SEVEN_DAYS) });
169
195
  timePeriods.push({ key: 4, name: this.getTimePeriodText(TIME_PERIOD_KEYS.LAST_THIRTY_DAYS) });
170
196
  timePeriods.push({ key: 5, name: this.getTimePeriodText(TIME_PERIOD_KEYS.LAST_ONE_YEAR) });
197
+ timePeriods.push({ key: 8, name: this.getTimePeriodText(TIME_PERIOD_KEYS.CUSTOM, this.localization.customTimeRange) });
171
198
  return timePeriods;
172
199
  }
173
200
  /**
174
- * 获取时间段文案(从系统文本库获取多语种翻译)
201
+ * 获取时间段文案(从系统文本库获取多语种翻译,fallback 到 localization)
175
202
  */
176
- getTimePeriodText(textKey) {
203
+ getTimePeriodText(textKey, fallback = '') {
177
204
  const currentCulture = this.getCurrentCulture();
178
205
  const systemType = SYSTEM_TEXT_LIBRARY_TYPES.COMPONENT_BUILTIN;
179
206
  if (this.systemTextLibraryService) {
@@ -182,13 +209,20 @@ export class HistoricalCurveElement extends ConditionalDisplayElement {
182
209
  return translation;
183
210
  }
184
211
  }
185
- return '';
212
+ return fallback;
186
213
  }
187
214
  updateTimeRange(timePeriodType) {
188
215
  this.currentTimePeriod = +timePeriodType;
189
216
  this.updateQueryTimeRange();
190
217
  }
191
218
  updateQueryTimeRange() {
219
+ if (this.currentTimePeriod === 8) {
220
+ if (this.customStartTime && this.customEndTime) {
221
+ this.startTime = this.customStartTime.clone();
222
+ this.endTime = this.customEndTime.clone();
223
+ }
224
+ return;
225
+ }
192
226
  this.endTime = moment();
193
227
  switch (this.currentTimePeriod) {
194
228
  case 1:
@@ -563,18 +597,143 @@ export class HistoricalCurveElement extends ConditionalDisplayElement {
563
597
  const operationArea = this.rootElement.append('g').attr('transform', `translate(0,${chartHeight + this.displayOption.operationAreaMarginTop})`)
564
598
  .append('foreignObject').attr('width', chartWidth).attr('height', this.displayOption.operationAreaHeight).attr('fill', 'none')
565
599
  .append('xhtml:div').style('height', (this.displayOption.operationAreaHeight - 4) + 'px').style('overflow', 'hidden').style('margin-top', '4px');
566
- const selectElement = operationArea.append('select').style('margin-left', this.displayOption.marginLeft + 'px')
567
- .style('background-color', backgroundColor)
568
- .style('font-size', this.displayOption.operationSelectFontSize).on('change', () => {
569
- const displayTimePeriod = this.rootElement.select('select').property('value');
570
- this.updateTimeRange(displayTimePeriod);
571
- this.reRenderElement(this.startTime, this.endTime, this.displayOption.dataLimit, HistoricalCurveTimeRange.BeginOpenEndOpen);
572
- });
573
600
  const rect = this.$element.parent().parent().find('rect');
574
601
  const fillColor = rect.attr('fill');
575
- const options = selectElement.selectAll('option').data(this.timePeriods).enter().append('option');
576
- options.text(d => d.name).attr('value', d => d.key).property('selected', d => d.key === Number(this.currentTimePeriod))
577
- .style('background-color', this.model.displaySetting.axisSetting.filterBackgroudColor || fillColor);
602
+ const dropdownBg = this.model.displaySetting.axisSetting.filterBackgroudColor || fillColor || '#fff';
603
+ const dropdownWrapper = operationArea.append('div')
604
+ .style('display', 'inline-block')
605
+ .style('position', 'relative')
606
+ .style('margin-left', this.displayOption.marginLeft + 'px')
607
+ .style('vertical-align', 'middle');
608
+ const selectedText = this.timePeriods.find(t => t.key === Number(this.currentTimePeriod));
609
+ const dropdownTrigger = dropdownWrapper.append('div')
610
+ .attr('class', 'hc-dropdown-trigger')
611
+ .style('background-color', backgroundColor)
612
+ .style('font-size', this.displayOption.operationSelectFontSize)
613
+ .style('cursor', 'pointer')
614
+ .style('padding', '0 18px 0 4px')
615
+ .style('border', '1px solid #ccc')
616
+ .style('border-radius', '2px')
617
+ .style('position', 'relative')
618
+ .style('white-space', 'nowrap')
619
+ .style('line-height', (this.displayOption.operationAreaHeight - 8) + 'px')
620
+ .style('min-height', (this.displayOption.operationAreaHeight - 8) + 'px')
621
+ .text(selectedText ? selectedText.name : '');
622
+ dropdownTrigger.append('span')
623
+ .style('position', 'absolute')
624
+ .style('right', '4px')
625
+ .style('top', '50%')
626
+ .style('transform', 'translateY(-50%)')
627
+ .style('font-size', '10px')
628
+ .style('pointer-events', 'none')
629
+ .text('▼');
630
+ const dropdownListEl = document.createElement('div');
631
+ dropdownListEl.style.cssText = 'display:none;position:fixed;background:' + dropdownBg
632
+ + ';border:1px solid #ccc;border-radius:2px;z-index:9999;box-shadow:0 2px 6px rgba(0,0,0,0.15);';
633
+ document.body.appendChild(dropdownListEl);
634
+ this.dropdownListEl = dropdownListEl;
635
+ this.dropdownTriggerEl = dropdownTrigger.node();
636
+ const dropdownList = d3.select(dropdownListEl);
637
+ this.timePeriods.forEach(tp => {
638
+ dropdownList.append('div')
639
+ .attr('class', 'hc-dropdown-item')
640
+ .style('padding', '4px 8px')
641
+ .style('cursor', 'pointer')
642
+ .style('white-space', 'nowrap')
643
+ .style('font-size', this.displayOption.operationSelectFontSize)
644
+ .style('line-height', (this.displayOption.operationAreaHeight - 8) + 'px')
645
+ .style('min-height', (this.displayOption.operationAreaHeight - 8) + 'px')
646
+ .style('background', tp.key === Number(this.currentTimePeriod) ? '#1890ff' : 'transparent')
647
+ .style('color', tp.key === Number(this.currentTimePeriod) ? '#fff' : '#333')
648
+ .text(tp.name)
649
+ .on('mouseover', function () {
650
+ const el = d3.select(this);
651
+ if (!el.classed('hc-dropdown-selected')) {
652
+ el.style('background', '#e6f7ff').style('color', '#333');
653
+ }
654
+ })
655
+ .on('mouseout', function () {
656
+ const el = d3.select(this);
657
+ if (!el.classed('hc-dropdown-selected')) {
658
+ el.style('background', 'transparent').style('color', '#333');
659
+ }
660
+ })
661
+ .on('click', () => {
662
+ dropdownListEl.style.display = 'none';
663
+ // 从 this.timePeriods 实时查找,确保语种切换后显示最新文案
664
+ const currentPeriod = this.timePeriods.find(p => p.key === tp.key);
665
+ const currentName = currentPeriod ? currentPeriod.name : tp.name;
666
+ dropdownTrigger.text(currentName);
667
+ dropdownTrigger.append('span')
668
+ .style('position', 'absolute')
669
+ .style('right', '4px')
670
+ .style('top', '50%')
671
+ .style('transform', 'translateY(-50%)')
672
+ .style('font-size', '10px')
673
+ .style('pointer-events', 'none')
674
+ .text('▼');
675
+ // 更新选中状态
676
+ dropdownList.selectAll('.hc-dropdown-item')
677
+ .classed('hc-dropdown-selected', false)
678
+ .style('background', 'transparent')
679
+ .style('color', '#333');
680
+ d3.select(d3.event.currentTarget)
681
+ .classed('hc-dropdown-selected', true)
682
+ .style('background', '#1890ff')
683
+ .style('color', '#fff');
684
+ if (tp.key === 8) {
685
+ this.showCustomTimeRangeModal();
686
+ return;
687
+ }
688
+ this.updateTimeRange(tp.key);
689
+ this.reRenderElement(this.startTime, this.endTime, this.displayOption.dataLimit, HistoricalCurveTimeRange.BeginOpenEndOpen);
690
+ });
691
+ });
692
+ // 测量下拉列表最大宽度,让 trigger 和列表宽度一致
693
+ dropdownListEl.style.display = 'block';
694
+ dropdownListEl.style.visibility = 'hidden';
695
+ const listContentWidth = dropdownListEl.offsetWidth;
696
+ dropdownListEl.style.display = 'none';
697
+ dropdownListEl.style.visibility = '';
698
+ dropdownListEl.style.width = listContentWidth + 'px';
699
+ dropdownTrigger.node().style.width = listContentWidth + 'px';
700
+ dropdownTrigger.node().style.boxSizing = 'border-box';
701
+ const hideDropdown = () => { dropdownListEl.style.display = 'none'; };
702
+ dropdownTrigger.on('click', () => {
703
+ const isVisible = dropdownListEl.style.display !== 'none';
704
+ if (isVisible) {
705
+ hideDropdown();
706
+ }
707
+ else {
708
+ const triggerNode = dropdownTrigger.node();
709
+ const triggerRect = triggerNode.getBoundingClientRect();
710
+ // 设置宽度至少和 trigger 一样宽
711
+ dropdownListEl.style.minWidth = triggerRect.width + 'px';
712
+ dropdownListEl.style.left = triggerRect.left + 'px';
713
+ dropdownListEl.style.display = 'block';
714
+ const listHeight = dropdownListEl.offsetHeight;
715
+ const spaceBelow = window.innerHeight - triggerRect.bottom;
716
+ if (spaceBelow < listHeight) {
717
+ // 向上展开
718
+ dropdownListEl.style.top = '';
719
+ dropdownListEl.style.bottom = (window.innerHeight - triggerRect.top) + 'px';
720
+ }
721
+ else {
722
+ // 向下展开
723
+ dropdownListEl.style.bottom = '';
724
+ dropdownListEl.style.top = triggerRect.bottom + 'px';
725
+ }
726
+ }
727
+ });
728
+ // 点击外部收起
729
+ document.addEventListener('click', (event) => {
730
+ const triggerNode = dropdownTrigger.node();
731
+ if (!dropdownListEl.contains(event.target) && !triggerNode.contains(event.target)) {
732
+ hideDropdown();
733
+ }
734
+ });
735
+ // 滚动时收起
736
+ document.addEventListener('scroll', hideDropdown, true);
578
737
  const buttonWidth = this.displayOption.operationButtonWidth + 'px', buttonHeight = this.displayOption.operationButtonHeight + 'px';
579
738
  operationArea.append('button').style('width', buttonWidth).style('height', buttonHeight).style('border', 'none')
580
739
  .style('float', 'right').style('background-image', 'url(assets/img/black_last_page.png)')
@@ -599,6 +758,131 @@ export class HistoricalCurveElement extends ConditionalDisplayElement {
599
758
  timeFormat(datetime, specifier) {
600
759
  return d3.time.format(specifier)(new Date(datetime));
601
760
  }
761
+ fmtDatetimeLocal(d) {
762
+ const p2 = (n) => (n < 10 ? '0' : '') + n;
763
+ return d.getFullYear() + '-' + p2(d.getMonth() + 1) + '-' + p2(d.getDate())
764
+ + 'T' + p2(d.getHours()) + ':' + p2(d.getMinutes()) + ':' + p2(d.getSeconds());
765
+ }
766
+ showCustomTimeRangeModal() {
767
+ const now = new Date();
768
+ const defaultStart = this.customStartTime ? this.customStartTime.toDate()
769
+ : new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
770
+ const defaultEnd = this.customEndTime ? this.customEndTime.toDate()
771
+ : new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59);
772
+ if (this.guiContext && this.guiContext.showCustomTimeRangeModal) {
773
+ this.guiContext.showCustomTimeRangeModal({ startTime: defaultStart, endTime: defaultEnd }).then(result => {
774
+ if (result) {
775
+ const momentAny = moment;
776
+ this.customStartTime = momentAny(result.startTime);
777
+ this.customEndTime = momentAny(result.endTime);
778
+ this.updateTimeRange(8);
779
+ this.reRenderElement(this.startTime, this.endTime, this.displayOption.dataLimit, HistoricalCurveTimeRange.BeginOpenEndOpen);
780
+ }
781
+ });
782
+ return;
783
+ }
784
+ this.showCustomTimeRangeModalFallback();
785
+ }
786
+ showCustomTimeRangeModalFallback() {
787
+ const prevTimePeriod = this.currentTimePeriod;
788
+ const now = new Date();
789
+ const defaultStart = this.fmtDatetimeLocal(this.customStartTime ? this.customStartTime.toDate()
790
+ : new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0));
791
+ const defaultEnd = this.fmtDatetimeLocal(this.customEndTime ? this.customEndTime.toDate()
792
+ : new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59));
793
+ const overlay = document.createElement('div');
794
+ overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:9999;';
795
+ const modal = document.createElement('div');
796
+ const modalCss = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);'
797
+ + 'background:#fff;border-radius:4px;min-width:460px;'
798
+ + 'box-shadow:0 4px 12px rgba(0,0,0,0.15);font-family:inherit;font-size:14px;';
799
+ modal.style.cssText = modalCss;
800
+ const header = document.createElement('div');
801
+ header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid #e8e8e8;';
802
+ const title = document.createElement('span');
803
+ title.style.cssText = 'font-size:16px;font-weight:500;color:#333;';
804
+ title.textContent = this.localization.customTimeRange || '自定义';
805
+ const closeBtn = document.createElement('span');
806
+ closeBtn.style.cssText = 'cursor:pointer;font-size:18px;color:#999;line-height:1;';
807
+ closeBtn.textContent = '×';
808
+ header.appendChild(title);
809
+ header.appendChild(closeBtn);
810
+ const body = document.createElement('div');
811
+ body.style.cssText = 'padding:20px 16px;display:flex;align-items:center;flex-wrap:wrap;gap:8px;';
812
+ const label = document.createElement('span');
813
+ label.style.cssText = 'color:#333;white-space:nowrap;';
814
+ label.textContent = '时间范围:';
815
+ const inputStyle = 'border:1px solid #d9d9d9;border-radius:4px;padding:4px 8px;font-size:14px;height:32px;outline:none;color:#333;';
816
+ const startInput = document.createElement('input');
817
+ startInput.type = 'datetime-local';
818
+ startInput.setAttribute('step', '1');
819
+ startInput.value = defaultStart;
820
+ startInput.style.cssText = inputStyle;
821
+ const separator = document.createElement('span');
822
+ separator.textContent = ' - ';
823
+ separator.style.cssText = 'color:#333;';
824
+ const endInput = document.createElement('input');
825
+ endInput.type = 'datetime-local';
826
+ endInput.setAttribute('step', '1');
827
+ endInput.value = defaultEnd;
828
+ endInput.style.cssText = inputStyle;
829
+ body.appendChild(label);
830
+ body.appendChild(startInput);
831
+ body.appendChild(separator);
832
+ body.appendChild(endInput);
833
+ const footer = document.createElement('div');
834
+ footer.style.cssText = 'display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid #e8e8e8;';
835
+ const cancelBtn = document.createElement('button');
836
+ cancelBtn.textContent = this.localization.cancel || '取消';
837
+ cancelBtn.style.cssText = 'padding:5px 16px;border:1px solid #d9d9d9;border-radius:4px;background:#fff;cursor:pointer;font-size:14px;color:#333;';
838
+ const saveBtn = document.createElement('button');
839
+ saveBtn.textContent = '保存';
840
+ saveBtn.style.cssText = 'padding:5px 16px;border:none;border-radius:4px;background:#1890ff;color:#fff;cursor:pointer;font-size:14px;';
841
+ footer.appendChild(cancelBtn);
842
+ footer.appendChild(saveBtn);
843
+ modal.appendChild(header);
844
+ modal.appendChild(body);
845
+ modal.appendChild(footer);
846
+ overlay.appendChild(modal);
847
+ document.body.appendChild(overlay);
848
+ const close = (revert) => {
849
+ document.body.removeChild(overlay);
850
+ if (revert) {
851
+ const selectEl = this.$element && this.$element.find('select');
852
+ if (selectEl && selectEl.length) {
853
+ selectEl.val(prevTimePeriod.toString());
854
+ }
855
+ }
856
+ };
857
+ closeBtn.addEventListener('click', () => close(true));
858
+ cancelBtn.addEventListener('click', () => close(true));
859
+ saveBtn.addEventListener('click', () => {
860
+ const startVal = startInput.value;
861
+ const endVal = endInput.value;
862
+ if (!startVal) {
863
+ startInput.style.borderColor = 'red';
864
+ return;
865
+ }
866
+ const startDate = new Date(startVal);
867
+ const endDate = endVal
868
+ ? new Date(endVal)
869
+ : new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate(), 23, 59, 59);
870
+ if (isNaN(startDate.getTime())) {
871
+ startInput.style.borderColor = 'red';
872
+ return;
873
+ }
874
+ if (endDate.getTime() <= startDate.getTime()) {
875
+ endInput.style.borderColor = 'red';
876
+ return;
877
+ }
878
+ const momentAny = moment;
879
+ this.customStartTime = momentAny(startDate);
880
+ this.customEndTime = momentAny(endDate);
881
+ close(false);
882
+ this.updateTimeRange(8);
883
+ this.reRenderElement(this.startTime, this.endTime, this.displayOption.dataLimit, HistoricalCurveTimeRange.BeginOpenEndOpen);
884
+ });
885
+ }
602
886
  loadFirstPage() {
603
887
  this.updateQueryTimeRange();
604
888
  this.reRenderElement(this.startTime, this.endTime, this.displayOption.dataLimit, HistoricalCurveTimeRange.BeginOpenEndOpen);
@@ -1 +1 @@
1
- [{"__symbolic":"module","version":4,"metadata":{"HistoricalCurveElement":{"__symbolic":"class","extends":{"__symbolic":"reference","module":"../base/conditional-display-element","name":"ConditionalDisplayElement","line":23,"character":44},"members":{"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"error","message":"Could not resolve type","line":71,"character":25,"context":{"typeName":"HTMLElement"}},{"__symbolic":"reference","module":"@angular/core","name":"Injector","line":72,"character":18},{"__symbolic":"reference","module":"../../service","name":"PermissionChecker","line":73,"character":27},{"__symbolic":"reference","module":"../../communication","name":"VariableCommunicator","line":74,"character":30},{"__symbolic":"reference","module":"../../config","name":"VariableStore","line":75,"character":23},{"__symbolic":"reference","module":"../../config","name":"HistoryDataStore","line":76,"character":43},{"__symbolic":"reference","name":"string"},{"__symbolic":"reference","module":"../../service","name":"SystemTextLibraryService","line":78,"character":52},{"__symbolic":"reference","module":"../../service","name":"LanguageService","line":79,"character":43},{"__symbolic":"reference","module":"../../gui/gui-context","name":"GuiContext","line":80,"character":38}]}],"dispose":[{"__symbolic":"method"}],"initKeyboardListener":[{"__symbolic":"method"}],"subscribeLanguageChange":[{"__symbolic":"method"}],"updateLanguageTexts":[{"__symbolic":"method"}],"getCurrentCulture":[{"__symbolic":"method"}],"getValidTimePeriods":[{"__symbolic":"method"}],"getTimePeriodText":[{"__symbolic":"method"}],"updateTimeRange":[{"__symbolic":"method"}],"updateQueryTimeRange":[{"__symbolic":"method"}],"reRenderElement":[{"__symbolic":"method"}],"renderElement":[{"__symbolic":"method"}],"setupTooltipAutoHide":[{"__symbolic":"method"}],"renderChart":[{"__symbolic":"method"}],"initPoint":[{"__symbolic":"method"}],"getLineChart":[{"__symbolic":"method"}],"getMultiBarWithFocusChart":[{"__symbolic":"method"}],"renderCommonProperty":[{"__symbolic":"method"}],"renderOperationArea":[{"__symbolic":"method"}],"timeFormat":[{"__symbolic":"method"}],"loadFirstPage":[{"__symbolic":"method"}],"loadNextPage":[{"__symbolic":"method"}],"loadPreviousPage":[{"__symbolic":"method"}],"loadLastPage":[{"__symbolic":"method"}],"initElementStatus":[{"__symbolic":"method"}],"updateElementStatus":[{"__symbolic":"method"}],"setStatusAsUnbound":[{"__symbolic":"method"}],"setStatusAsLoading":[{"__symbolic":"method"}],"setStatusAsLoadFailed":[{"__symbolic":"method"}],"renderStatus":[{"__symbolic":"method"}],"clearStatus":[{"__symbolic":"method"}]}}}}]
1
+ [{"__symbolic":"module","version":4,"metadata":{"HistoricalCurveElement":{"__symbolic":"class","extends":{"__symbolic":"reference","module":"../base/conditional-display-element","name":"ConditionalDisplayElement","line":23,"character":44},"members":{"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"error","message":"Could not resolve type","line":75,"character":25,"context":{"typeName":"HTMLElement"}},{"__symbolic":"reference","module":"@angular/core","name":"Injector","line":76,"character":18},{"__symbolic":"reference","module":"../../service","name":"PermissionChecker","line":77,"character":27},{"__symbolic":"reference","module":"../../communication","name":"VariableCommunicator","line":78,"character":30},{"__symbolic":"reference","module":"../../config","name":"VariableStore","line":79,"character":23},{"__symbolic":"reference","module":"../../config","name":"HistoryDataStore","line":80,"character":43},{"__symbolic":"reference","name":"string"},{"__symbolic":"reference","module":"../../service","name":"SystemTextLibraryService","line":82,"character":52},{"__symbolic":"reference","module":"../../service","name":"LanguageService","line":83,"character":43},{"__symbolic":"reference","module":"../../gui/gui-context","name":"GuiContext","line":84,"character":38}]}],"dispose":[{"__symbolic":"method"}],"initKeyboardListener":[{"__symbolic":"method"}],"subscribeLanguageChange":[{"__symbolic":"method"}],"updateLanguageTexts":[{"__symbolic":"method"}],"getCurrentCulture":[{"__symbolic":"method"}],"getValidTimePeriods":[{"__symbolic":"method"}],"getTimePeriodText":[{"__symbolic":"method"}],"updateTimeRange":[{"__symbolic":"method"}],"updateQueryTimeRange":[{"__symbolic":"method"}],"reRenderElement":[{"__symbolic":"method"}],"renderElement":[{"__symbolic":"method"}],"setupTooltipAutoHide":[{"__symbolic":"method"}],"renderChart":[{"__symbolic":"method"}],"initPoint":[{"__symbolic":"method"}],"getLineChart":[{"__symbolic":"method"}],"getMultiBarWithFocusChart":[{"__symbolic":"method"}],"renderCommonProperty":[{"__symbolic":"method"}],"renderOperationArea":[{"__symbolic":"method"}],"timeFormat":[{"__symbolic":"method"}],"fmtDatetimeLocal":[{"__symbolic":"method"}],"showCustomTimeRangeModal":[{"__symbolic":"method"}],"showCustomTimeRangeModalFallback":[{"__symbolic":"method"}],"loadFirstPage":[{"__symbolic":"method"}],"loadNextPage":[{"__symbolic":"method"}],"loadPreviousPage":[{"__symbolic":"method"}],"loadLastPage":[{"__symbolic":"method"}],"initElementStatus":[{"__symbolic":"method"}],"updateElementStatus":[{"__symbolic":"method"}],"setStatusAsUnbound":[{"__symbolic":"method"}],"setStatusAsLoading":[{"__symbolic":"method"}],"setStatusAsLoadFailed":[{"__symbolic":"method"}],"renderStatus":[{"__symbolic":"method"}],"clearStatus":[{"__symbolic":"method"}]}}}}]
@@ -2,7 +2,7 @@ import { LOGGER_SERVICE_TOKEN } from '../../logger';
2
2
  import * as d3 from 'd3-selection';
3
3
  import { isUndefined } from 'lodash';
4
4
  import { IndicatorLightType, SwitchType, State, BitSwitchOperation } from '../../model';
5
- import { VariableState } from '../../communication';
5
+ import { VariableState, VariableStateEnum } from '../../communication';
6
6
  import { ConditionalEnableElement } from '../base/conditional-enable-element';
7
7
  import { GraphStateElement } from '../shared/graph/graph-state-element';
8
8
  import { TextStateElement } from '../shared/text/text-state-element';
@@ -195,11 +195,18 @@ export class SwitchIndicatorLightElement extends ConditionalEnableElement {
195
195
  }
196
196
  }
197
197
  initIndictorLightOperator() {
198
+ var _a, _b, _c, _d;
198
199
  const settings = this.model.indicatorLightSettings;
199
200
  if (settings.settings.variableName) {
200
201
  const variable = new VariableDefinition(settings.settings.variableName, settings.settings.variableGroupName, settings.settings.dataSourceCode, settings.settings.variableVersion);
201
- this.addElementState(new VariableState(VariableUtil.getConvertedVariableName(this.variableStore, variable), undefined));
202
+ const convertedName = VariableUtil.getConvertedVariableName(this.variableStore, variable);
203
+ this.addElementState(new VariableState(convertedName, undefined));
202
204
  this.initState();
205
+ const validNames = (_d = (_c = (_b = (_a = this.guiContext) === null || _a === void 0 ? void 0 : _a.configStore) === null || _b === void 0 ? void 0 : _b.variableStore) === null || _c === void 0 ? void 0 : _c.getAllVariableNames) === null || _d === void 0 ? void 0 : _d.call(_c);
206
+ if (validNames && !validNames.includes(convertedName)) {
207
+ this.updateElementStates([new VariableState(convertedName, VariableStateEnum.InvalidMonitor)]);
208
+ return;
209
+ }
203
210
  }
204
211
  switch (settings.type) {
205
212
  case IndicatorLightType.Bit:
@@ -22,5 +22,12 @@ export interface GuiContext {
22
22
  getDefaultLanguageId?(): number;
23
23
  getLanguageCultureById?(languageId: number | null): string | null;
24
24
  updateCurrentLanguageId?(languageId: number | null): Promise<void>;
25
+ showCustomTimeRangeModal?(config: {
26
+ startTime?: Date;
27
+ endTime?: Date;
28
+ }): Promise<{
29
+ startTime: Date;
30
+ endTime: Date;
31
+ } | null>;
25
32
  dispose(): void;
26
33
  }
package/gui/gui-view.js CHANGED
@@ -2,7 +2,7 @@ import { LOGGER_SERVICE_TOKEN } from '../logger';
2
2
  import * as d3 from 'd3-selection';
3
3
  import { each, forEach } from 'lodash';
4
4
  import { map } from 'rxjs/operators';
5
- import { VariableStateEnum } from '../communication';
5
+ import { VariableStateEnum, VariableState } from '../communication';
6
6
  import { MainElement } from '../elements/main-element';
7
7
  import { PerViewVariableCommunicator } from '../elements/per-view-variable-communicator';
8
8
  import { ViewPopupBackdropType, ViewPopupLocationType } from '../model';
@@ -64,6 +64,7 @@ export class GuiView {
64
64
  this.logger.debug('[GUI] View loaded.');
65
65
  }
66
66
  loadElementState() {
67
+ var _a, _b;
67
68
  if (!this.mainElement) {
68
69
  return;
69
70
  }
@@ -108,6 +109,20 @@ export class GuiView {
108
109
  });
109
110
  normalVariablesForState.splice(normalVariablesForState.indexOf('设备状态'), 1);
110
111
  }
112
+ const validVariableNames = (_b = (_a = this.context.configStore.variableStore).getAllVariableNames) === null || _b === void 0 ? void 0 : _b.call(_a);
113
+ if (validVariableNames) {
114
+ const invalidVariables = normalVariablesForState.filter(name => !validVariableNames.includes(name));
115
+ if (invalidVariables.length > 0) {
116
+ const unbindStates = invalidVariables.map(name => new VariableState(name, VariableStateEnum.InvalidMonitor));
117
+ this.mainElement.reportVariableStates(unbindStates);
118
+ invalidVariables.forEach(name => {
119
+ const idx = normalVariablesForState.indexOf(name);
120
+ if (idx !== -1) {
121
+ normalVariablesForState.splice(idx, 1);
122
+ }
123
+ });
124
+ }
125
+ }
111
126
  if (normalVariablesForState.length === 0) {
112
127
  return;
113
128
  }
@@ -29,6 +29,7 @@ export interface Localization {
29
29
  offline: any;
30
30
  abnormal: any;
31
31
  disable: any;
32
+ invalidMonitor: any;
32
33
  permissiontip: any;
33
34
  conditionIsNotMetTip: any;
34
35
  chartNoData: any;
@@ -39,6 +40,9 @@ export interface Localization {
39
40
  lastSevenDays: any;
40
41
  lastThirtyDays: any;
41
42
  lastOneYear: any;
43
+ customTimeRange: any;
44
+ startTime: any;
45
+ endTime: any;
42
46
  grouped: any;
43
47
  stacked: any;
44
48
  passwordVerify: any;
@@ -29,6 +29,7 @@ export const DefaultLocalization = {
29
29
  offline: 'Offline',
30
30
  abnormal: 'Data abnormal',
31
31
  disable: 'Disable',
32
+ invalidMonitor: 'Element binding monitor point is invalid',
32
33
  permissiontip: 'You have no permission to operate.',
33
34
  conditionIsNotMetTip: 'Operation conditions not met or variable anomalies.',
34
35
  chartNoData: 'No Data Available',
@@ -39,6 +40,9 @@ export const DefaultLocalization = {
39
40
  lastSevenDays: 'Last 7 days',
40
41
  lastThirtyDays: 'Last 30 days',
41
42
  lastOneYear: 'Last 1 year',
43
+ customTimeRange: 'Custom',
44
+ startTime: 'Start Time',
45
+ endTime: 'End Time',
42
46
  grouped: 'Grouped',
43
47
  stacked: 'Stacked',
44
48
  passwordVerify: 'Password verifiers',