@flexem/fc-gui 3.0.0-alpha.18 → 3.0.0-alpha.180

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 (185) hide show
  1. package/CHANGELOG.md +419 -1
  2. package/assets/img/black_first_page.png +0 -0
  3. package/assets/img/black_last_page.png +0 -0
  4. package/assets/img/black_next_page.png +0 -0
  5. package/assets/img/black_previous_page.png +0 -0
  6. package/assets/img/exclamation-outline-circle.png +0 -0
  7. package/bundles/@flexem/fc-gui.umd.js +39377 -34650
  8. package/bundles/@flexem/fc-gui.umd.js.map +1 -1
  9. package/bundles/@flexem/fc-gui.umd.min.js +5 -5
  10. package/bundles/@flexem/fc-gui.umd.min.js.map +1 -1
  11. package/communication/variable/variable-communicator.d.ts +5 -0
  12. package/communication/variable/variable-state-enum.d.ts +1 -0
  13. package/communication/variable/variable-state-enum.js +1 -0
  14. package/communication/variable/variable-state-enum.metadata.json +1 -1
  15. package/communication/variable/variable-value.d.ts +4 -1
  16. package/communication/variable/variable-value.js +4 -1
  17. package/communication/variable/variable-value.metadata.json +1 -1
  18. package/config/alarm/alarm.store.d.ts +6 -0
  19. package/config/alarm/alarm.store.js +0 -0
  20. package/config/alarm/alarm.store.metadata.json +1 -0
  21. package/config/alarm/get-alarms-args.d.ts +12 -0
  22. package/config/alarm/get-alarms-args.js +13 -0
  23. package/config/alarm/get-alarms-args.metadata.json +1 -0
  24. package/config/alarm/index.d.ts +2 -0
  25. package/config/alarm/index.js +1 -0
  26. package/config/alarm/index.metadata.json +1 -0
  27. package/config/config-store.d.ts +2 -0
  28. package/config/gui-feature-config.d.ts +3 -0
  29. package/config/gui-feature-config.js +3 -0
  30. package/config/gui-feature-config.metadata.json +1 -0
  31. package/config/index.d.ts +2 -0
  32. package/config/index.js +2 -0
  33. package/config/index.metadata.json +1 -1
  34. package/config/variable/variable-store.d.ts +1 -0
  35. package/elements/air-quality/air-quality-element.js +3 -4
  36. package/elements/alarm/alarm-element.d.ts +69 -0
  37. package/elements/alarm/alarm-element.js +497 -0
  38. package/elements/alarm/alarm-element.metadata.json +1 -0
  39. package/elements/bar-graph-element.d.ts +10 -2
  40. package/elements/bar-graph-element.js +135 -5
  41. package/elements/bar-graph-element.metadata.json +1 -1
  42. package/elements/base/readable-element.d.ts +6 -1
  43. package/elements/base/readable-element.js +69 -2
  44. package/elements/base/readable-element.metadata.json +1 -1
  45. package/elements/base/state-control-element.d.ts +3 -1
  46. package/elements/base/state-control-element.js +21 -5
  47. package/elements/character-display/character-display-element.js +3 -2
  48. package/elements/character-display/character-display-element.metadata.json +1 -1
  49. package/elements/datetime-display/datetime-display-element.d.ts +1 -0
  50. package/elements/datetime-display/datetime-display-element.js +10 -2
  51. package/elements/datetime-display/datetime-display-element.metadata.json +1 -1
  52. package/elements/datetime-display/time-zone-select-json.d.ts +8 -0
  53. package/elements/datetime-display/time-zone-select-json.js +558 -0
  54. package/elements/historical-curve/historical-curve.element.d.ts +44 -3
  55. package/elements/historical-curve/historical-curve.element.js +696 -32
  56. package/elements/historical-curve/historical-curve.element.metadata.json +1 -1
  57. package/elements/main-element.d.ts +1 -0
  58. package/elements/main-element.js +70 -9
  59. package/elements/main-element.metadata.json +1 -1
  60. package/elements/meter-element.d.ts +7 -1
  61. package/elements/meter-element.js +76 -7
  62. package/elements/meter-element.metadata.json +1 -1
  63. package/elements/numerical-display/numerical-display-element.d.ts +22 -3
  64. package/elements/numerical-display/numerical-display-element.js +145 -12
  65. package/elements/numerical-display/numerical-display-element.metadata.json +1 -1
  66. package/elements/per-view-variable-communicator.d.ts +4 -0
  67. package/elements/per-view-variable-communicator.js +21 -1
  68. package/elements/per-view-variable-communicator.metadata.json +1 -1
  69. package/elements/ring-graph/ring-graph-element.d.ts +13 -1
  70. package/elements/ring-graph/ring-graph-element.js +164 -3
  71. package/elements/ring-graph/ring-graph-element.metadata.json +1 -1
  72. package/elements/scroll-alarm/scroll-alarm-element.d.ts +79 -0
  73. package/elements/scroll-alarm/scroll-alarm-element.js +845 -0
  74. package/elements/scroll-alarm/scroll-alarm-element.metadata.json +1 -0
  75. package/elements/shared/graph/graph-state-element.d.ts +1 -0
  76. package/elements/shared/graph/graph-state-element.js +30 -1
  77. package/elements/shared/graph/graph-state-element.metadata.json +1 -1
  78. package/elements/shared/text/text-element.d.ts +14 -0
  79. package/elements/shared/text/text-element.js +42 -4
  80. package/elements/shared/text/text-element.metadata.json +1 -1
  81. package/elements/shared/text/text-state-element.d.ts +39 -2
  82. package/elements/shared/text/text-state-element.js +242 -67
  83. package/elements/shared/text/text-state-element.metadata.json +1 -1
  84. package/elements/static-elements/hyperlink-element.d.ts +28 -2
  85. package/elements/static-elements/hyperlink-element.js +153 -4
  86. package/elements/static-elements/hyperlink-element.metadata.json +1 -1
  87. package/elements/static-elements/text-element.d.ts +28 -2
  88. package/elements/static-elements/text-element.js +162 -12
  89. package/elements/static-elements/text-element.metadata.json +1 -1
  90. package/elements/switch-indicator-light/bit-indicator-light-operator.d.ts +1 -0
  91. package/elements/switch-indicator-light/bit-indicator-light-operator.js +10 -3
  92. package/elements/switch-indicator-light/bit-indicator-light-operator.metadata.json +1 -1
  93. package/elements/switch-indicator-light/switch-indicator-light-element.d.ts +11 -2
  94. package/elements/switch-indicator-light/switch-indicator-light-element.js +70 -12
  95. package/elements/switch-indicator-light/switch-indicator-light-element.metadata.json +1 -1
  96. package/elements/switch-indicator-light/word-indicator-light-operator.d.ts +1 -0
  97. package/elements/switch-indicator-light/word-indicator-light-operator.js +10 -3
  98. package/elements/switch-indicator-light/word-indicator-light-operator.metadata.json +1 -1
  99. package/elements/video/video-element.d.ts +10 -0
  100. package/elements/video/video-element.js +139 -21
  101. package/elements/video/video-element.metadata.json +1 -1
  102. package/elements/view-operation/view-operation.element.d.ts +28 -2
  103. package/elements/view-operation/view-operation.element.js +163 -4
  104. package/elements/view-operation/view-operation.element.metadata.json +1 -1
  105. package/elements/weather/weater-element.js +0 -1
  106. package/gui/gui-context.d.ts +25 -2
  107. package/gui/gui-host.d.ts +1 -1
  108. package/gui/gui-view.d.ts +4 -1
  109. package/gui/gui-view.js +69 -8
  110. package/gui/gui-view.metadata.json +1 -1
  111. package/gui/gui.component.d.ts +3 -0
  112. package/gui/gui.component.js +15 -2
  113. package/gui/gui.component.metadata.json +1 -1
  114. package/localization/localization.service.d.ts +11 -0
  115. package/localization/localization.service.js +16 -5
  116. package/localization/localization.service.metadata.json +1 -1
  117. package/localization/localization.service.zh_CN.js +12 -1
  118. package/localization/localization.service.zh_CN.metadata.json +1 -1
  119. package/modal/write-character/write-character-modal.component.d.ts +3 -2
  120. package/modal/write-character/write-character-modal.component.html +4 -4
  121. package/modal/write-character/write-character-modal.component.js +7 -5
  122. package/modal/write-character/write-character-modal.component.less +15 -0
  123. package/modal/write-character/write-character-modal.component.metadata.json +1 -1
  124. package/modal/write-value/write-value-modal-args.d.ts +5 -1
  125. package/modal/write-value/write-value-modal-args.js +3 -1
  126. package/modal/write-value/write-value-modal-args.metadata.json +1 -1
  127. package/modal/write-value/write-value-modal.component.d.ts +15 -8
  128. package/modal/write-value/write-value-modal.component.html +10 -5
  129. package/modal/write-value/write-value-modal.component.js +99 -21
  130. package/modal/write-value/write-value-modal.component.less +16 -1
  131. package/modal/write-value/write-value-modal.component.metadata.json +1 -1
  132. package/model/alarm/alarm.model.d.ts +13 -0
  133. package/model/alarm/alarm.model.js +0 -0
  134. package/model/alarm/alarm.model.metadata.json +1 -0
  135. package/model/bar-graph/bar-graph.d.ts +4 -0
  136. package/model/base/font-setting-model.d.ts +22 -1
  137. package/model/base/font-setting-model.metadata.json +1 -1
  138. package/model/base/readable-model.d.ts +4 -0
  139. package/model/datetime-display/datetime-display.d.ts +1 -0
  140. package/model/historical-curve/historical-curve-axis-settings.d.ts +11 -0
  141. package/model/historical-curve/historical-curve-axis-settings.js +5 -0
  142. package/model/historical-curve/historical-curve-axis-settings.metadata.json +1 -1
  143. package/model/historical-curve/historical-curve-chanel.model.d.ts +8 -0
  144. package/model/meter/meter.d.ts +4 -0
  145. package/model/numerical-display/numerical-operation.d.ts +10 -2
  146. package/model/ring-graph/ring-graph.model.d.ts +8 -0
  147. package/model/scroll-alarm/scroll-alarm.model.d.ts +21 -0
  148. package/model/scroll-alarm/scroll-alarm.model.js +0 -0
  149. package/model/scroll-alarm/scroll-alarm.model.metadata.json +1 -0
  150. package/model/shared/state/state.d.ts +1 -0
  151. package/model/shared/state/state.js +1 -0
  152. package/model/shared/state/state.metadata.json +1 -1
  153. package/model/shared/text/font.d.ts +1 -1
  154. package/model/shared/text/text.d.ts +3 -0
  155. package/model/switch-indicator-light/switch-indicator-light.d.ts +2 -0
  156. package/model/view-operation/view-operation-element.model.d.ts +7 -1
  157. package/package.json +1 -1
  158. package/public_api.js +1 -0
  159. package/remote/communication/variable/remote-variable-communicator.d.ts +30 -0
  160. package/remote/communication/variable/remote-variable-communicator.js +193 -3
  161. package/remote/communication/variable/remote-variable-communicator.metadata.json +1 -1
  162. package/remote/communication/variable/remote-variable-protocol.d.ts +8 -0
  163. package/service/index.d.ts +4 -0
  164. package/service/index.js +1 -0
  165. package/service/index.metadata.json +1 -1
  166. package/service/language.service.d.ts +37 -0
  167. package/service/language.service.js +0 -0
  168. package/service/language.service.metadata.json +1 -0
  169. package/service/released-variable/index.d.ts +1 -0
  170. package/service/released-variable/index.js +0 -0
  171. package/service/released-variable/index.metadata.json +1 -0
  172. package/service/released-variable/released-variable.service.d.ts +4 -0
  173. package/service/released-variable/released-variable.service.js +0 -0
  174. package/service/released-variable/released-variable.service.metadata.json +1 -0
  175. package/service/system-text-library.service.d.ts +77 -0
  176. package/service/system-text-library.service.js +29 -0
  177. package/service/system-text-library.service.metadata.json +1 -0
  178. package/service/text-library.service.d.ts +49 -0
  179. package/service/text-library.service.js +0 -0
  180. package/service/text-library.service.metadata.json +1 -0
  181. package/shared/gui-consts.d.ts +2 -0
  182. package/shared/gui-consts.js +3 -1
  183. package/shared/gui-consts.metadata.json +1 -1
  184. package/utils/data-type/fbox-data-type.service.js +40 -0
  185. package/utils/numerical-operation.service.js +28 -4
@@ -5,15 +5,21 @@ import * as nv from 'nvd3';
5
5
  import { GetHistoryDataArgs } from '../../config';
6
6
  import { HistoricalCurveTimeRange } from '../../config/history-data/historical-curve.time-range';
7
7
  import { LOCALIZATION } from '../../localization';
8
+ import { SYSTEM_TEXT_LIBRARY_TYPES, TIME_PERIOD_KEYS } from '../../service';
8
9
  import { ConditionalDisplayElement } from '../base/conditional-display-element';
9
10
  import { HistoricalCurveElementStatus } from './historical-curve-element-status';
10
11
  import { LOGGER_SERVICE_TOKEN } from '../../logger';
11
12
  import { GlobalSettings, DisplayMode } from '../../settings';
12
13
  import { CurveType } from '../../model/historical-curve/curve-type';
14
+ import { AxisRangeType } from '../../model/historical-curve/historical-curve-axis-settings';
13
15
  export class HistoricalCurveElement extends ConditionalDisplayElement {
14
- constructor(element, injector, permissionChecker, variableCommunicator, variableStore, historyDataStore, signalRAppId) {
16
+ constructor(element, injector, permissionChecker, variableCommunicator, variableStore, historyDataStore, signalRAppId, systemTextLibraryService, languageService, guiContext, releasedVariableService) {
15
17
  super(element, permissionChecker, variableCommunicator, variableStore, signalRAppId);
16
18
  this.historyDataStore = historyDataStore;
19
+ this.systemTextLibraryService = systemTextLibraryService;
20
+ this.languageService = languageService;
21
+ this.guiContext = guiContext;
22
+ this.releasedVariableService = releasedVariableService;
17
23
  this.displayOption = {
18
24
  dataLimit: 500,
19
25
  dataZoomHeight: 32,
@@ -21,16 +27,28 @@ export class HistoricalCurveElement extends ConditionalDisplayElement {
21
27
  marginRight: 20,
22
28
  mobileMinWidth: 450,
23
29
  operationAreaHeight: 32,
24
- operationAreaMarginTop: 10,
30
+ operationAreaMarginTop: 25,
25
31
  operationSelectFontSize: '16px',
26
32
  operationButtonWidth: 24,
27
33
  operationButtonHeight: 24,
28
34
  operationButtonMargin: 4
29
35
  };
36
+ this.customStartTime = null;
37
+ this.customEndTime = null;
30
38
  this.elementStatus = HistoricalCurveElementStatus.Loading;
39
+ this.data = [];
40
+ this.needResize = true;
41
+ this.dropdownListEl = null;
42
+ this.dropdownTriggerEl = null;
43
+ this._outsideClickHandler = null;
44
+ this._fullscreenChangeHandler = null;
45
+ this._resizeHideHandler = null;
46
+ this.setNeedResize = () => {
47
+ this.needResize = false;
48
+ setTimeout(() => this.needResize = true, 500);
49
+ };
31
50
  this.logger = injector.get(LOGGER_SERVICE_TOKEN);
32
51
  this.localization = injector.get(LOCALIZATION);
33
- this.timePeriods = this.getValidTimePeriods();
34
52
  this.updateTimeRange(this.model.displaySetting.displayTimePeriod);
35
53
  this.refreshIntervalId = setInterval(() => {
36
54
  this.loadFirstPage();
@@ -38,7 +56,7 @@ export class HistoricalCurveElement extends ConditionalDisplayElement {
38
56
  }, this.model.displaySetting.refreshInterval * 1000);
39
57
  this.isMobileMode = DisplayMode.Mobile === injector.get(GlobalSettings).displayMode;
40
58
  if (this.isMobileMode) {
41
- this.displayOption.operationAreaMarginTop = 20;
59
+ this.displayOption.operationAreaMarginTop = 35;
42
60
  if (this.model.displaySetting.size.width >= this.displayOption.mobileMinWidth) {
43
61
  this.displayOption.operationAreaHeight = 68;
44
62
  this.displayOption.operationSelectFontSize = '24px';
@@ -47,29 +65,186 @@ export class HistoricalCurveElement extends ConditionalDisplayElement {
47
65
  this.displayOption.operationButtonMargin = 6;
48
66
  }
49
67
  }
68
+ // 初始化时间段数据(此时文本库可能未加载,先填充空文本占位)
69
+ this.timePeriods = this.getValidTimePeriods();
50
70
  this.loadFirstPage();
71
+ this.initKeyboardListener();
72
+ // 订阅语种变化事件 和 文本库缓存更新事件
73
+ this.subscribeLanguageChange();
51
74
  }
52
75
  dispose() {
53
76
  clearInterval(this.refreshIntervalId);
54
77
  if (this.chartElement) {
55
78
  this.chartElement.tooltip.hidden(true);
56
79
  }
80
+ if (this.resizeEventListener) {
81
+ this.resizeEventListener.clear();
82
+ }
83
+ if (this.isAndroid) {
84
+ window.removeEventListener('native.keyboardshow', this.setNeedResize);
85
+ window.removeEventListener('native.keyboardhide', this.setNeedResize);
86
+ }
87
+ // 清理下拉相关事件监听及 DOM
88
+ if (this._outsideClickHandler) {
89
+ document.removeEventListener('click', this._outsideClickHandler);
90
+ this._outsideClickHandler = null;
91
+ }
92
+ if (this._fullscreenChangeHandler) {
93
+ document.removeEventListener('fullscreenchange', this._fullscreenChangeHandler);
94
+ document.removeEventListener('webkitfullscreenchange', this._fullscreenChangeHandler);
95
+ this._fullscreenChangeHandler = null;
96
+ }
97
+ if (this._resizeHideHandler) {
98
+ window.removeEventListener('resize', this._resizeHideHandler);
99
+ this._resizeHideHandler = null;
100
+ }
101
+ if (this.dropdownListEl && this.dropdownListEl.parentNode) {
102
+ this.dropdownListEl.parentNode.removeChild(this.dropdownListEl);
103
+ this.dropdownListEl = null;
104
+ }
105
+ // 取消语种变化订阅
106
+ if (this.languageChangeSubscription) {
107
+ this.languageChangeSubscription.unsubscribe();
108
+ this.languageChangeSubscription = undefined;
109
+ }
57
110
  this.logger.debug(`[GUI]Dispose Histoical Curve Refresh Interval:${d3.time.format('%x %X')(new Date())}`);
58
111
  }
112
+ initKeyboardListener() {
113
+ this.isAndroid = !!navigator.userAgent.match(/(Android)/i);
114
+ if (this.isAndroid) {
115
+ window.addEventListener('native.keyboardshow', this.setNeedResize);
116
+ window.addEventListener('native.keyboardhide', this.setNeedResize);
117
+ }
118
+ }
119
+ /**
120
+ * 订阅语种变化事件
121
+ */
122
+ subscribeLanguageChange() {
123
+ var _a;
124
+ if (this.guiContext && this.guiContext.languageChanged$) {
125
+ this.languageChangeSubscription = this.guiContext.languageChanged$.subscribe(() => {
126
+ // 只更新时间段选择器的文案,不重新查询数据
127
+ this.updateLanguageTexts();
128
+ });
129
+ }
130
+ // 订阅文本库缓存更新事件,解决首次加载时文本库未就绪导致下拉选项为空的问题
131
+ if ((_a = this.systemTextLibraryService) === null || _a === void 0 ? void 0 : _a.cacheUpdated) {
132
+ const cacheSubscription = this.systemTextLibraryService.cacheUpdated.subscribe(() => {
133
+ this.updateLanguageTexts();
134
+ });
135
+ if (this.languageChangeSubscription) {
136
+ this.languageChangeSubscription.add(cacheSubscription);
137
+ }
138
+ else {
139
+ this.languageChangeSubscription = cacheSubscription;
140
+ }
141
+ }
142
+ }
143
+ /**
144
+ * 更新语种相关的文案(时间段选择器)
145
+ */
146
+ updateLanguageTexts() {
147
+ const updatedTimePeriods = this.getValidTimePeriods();
148
+ this.timePeriods = updatedTimePeriods;
149
+ // 更新下拉列表各选项文字
150
+ if (this.dropdownListEl) {
151
+ const items = this.dropdownListEl.querySelectorAll('.hc-dropdown-item');
152
+ items.forEach((item, i) => {
153
+ if (i < updatedTimePeriods.length) {
154
+ item.textContent = updatedTimePeriods[i].name;
155
+ }
156
+ });
157
+ // 文字更新后重新测量宽度,修正首次加载时文字为空导致宽度过小的问题
158
+ const prevDisplay = this.dropdownListEl.style.display;
159
+ const prevVisibility = this.dropdownListEl.style.visibility;
160
+ this.dropdownListEl.style.display = 'block';
161
+ this.dropdownListEl.style.visibility = 'hidden';
162
+ this.dropdownListEl.style.width = '';
163
+ const newWidth = this.dropdownListEl.offsetWidth;
164
+ this.dropdownListEl.style.display = prevDisplay;
165
+ this.dropdownListEl.style.visibility = prevVisibility;
166
+ this.dropdownListEl.style.width = newWidth + 'px';
167
+ if (this.dropdownTriggerEl) {
168
+ this.dropdownTriggerEl.style.width = newWidth + 'px';
169
+ }
170
+ }
171
+ // 更新触发按钮文字(显示当前选中项的最新语言文案)
172
+ if (this.dropdownTriggerEl) {
173
+ const selected = updatedTimePeriods.find(p => p.key === Number(this.currentTimePeriod));
174
+ if (selected) {
175
+ const textNode = Array.from(this.dropdownTriggerEl.childNodes).find(n => n.nodeType === Node.TEXT_NODE);
176
+ if (textNode) {
177
+ textNode.textContent = selected.name;
178
+ }
179
+ else {
180
+ this.dropdownTriggerEl.insertBefore(document.createTextNode(selected.name), this.dropdownTriggerEl.firstChild);
181
+ }
182
+ }
183
+ }
184
+ }
185
+ /**
186
+ * 获取当前语种的 culture 代码
187
+ */
188
+ getCurrentCulture() {
189
+ var _a, _b, _c, _d, _e, _f;
190
+ // 获取当前语种ID
191
+ const currentLanguageId = (_c = (_b = (_a = this.guiContext) === null || _a === void 0 ? void 0 : _a.getCurrentLanguageId) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : null;
192
+ // 确定要使用的语种代码(culture)
193
+ const defaultLanguage = ((_d = this.languageService) === null || _d === void 0 ? void 0 : _d.getDefaultLanguage()) || 'zh-CN';
194
+ if (currentLanguageId === null || currentLanguageId === undefined) {
195
+ // 设备未设置当前语种,使用默认语种
196
+ return defaultLanguage;
197
+ }
198
+ else {
199
+ // 设备已设置当前语种,获取对应的语种代码
200
+ const currentLanguage = (_f = (_e = this.guiContext) === null || _e === void 0 ? void 0 : _e.getLanguageCultureById) === null || _f === void 0 ? void 0 : _f.call(_e, currentLanguageId);
201
+ if (currentLanguage) {
202
+ return currentLanguage;
203
+ }
204
+ else {
205
+ // 无法获取语种代码,使用默认语种
206
+ return defaultLanguage;
207
+ }
208
+ }
209
+ }
59
210
  getValidTimePeriods() {
60
211
  const timePeriods = new Array();
61
- timePeriods.push({ key: 1, name: this.localization.lastOneHour });
62
- timePeriods.push({ key: 2, name: this.localization.lastTwentyFourHours });
63
- timePeriods.push({ key: 3, name: this.localization.lastSevenDays });
64
- timePeriods.push({ key: 4, name: this.localization.lastThirtyDays });
65
- timePeriods.push({ key: 5, name: this.localization.lastOneYear });
212
+ timePeriods.push({ key: 6, name: this.getTimePeriodText(TIME_PERIOD_KEYS.LAST_THIRTY_MINUTES) });
213
+ timePeriods.push({ key: 1, name: this.getTimePeriodText(TIME_PERIOD_KEYS.LAST_ONE_HOUR) });
214
+ timePeriods.push({ key: 7, name: this.getTimePeriodText(TIME_PERIOD_KEYS.LAST_EIGHT_HOURS) });
215
+ timePeriods.push({ key: 2, name: this.getTimePeriodText(TIME_PERIOD_KEYS.LAST_TWENTY_FOUR_HOURS) });
216
+ timePeriods.push({ key: 3, name: this.getTimePeriodText(TIME_PERIOD_KEYS.LAST_SEVEN_DAYS) });
217
+ timePeriods.push({ key: 4, name: this.getTimePeriodText(TIME_PERIOD_KEYS.LAST_THIRTY_DAYS) });
218
+ timePeriods.push({ key: 5, name: this.getTimePeriodText(TIME_PERIOD_KEYS.LAST_ONE_YEAR) });
219
+ timePeriods.push({ key: 8, name: this.getTimePeriodText(TIME_PERIOD_KEYS.CUSTOM) });
66
220
  return timePeriods;
67
221
  }
222
+ /**
223
+ * 获取时间段文案(从系统文本库获取多语种翻译)
224
+ */
225
+ getTimePeriodText(textKey) {
226
+ const currentCulture = this.getCurrentCulture();
227
+ const systemType = SYSTEM_TEXT_LIBRARY_TYPES.COMPONENT_BUILTIN;
228
+ if (this.systemTextLibraryService) {
229
+ const translation = this.systemTextLibraryService.getSystemTextValue(systemType, textKey, currentCulture);
230
+ if (translation) {
231
+ return translation;
232
+ }
233
+ }
234
+ return '';
235
+ }
68
236
  updateTimeRange(timePeriodType) {
69
237
  this.currentTimePeriod = +timePeriodType;
70
238
  this.updateQueryTimeRange();
71
239
  }
72
240
  updateQueryTimeRange() {
241
+ if (this.currentTimePeriod === 8) {
242
+ if (this.customStartTime && this.customEndTime) {
243
+ this.startTime = this.customStartTime.clone();
244
+ this.endTime = this.customEndTime.clone();
245
+ }
246
+ return;
247
+ }
73
248
  this.endTime = moment();
74
249
  switch (this.currentTimePeriod) {
75
250
  case 1:
@@ -84,13 +259,29 @@ export class HistoricalCurveElement extends ConditionalDisplayElement {
84
259
  case 5:
85
260
  this.startTime = moment().subtract(1, 'years');
86
261
  break;
262
+ case 6:
263
+ this.startTime = moment().subtract(30, 'minutes');
264
+ break;
265
+ case 7:
266
+ this.startTime = moment().subtract(8, 'hours');
267
+ break;
87
268
  default:
88
269
  this.startTime = moment().subtract(1, 'days');
89
270
  }
90
271
  }
91
272
  reRenderElement(startTime, endTime, limit, rangeType) {
273
+ // 清理图表实例
274
+ if (this.chartElement) {
275
+ // 隐藏 tooltip
276
+ if (this.chartElement.tooltip) {
277
+ this.chartElement.tooltip.hidden(true);
278
+ }
279
+ // 清理图表引用
280
+ this.chartElement = null;
281
+ }
282
+ // 清除 DOM 元素
92
283
  this.rootElement.selectAll('*').remove();
93
- this.chartElement = null;
284
+ // 重新渲染
94
285
  this.renderElement(startTime, endTime, limit, rangeType);
95
286
  }
96
287
  renderElement(startTime, endTime, limit, rangeType) {
@@ -122,6 +313,28 @@ export class HistoricalCurveElement extends ConditionalDisplayElement {
122
313
  }
123
314
  });
124
315
  }
316
+ setupTooltipAutoHide(chart) {
317
+ const chartContainer = this.rootElement.select('.nv-focus').node();
318
+ if (!chartContainer || !chart)
319
+ return;
320
+ let timeoutId;
321
+ // 鼠标移入图表时显示 tooltip
322
+ chartContainer.addEventListener('mouseover', () => {
323
+ hideTooltipAfterDelay();
324
+ });
325
+ const clearTooltipTimeout = () => {
326
+ if (timeoutId) {
327
+ clearTimeout(timeoutId);
328
+ timeoutId = null;
329
+ }
330
+ };
331
+ const hideTooltipAfterDelay = () => {
332
+ clearTooltipTimeout();
333
+ timeoutId = setTimeout(() => {
334
+ chart.tooltip.hidden(true);
335
+ }, 2000); // 2秒延迟
336
+ };
337
+ }
125
338
  renderChart(result) {
126
339
  const chartWidth = this.model.displaySetting.size.width;
127
340
  const chartHeight = this.model.displaySetting.size.height - this.displayOption.operationAreaHeight - this.displayOption.operationAreaMarginTop;
@@ -131,19 +344,69 @@ export class HistoricalCurveElement extends ConditionalDisplayElement {
131
344
  each(result, v => values.push({ x: moment(v.time).local().toDate().valueOf(), y: v.values[key] }));
132
345
  data.push({ key: channel.name, area: channel.projectEnabled, values: values });
133
346
  });
134
- nv.addGraph(() => {
135
- if (this.model.displaySetting.curveType === CurveType.BarGroup || this.model.displaySetting.curveType === CurveType.BarStack) {
136
- return this.getMultiBarWithFocusChart(chartWidth, chartHeight, data);
137
- }
138
- else {
139
- return this.getLineChart(chartWidth, chartHeight, data);
347
+ this.data = data;
348
+ let chart;
349
+ if (this.model.displaySetting.curveType === CurveType.BarGroup || this.model.displaySetting.curveType === CurveType.BarStack) {
350
+ chart = this.getMultiBarWithFocusChart(chartWidth, chartHeight, data);
351
+ }
352
+ else {
353
+ chart = this.getLineChart(chartWidth, chartHeight, data);
354
+ }
355
+ // 设置 tooltip 自动隐藏逻辑
356
+ this.setupTooltipAutoHide(chart);
357
+ return chart;
358
+ }
359
+ initPoint() {
360
+ try {
361
+ const legendList = this.$element
362
+ .find('.nv-legend')
363
+ .find('.nv-series');
364
+ let hiddenCount = 0;
365
+ for (let i = 0; i < this.data.length; i++) {
366
+ const channel = this.model.dataSetting.channels[i];
367
+ if (legendList.eq(i).children().eq(0).css('fill-opacity') === '1') {
368
+ const pointList = this.$element
369
+ .find('.nv-scatterWrap')
370
+ .find('.nv-series-' + (i - hiddenCount))
371
+ .find('.nv-point');
372
+ if (pointList && pointList.length) {
373
+ for (let j = 0; j < pointList.length; j++) {
374
+ const point = pointList.eq(j);
375
+ const previousPoint = pointList.eq(j - 1);
376
+ if (j && point.attr('transform').split(',')[1] !== previousPoint.attr('transform').split(',')[1]) {
377
+ if (channel.enablePoint && channel.pointColor) {
378
+ const pointStyle = {
379
+ 'stroke-opacity': 1,
380
+ 'stroke-width': '2px',
381
+ 'stroke': channel.pointColor,
382
+ 'fill-opacity': 1,
383
+ 'fill': channel.pointColor
384
+ };
385
+ point.addClass('nv-mark-point');
386
+ point.css(pointStyle);
387
+ previousPoint.addClass('nv-mark-point');
388
+ previousPoint.css(pointStyle);
389
+ }
390
+ }
391
+ }
392
+ }
393
+ }
394
+ else {
395
+ hiddenCount++;
396
+ }
140
397
  }
141
- });
398
+ }
399
+ catch (e) {
400
+ console.log(e);
401
+ }
142
402
  }
143
403
  getLineChart(chartWidth, chartHeight, data) {
144
404
  const chart = nv.models.lineChart().showLegend(true)
145
405
  .margin({ top: 0, bottom: 0, left: this.displayOption.marginLeft, right: this.displayOption.marginRight })
146
406
  .noData(this.localization.chartNoData);
407
+ if (this.model.displaySetting.axisSetting.yAxisRangeType === AxisRangeType.Custom) {
408
+ chart.yDomain([this.model.displaySetting.axisSetting.yAxisMin, this.model.displaySetting.axisSetting.yAxisMax]);
409
+ }
147
410
  if (!this.isMobileMode) {
148
411
  chart.focusEnable(true);
149
412
  chart.focus.margin({ top: 10, right: 0, bottom: 0, left: 0 });
@@ -152,6 +415,19 @@ export class HistoricalCurveElement extends ConditionalDisplayElement {
152
415
  }
153
416
  this.renderCommonProperty(chart, chartWidth, chartHeight, data);
154
417
  this.renderOperationArea(chartWidth, chartHeight);
418
+ this.initPoint();
419
+ chart.legend.dispatch.on('legendClick', () => {
420
+ setTimeout(() => {
421
+ this.$element.find('.nv-mark-point').css({
422
+ 'stroke-opacity': 0,
423
+ 'stroke-width': 0,
424
+ 'stroke': 'unset',
425
+ 'fill-opacity': 0,
426
+ 'fill': 'unset'
427
+ }).removeClass('nv-mark-point');
428
+ this.initPoint();
429
+ }, 1);
430
+ });
155
431
  return chart;
156
432
  }
157
433
  getMultiBarWithFocusChart(chartWidth, chartHeight, data) {
@@ -168,6 +444,9 @@ export class HistoricalCurveElement extends ConditionalDisplayElement {
168
444
  chart.multibar.stacked(true);
169
445
  chart.multibar2.stacked(true);
170
446
  }
447
+ if (this.model.displaySetting.axisSetting.yAxisRangeType === AxisRangeType.Custom) {
448
+ chart.yDomain([this.model.displaySetting.axisSetting.yAxisMin, this.model.displaySetting.axisSetting.yAxisMax]);
449
+ }
171
450
  if (!this.isMobileMode) {
172
451
  chart.focusEnable(true);
173
452
  chart.focusShowAxisX(false);
@@ -188,9 +467,27 @@ export class HistoricalCurveElement extends ConditionalDisplayElement {
188
467
  return chart;
189
468
  }
190
469
  renderCommonProperty(chart, chartWidth, chartHeight, data) {
470
+ var _a, _b;
191
471
  chart.tooltip.headerFormatter(d => this.timeFormat(d, '%x %X'));
192
472
  if (this.model.displaySetting.showAxis) {
193
- chart.xAxis.showMaxMin(true).tickFormat(d => this.timeFormat(d, '%X'));
473
+ chart.xAxis.showMaxMin(true).tickFormat(d => {
474
+ this.$element.find('.nv-mark-point').css({
475
+ 'stroke-opacity': 0,
476
+ 'stroke-width': 0,
477
+ 'stroke': 'unset',
478
+ 'fill-opacity': 0,
479
+ 'fill': 'unset'
480
+ }).removeClass('nv-mark-point');
481
+ clearTimeout(this.timer);
482
+ this.timer = undefined;
483
+ this.timer = setTimeout(() => {
484
+ this.initPoint();
485
+ }, 100);
486
+ if (this.currentTimePeriod === 3 || this.currentTimePeriod === 4 || this.currentTimePeriod === 5) {
487
+ return this.timeFormat(d, '%y-%m-%d');
488
+ }
489
+ return this.timeFormat(Number(d), '%X');
490
+ });
194
491
  if (this.model.displaySetting.axisSetting) {
195
492
  if (this.model.displaySetting.axisSetting.showAxisLabel && this.model.displaySetting.axisSetting.axisLabelFont) {
196
493
  chart.xAxis.fontSize(this.model.displaySetting.axisSetting.axisLabelFont.fontSize);
@@ -211,52 +508,419 @@ export class HistoricalCurveElement extends ConditionalDisplayElement {
211
508
  chart.color(this.model.dataSetting.channels.map(c => c.connectorColor));
212
509
  this.rootElement.append('g').datum(data).call(chart);
213
510
  this.rootElement.selectAll('.nv-noData').attr('x', chartWidth / 2).attr('y', chartHeight / 2 + this.displayOption.operationAreaHeight);
214
- nv.utils.windowResize(() => {
511
+ this.resizeEventListener = nv.utils.windowResize(() => {
512
+ if (!this.needResize)
513
+ return;
215
514
  chart.update();
216
515
  this.rootElement.selectAll('.nv-noData').attr('x', chartWidth / 2).attr('y', chartHeight / 2 + this.displayOption.operationAreaHeight);
217
516
  });
517
+ const fontSize = (_b = (_a = this.model.displaySetting.axisSetting) === null || _a === void 0 ? void 0 : _a.axisLabelFont) === null || _b === void 0 ? void 0 : _b.fontSize;
218
518
  this.rootElement.selectAll('.domain').style('stroke-opacity', 1);
219
519
  if (this.model.displaySetting.showAxis && this.model.displaySetting.axisSetting) {
220
520
  const axisColor = this.model.displaySetting.axisSetting.axisColor;
221
521
  this.rootElement.selectAll('.domain').style('stroke', axisColor);
222
- if (this.model.displaySetting.axisSetting.showAxisLabel) {
223
- const fontSize = this.model.displaySetting.axisSetting.axisLabelFont.fontSize;
522
+ if (fontSize && this.model.displaySetting.axisSetting.showAxisLabel) {
224
523
  this.rootElement.selectAll('.nv-axisMaxMin').select('text').style('font-size', fontSize);
225
524
  }
226
525
  }
526
+ let strokeWidth = 0;
527
+ if (this.model.displaySetting.axisSetting.showTick !== false) {
528
+ strokeWidth = 1;
529
+ }
530
+ this.rootElement
531
+ .selectAll('.nv-x')
532
+ .selectAll('.tick')
533
+ .selectAll('line')
534
+ .attr('style', `stroke:${this.model.displaySetting.axisSetting.xAxisTickColor || 'rgb(127, 147, 159)'};stroke-width:${strokeWidth};`);
535
+ this.rootElement
536
+ .selectAll('.nv-y')
537
+ .selectAll('.tick')
538
+ .selectAll('line')
539
+ .attr('style', `stroke:${this.model.displaySetting.axisSetting.yAxisTickColor || 'rgb(127, 147, 159)'};stroke-width:${strokeWidth};`);
540
+ if (fontSize && this.currentTimePeriod === 3 || this.currentTimePeriod === 4 || this.currentTimePeriod === 5) {
541
+ const self = this;
542
+ this.rootElement
543
+ .selectAll('.nv-x')
544
+ .selectAll('.tick')
545
+ .selectAll('text')
546
+ .data(function (d) {
547
+ return [self.timeFormat(Number(d), '%y-%m-%d'), self.timeFormat(Number(d), '%H:%M:%S')];
548
+ })
549
+ .enter()
550
+ .append('text')
551
+ .attr('class', 'full-date')
552
+ .attr('x', 0)
553
+ .attr('y', 0)
554
+ .attr('dy', '2.3em')
555
+ .style('text-anchor', 'middle')
556
+ .style('font-size', fontSize)
557
+ .text((d) => d);
558
+ this.rootElement
559
+ .selectAll('.nv-axisMaxMin-x')
560
+ .selectAll('text')
561
+ .data(function (d) {
562
+ return [self.timeFormat(Number(d), '%y-%m-%d'), self.timeFormat(Number(d), '%H:%M:%S')];
563
+ })
564
+ .enter()
565
+ .append('text')
566
+ .attr('class', 'full-date')
567
+ .attr('x', 0)
568
+ .attr('y', 0)
569
+ .attr('dy', '2.3em')
570
+ .style('text-anchor', 'middle')
571
+ .style('font-size', fontSize)
572
+ .text((d) => d);
573
+ const focusWrap = this.rootElement.selectAll('.nv-focusWrap');
574
+ if (focusWrap.size()) {
575
+ let h = focusWrap.attr('transform');
576
+ if (h && h.length && h.indexOf(',') !== -1) {
577
+ h = h.slice(0, -1).split(',')[1];
578
+ this.rootElement
579
+ .selectAll('.nv-focusWrap')
580
+ .attr('transform', `translate(0,${Number(h) + 15})`);
581
+ }
582
+ }
583
+ const resizeObserver = new window.MutationObserver(() => {
584
+ this.rootElement
585
+ .selectAll('.nv-x')
586
+ .selectAll('.tick')
587
+ .selectAll('.full-date')
588
+ .remove();
589
+ this.rootElement
590
+ .selectAll('.nv-x')
591
+ .selectAll('.tick')
592
+ .selectAll('text')
593
+ .data(function (d) {
594
+ return [self.timeFormat(Number(d), '%y-%m-%d'), self.timeFormat(Number(d), '%H:%M:%S')];
595
+ })
596
+ .enter()
597
+ .append('text')
598
+ .attr('class', 'full-date')
599
+ .attr('x', 0)
600
+ .attr('y', 0)
601
+ .attr('dy', '2.3em')
602
+ .style('text-anchor', 'middle')
603
+ .style('font-size', fontSize)
604
+ .text((d) => d);
605
+ });
606
+ const extent = document.getElementsByClassName('extent');
607
+ if (extent.length) {
608
+ resizeObserver.observe(extent[0], { attributes: true });
609
+ }
610
+ }
611
+ else {
612
+ this.rootElement
613
+ .selectAll('.full-date')
614
+ .remove();
615
+ }
227
616
  }
228
617
  renderOperationArea(chartWidth, chartHeight) {
618
+ const backgroundColor = this.model.displaySetting.axisSetting.filterBackgroudColor || 'inherit';
229
619
  const operationArea = this.rootElement.append('g').attr('transform', `translate(0,${chartHeight + this.displayOption.operationAreaMarginTop})`)
230
620
  .append('foreignObject').attr('width', chartWidth).attr('height', this.displayOption.operationAreaHeight).attr('fill', 'none')
231
621
  .append('xhtml:div').style('height', (this.displayOption.operationAreaHeight - 4) + 'px').style('overflow', 'hidden').style('margin-top', '4px');
232
- const selectElement = operationArea.append('select').style('margin-left', this.displayOption.marginLeft + 'px')
233
- .style('font-size', this.displayOption.operationSelectFontSize).on('change', () => {
234
- const displayTimePeriod = this.rootElement.select('select').property('value');
235
- this.updateTimeRange(displayTimePeriod);
236
- this.reRenderElement(this.startTime, this.endTime, this.displayOption.dataLimit, HistoricalCurveTimeRange.BeginOpenEndOpen);
622
+ const rect = this.$element.parent().parent().find('rect');
623
+ const fillColor = rect.attr('fill');
624
+ const dropdownBg = this.model.displaySetting.axisSetting.filterBackgroudColor || fillColor || '#fff';
625
+ const dropdownWrapper = operationArea.append('div')
626
+ .style('display', 'inline-block')
627
+ .style('margin-left', this.displayOption.marginLeft + 'px')
628
+ .style('vertical-align', 'middle');
629
+ const selectedText = this.timePeriods.find(t => t.key === Number(this.currentTimePeriod));
630
+ const arrowIconUrl = 'url("data:image/svg+xml,'
631
+ + '%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%228%22 height=%225%22 viewBox=%220 0 8 5%22%3E'
632
+ + '%3Cpath d=%22M0 0L4 5L8 0z%22 fill=%22%23666%22/%3E%3C/svg%3E")';
633
+ const dropdownTrigger = dropdownWrapper.append('div')
634
+ .attr('class', 'hc-dropdown-trigger')
635
+ .style('background-color', backgroundColor)
636
+ .style('background-image', arrowIconUrl)
637
+ .style('background-repeat', 'no-repeat')
638
+ .style('background-position', 'right 4px center')
639
+ .style('font-size', this.displayOption.operationSelectFontSize)
640
+ .style('cursor', 'pointer')
641
+ .style('padding', '0 18px 0 4px')
642
+ .style('border', '1px solid #ccc')
643
+ .style('border-radius', '2px')
644
+ .style('white-space', 'nowrap')
645
+ .style('line-height', (this.displayOption.operationAreaHeight - 8) + 'px')
646
+ .style('min-height', (this.displayOption.operationAreaHeight - 8) + 'px')
647
+ .text(selectedText ? selectedText.name : '');
648
+ const dropdownListEl = document.createElement('div');
649
+ dropdownListEl.style.cssText = 'display:none;position:fixed;background:' + dropdownBg
650
+ + ';border:1px solid #ccc;border-radius:2px;z-index:9999;box-shadow:0 2px 6px rgba(0,0,0,0.15);';
651
+ document.body.appendChild(dropdownListEl);
652
+ this.dropdownListEl = dropdownListEl;
653
+ this.dropdownTriggerEl = dropdownTrigger.node();
654
+ const dropdownList = d3.select(dropdownListEl);
655
+ this.timePeriods.forEach(tp => {
656
+ dropdownList.append('div')
657
+ .attr('class', 'hc-dropdown-item')
658
+ .style('padding', '4px 8px')
659
+ .style('cursor', 'pointer')
660
+ .style('white-space', 'nowrap')
661
+ .style('font-size', this.displayOption.operationSelectFontSize)
662
+ .style('line-height', (this.displayOption.operationAreaHeight - 8) + 'px')
663
+ .style('min-height', (this.displayOption.operationAreaHeight - 8) + 'px')
664
+ .style('background', tp.key === Number(this.currentTimePeriod) ? '#1890ff' : 'transparent')
665
+ .style('color', tp.key === Number(this.currentTimePeriod) ? '#fff' : '#333')
666
+ .text(tp.name)
667
+ .on('mouseover', function () {
668
+ const el = d3.select(this);
669
+ if (!el.classed('hc-dropdown-selected')) {
670
+ el.style('background', '#e6f7ff').style('color', '#333');
671
+ }
672
+ })
673
+ .on('mouseout', function () {
674
+ const el = d3.select(this);
675
+ if (!el.classed('hc-dropdown-selected')) {
676
+ el.style('background', 'transparent').style('color', '#333');
677
+ }
678
+ })
679
+ .on('click', () => {
680
+ dropdownListEl.style.display = 'none';
681
+ // 从 this.timePeriods 实时查找,确保语种切换后显示最新文案
682
+ const currentPeriod = this.timePeriods.find(p => p.key === tp.key);
683
+ const currentName = currentPeriod ? currentPeriod.name : tp.name;
684
+ dropdownTrigger.text(currentName);
685
+ // 更新选中状态
686
+ dropdownList.selectAll('.hc-dropdown-item')
687
+ .classed('hc-dropdown-selected', false)
688
+ .style('background', 'transparent')
689
+ .style('color', '#333');
690
+ d3.select(d3.event.currentTarget)
691
+ .classed('hc-dropdown-selected', true)
692
+ .style('background', '#1890ff')
693
+ .style('color', '#fff');
694
+ if (tp.key === 8) {
695
+ const isSimulation = !this.releasedVariableService;
696
+ if (!isSimulation) {
697
+ this.showCustomTimeRangeModal();
698
+ }
699
+ return;
700
+ }
701
+ this.updateTimeRange(tp.key);
702
+ this.reRenderElement(this.startTime, this.endTime, this.displayOption.dataLimit, HistoricalCurveTimeRange.BeginOpenEndOpen);
703
+ });
237
704
  });
238
- const options = selectElement.selectAll('option').data(this.timePeriods).enter().append('option');
239
- options.text(d => d.name).attr('value', d => d.key).property('selected', d => d.key === Number(this.currentTimePeriod));
705
+ // 测量下拉列表最大宽度,让 trigger 和列表宽度一致
706
+ dropdownListEl.style.display = 'block';
707
+ dropdownListEl.style.visibility = 'hidden';
708
+ const listContentWidth = dropdownListEl.offsetWidth;
709
+ dropdownListEl.style.display = 'none';
710
+ dropdownListEl.style.visibility = '';
711
+ dropdownListEl.style.width = listContentWidth + 'px';
712
+ dropdownTrigger.node().style.width = listContentWidth + 'px';
713
+ dropdownTrigger.node().style.boxSizing = 'border-box';
714
+ const hideDropdown = () => { dropdownListEl.style.display = 'none'; };
715
+ dropdownTrigger.on('click', () => {
716
+ const isVisible = dropdownListEl.style.display !== 'none';
717
+ if (isVisible) {
718
+ hideDropdown();
719
+ }
720
+ else {
721
+ const triggerNode = dropdownTrigger.node();
722
+ const triggerRect = triggerNode.getBoundingClientRect();
723
+ // 设置宽度至少和 trigger 一样宽
724
+ dropdownListEl.style.minWidth = triggerRect.width + 'px';
725
+ dropdownListEl.style.left = triggerRect.left + 'px';
726
+ dropdownListEl.style.display = 'block';
727
+ const listHeight = dropdownListEl.offsetHeight;
728
+ const spaceBelow = window.innerHeight - triggerRect.bottom;
729
+ if (spaceBelow < listHeight) {
730
+ // 向上展开
731
+ dropdownListEl.style.top = '';
732
+ dropdownListEl.style.bottom = (window.innerHeight - triggerRect.top) + 'px';
733
+ }
734
+ else {
735
+ // 向下展开
736
+ dropdownListEl.style.bottom = '';
737
+ dropdownListEl.style.top = triggerRect.bottom + 'px';
738
+ }
739
+ }
740
+ });
741
+ // 点击外部收起
742
+ this._outsideClickHandler = (event) => {
743
+ const triggerNode = dropdownTrigger.node();
744
+ if (!dropdownListEl.contains(event.target) && !triggerNode.contains(event.target)) {
745
+ hideDropdown();
746
+ }
747
+ };
748
+ document.addEventListener('click', this._outsideClickHandler);
749
+ // 全屏切换(含点击全屏按钮和 F11)时收起
750
+ this._fullscreenChangeHandler = () => { hideDropdown(); };
751
+ document.addEventListener('fullscreenchange', this._fullscreenChangeHandler);
752
+ document.addEventListener('webkitfullscreenchange', this._fullscreenChangeHandler);
753
+ // F11 退出全屏时 keydown 被浏览器拦截,用 resize 兜底
754
+ this._resizeHideHandler = () => { hideDropdown(); };
755
+ window.addEventListener('resize', this._resizeHideHandler);
756
+ // 滚动时收起
757
+ document.addEventListener('scroll', hideDropdown, true);
240
758
  const buttonWidth = this.displayOption.operationButtonWidth + 'px', buttonHeight = this.displayOption.operationButtonHeight + 'px';
241
759
  operationArea.append('button').style('width', buttonWidth).style('height', buttonHeight).style('border', 'none')
242
- .style('float', 'right').style('background', 'white').style('background-image', 'url(assets/img/last_page.svg)')
760
+ .style('float', 'right').style('background-image', 'url(assets/img/black_last_page.png)')
761
+ .style('background-color', backgroundColor).style('background-size', 'cover')
243
762
  .on('click', () => { this.loadLastPage(); });
244
763
  operationArea.append('button').style('width', buttonWidth).style('height', buttonHeight).style('border', 'none')
245
- .style('float', 'right').style('background', 'white').style('background-image', 'url(assets/img/next_page.svg)')
764
+ .style('float', 'right').style('background-image', 'url(assets/img/black_next_page.png)')
246
765
  .style('margin', `0 ${this.displayOption.operationButtonMargin}px 0 0`)
766
+ .style('background-color', backgroundColor).style('background-size', 'cover')
247
767
  .on('click', () => { this.loadNextPage(); });
248
768
  operationArea.append('button').style('width', buttonWidth).style('height', buttonHeight).style('border', 'none')
249
- .style('float', 'right').style('background', 'white').style('background-image', 'url(assets/img/previous_page.svg)')
769
+ .style('float', 'right').style('background-image', 'url(assets/img/black_previous_page.png)')
250
770
  .style('margin', `0 ${this.displayOption.operationButtonMargin}px 0 0`)
771
+ .style('background-color', backgroundColor).style('background-size', 'cover')
251
772
  .on('click', () => { this.loadPreviousPage(); });
252
773
  operationArea.append('button').style('width', buttonWidth).style('height', buttonHeight).style('border', 'none')
253
- .style('float', 'right').style('background', 'white').style('background-image', 'url(assets/img/first_page.svg)')
774
+ .style('float', 'right').style('background-image', 'url(assets/img/black_first_page.png)')
254
775
  .style('margin', `0 ${this.displayOption.operationButtonMargin}px 0 0`)
776
+ .style('background-color', backgroundColor).style('background-size', 'cover')
255
777
  .on('click', () => { this.loadFirstPage(); });
256
778
  }
257
779
  timeFormat(datetime, specifier) {
258
780
  return d3.time.format(specifier)(new Date(datetime));
259
781
  }
782
+ fmtDatetimeLocal(d) {
783
+ const p2 = (n) => (n < 10 ? '0' : '') + n;
784
+ return d.getFullYear() + '-' + p2(d.getMonth() + 1) + '-' + p2(d.getDate())
785
+ + 'T' + p2(d.getHours()) + ':' + p2(d.getMinutes()) + ':' + p2(d.getSeconds());
786
+ }
787
+ showCustomTimeRangeModal() {
788
+ const now = new Date();
789
+ const defaultStart = this.customStartTime ? this.customStartTime.toDate()
790
+ : new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
791
+ const defaultEnd = this.customEndTime ? this.customEndTime.toDate()
792
+ : new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59);
793
+ if (this.guiContext && this.guiContext.showCustomTimeRangeModal) {
794
+ this.guiContext.showCustomTimeRangeModal({ startTime: defaultStart, endTime: defaultEnd }).then(result => {
795
+ if (result) {
796
+ const momentAny = moment;
797
+ this.customStartTime = momentAny(result.startTime);
798
+ this.customEndTime = momentAny(result.endTime);
799
+ this.updateTimeRange(8);
800
+ this.reRenderElement(this.startTime, this.endTime, this.displayOption.dataLimit, HistoricalCurveTimeRange.BeginOpenEndOpen);
801
+ }
802
+ });
803
+ return;
804
+ }
805
+ this.showCustomTimeRangeModalFallback();
806
+ }
807
+ showCustomTimeRangeModalFallback() {
808
+ const prevTimePeriod = this.currentTimePeriod;
809
+ const now = new Date();
810
+ const defaultStart = this.fmtDatetimeLocal(this.customStartTime ? this.customStartTime.toDate()
811
+ : new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0));
812
+ const defaultEnd = this.fmtDatetimeLocal(this.customEndTime ? this.customEndTime.toDate()
813
+ : new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59));
814
+ // 临时将 <html lang> 切换为当前应用语言,使原生 datetime-local
815
+ // 弹出日历的 UI(星期/月份文字等)跟随应用语种显示,关闭时还原
816
+ const currentLang = this.getCurrentCulture();
817
+ const prevDocLang = document.documentElement.lang;
818
+ document.documentElement.lang = currentLang;
819
+ const overlay = document.createElement('div');
820
+ overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:9999;';
821
+ // 部分浏览器可直接读取元素上的 lang 属性来本地化原生控件
822
+ overlay.lang = currentLang;
823
+ const modal = document.createElement('div');
824
+ let modalCss = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);'
825
+ + 'background:#fff;border-radius:4px;';
826
+ if (this.isMobileMode) {
827
+ modalCss += 'width:90%;';
828
+ }
829
+ else {
830
+ modalCss += 'min-width:460px;';
831
+ }
832
+ modalCss += 'box-shadow:0 4px 12px rgba(0,0,0,0.15);font-family:inherit;font-size:14px;';
833
+ modal.style.cssText = modalCss;
834
+ const header = document.createElement('div');
835
+ header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid #e8e8e8;';
836
+ const title = document.createElement('span');
837
+ title.style.cssText = 'font-size:16px;font-weight:500;color:#333;';
838
+ title.textContent = this.localization.customTimeRange;
839
+ const closeBtn = document.createElement('span');
840
+ closeBtn.style.cssText = 'cursor:pointer;font-size:18px;color:#999;line-height:1;';
841
+ closeBtn.textContent = '×';
842
+ header.appendChild(title);
843
+ header.appendChild(closeBtn);
844
+ const body = document.createElement('div');
845
+ body.style.cssText = 'padding:20px 16px;display:flex;align-items:center;flex-wrap:wrap;gap:8px;';
846
+ const label = document.createElement('span');
847
+ label.style.cssText = 'color:#333;white-space:nowrap;';
848
+ label.textContent = this.localization.timeRange + ':';
849
+ const inputStyle = 'border:1px solid #d9d9d9;border-radius:4px;padding:4px 8px;font-size:14px;height:32px;outline:none;color:#333;';
850
+ const startInput = document.createElement('input');
851
+ startInput.type = 'datetime-local';
852
+ startInput.setAttribute('step', '1');
853
+ startInput.setAttribute('lang', currentLang);
854
+ startInput.value = defaultStart;
855
+ startInput.style.cssText = inputStyle;
856
+ const separator = document.createElement('span');
857
+ separator.textContent = ' - ';
858
+ separator.style.cssText = 'color:#333;';
859
+ const endInput = document.createElement('input');
860
+ endInput.type = 'datetime-local';
861
+ endInput.setAttribute('step', '1');
862
+ endInput.setAttribute('lang', currentLang);
863
+ endInput.value = defaultEnd;
864
+ endInput.style.cssText = inputStyle;
865
+ body.appendChild(label);
866
+ body.appendChild(startInput);
867
+ body.appendChild(separator);
868
+ body.appendChild(endInput);
869
+ const footer = document.createElement('div');
870
+ footer.style.cssText = 'display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid #e8e8e8;';
871
+ const cancelBtn = document.createElement('button');
872
+ cancelBtn.textContent = this.localization.cancel;
873
+ cancelBtn.style.cssText = 'padding:5px 16px;border:1px solid #d9d9d9;border-radius:4px;background:#fff;cursor:pointer;font-size:14px;color:#333;';
874
+ const saveBtn = document.createElement('button');
875
+ saveBtn.textContent = this.localization.submit;
876
+ saveBtn.style.cssText = 'padding:5px 16px;border:none;border-radius:4px;background:#1890ff;color:#fff;cursor:pointer;font-size:14px;';
877
+ footer.appendChild(cancelBtn);
878
+ footer.appendChild(saveBtn);
879
+ modal.appendChild(header);
880
+ modal.appendChild(body);
881
+ modal.appendChild(footer);
882
+ overlay.appendChild(modal);
883
+ document.body.appendChild(overlay);
884
+ const close = (revert) => {
885
+ // 还原弹窗打开前的 <html lang>,避免影响页面其他部分
886
+ document.documentElement.lang = prevDocLang;
887
+ document.body.removeChild(overlay);
888
+ if (revert) {
889
+ const selectEl = this.$element && this.$element.find('select');
890
+ if (selectEl && selectEl.length) {
891
+ selectEl.val(prevTimePeriod.toString());
892
+ }
893
+ }
894
+ };
895
+ closeBtn.addEventListener('click', () => close(true));
896
+ cancelBtn.addEventListener('click', () => close(true));
897
+ saveBtn.addEventListener('click', () => {
898
+ const startVal = startInput.value;
899
+ const endVal = endInput.value;
900
+ if (!startVal) {
901
+ startInput.style.borderColor = 'red';
902
+ return;
903
+ }
904
+ const startDate = new Date(startVal);
905
+ const endDate = endVal
906
+ ? new Date(endVal)
907
+ : new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate(), 23, 59, 59);
908
+ if (isNaN(startDate.getTime())) {
909
+ startInput.style.borderColor = 'red';
910
+ return;
911
+ }
912
+ if (endDate.getTime() <= startDate.getTime()) {
913
+ endInput.style.borderColor = 'red';
914
+ return;
915
+ }
916
+ const momentAny = moment;
917
+ this.customStartTime = momentAny(startDate);
918
+ this.customEndTime = momentAny(endDate);
919
+ close(false);
920
+ this.updateTimeRange(8);
921
+ this.reRenderElement(this.startTime, this.endTime, this.displayOption.dataLimit, HistoricalCurveTimeRange.BeginOpenEndOpen);
922
+ });
923
+ }
260
924
  loadFirstPage() {
261
925
  this.updateQueryTimeRange();
262
926
  this.reRenderElement(this.startTime, this.endTime, this.displayOption.dataLimit, HistoricalCurveTimeRange.BeginOpenEndOpen);