@flexem/fc-gui 3.0.0-alpha.19 → 3.0.0-alpha.190

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 (212) hide show
  1. package/CHANGELOG.md +413 -0
  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 +37817 -32408
  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 +5 -0
  35. package/elements/air-quality/air-quality-element.js +3 -4
  36. package/elements/alarm/alarm-element.d.ts +106 -0
  37. package/elements/alarm/alarm-element.js +836 -0
  38. package/elements/alarm/alarm-element.metadata.json +1 -0
  39. package/elements/alarm/alarm-table-scrollbar.d.ts +35 -0
  40. package/elements/alarm/alarm-table-scrollbar.js +170 -0
  41. package/elements/alarm/alarm-table-scrollbar.metadata.json +1 -0
  42. package/elements/bar-graph-element.d.ts +10 -2
  43. package/elements/bar-graph-element.js +135 -5
  44. package/elements/bar-graph-element.metadata.json +1 -1
  45. package/elements/base/readable-element.d.ts +6 -1
  46. package/elements/base/readable-element.js +69 -2
  47. package/elements/base/readable-element.metadata.json +1 -1
  48. package/elements/base/state-control-element.d.ts +13 -1
  49. package/elements/base/state-control-element.js +42 -8
  50. package/elements/base/state-control-element.metadata.json +1 -1
  51. package/elements/character-display/character-display-element.d.ts +12 -2
  52. package/elements/character-display/character-display-element.js +35 -7
  53. package/elements/character-display/character-display-element.metadata.json +1 -1
  54. package/elements/datetime-display/datetime-display-element.d.ts +1 -0
  55. package/elements/datetime-display/datetime-display-element.js +10 -2
  56. package/elements/datetime-display/datetime-display-element.metadata.json +1 -1
  57. package/elements/datetime-display/time-zone-select-json.d.ts +8 -0
  58. package/elements/datetime-display/time-zone-select-json.js +558 -0
  59. package/elements/historical-curve/historical-curve.element.d.ts +45 -3
  60. package/elements/historical-curve/historical-curve.element.js +707 -45
  61. package/elements/historical-curve/historical-curve.element.metadata.json +1 -1
  62. package/elements/main-element.d.ts +1 -0
  63. package/elements/main-element.js +72 -10
  64. package/elements/main-element.metadata.json +1 -1
  65. package/elements/meter-element.d.ts +7 -1
  66. package/elements/meter-element.js +76 -7
  67. package/elements/meter-element.metadata.json +1 -1
  68. package/elements/numerical-display/numerical-display-element.d.ts +31 -3
  69. package/elements/numerical-display/numerical-display-element.js +166 -15
  70. package/elements/numerical-display/numerical-display-element.metadata.json +1 -1
  71. package/elements/per-view-variable-communicator.d.ts +4 -0
  72. package/elements/per-view-variable-communicator.js +21 -1
  73. package/elements/per-view-variable-communicator.metadata.json +1 -1
  74. package/elements/ring-graph/ring-graph-element.d.ts +13 -1
  75. package/elements/ring-graph/ring-graph-element.js +164 -3
  76. package/elements/ring-graph/ring-graph-element.metadata.json +1 -1
  77. package/elements/scroll-alarm/scroll-alarm-element.d.ts +79 -0
  78. package/elements/scroll-alarm/scroll-alarm-element.js +845 -0
  79. package/elements/scroll-alarm/scroll-alarm-element.metadata.json +1 -0
  80. package/elements/shared/graph/graph-state-element.d.ts +1 -0
  81. package/elements/shared/graph/graph-state-element.js +30 -1
  82. package/elements/shared/graph/graph-state-element.metadata.json +1 -1
  83. package/elements/shared/text/text-element.d.ts +14 -0
  84. package/elements/shared/text/text-element.js +38 -6
  85. package/elements/shared/text/text-element.metadata.json +1 -1
  86. package/elements/shared/text/text-state-element.d.ts +39 -2
  87. package/elements/shared/text/text-state-element.js +242 -67
  88. package/elements/shared/text/text-state-element.metadata.json +1 -1
  89. package/elements/static-elements/hyperlink-element.d.ts +28 -2
  90. package/elements/static-elements/hyperlink-element.js +153 -4
  91. package/elements/static-elements/hyperlink-element.metadata.json +1 -1
  92. package/elements/static-elements/text-element.d.ts +28 -2
  93. package/elements/static-elements/text-element.js +162 -12
  94. package/elements/static-elements/text-element.metadata.json +1 -1
  95. package/elements/switch-indicator-light/bit-indicator-light-operator.d.ts +1 -0
  96. package/elements/switch-indicator-light/bit-indicator-light-operator.js +10 -3
  97. package/elements/switch-indicator-light/bit-indicator-light-operator.metadata.json +1 -1
  98. package/elements/switch-indicator-light/switch-indicator-light-element.d.ts +20 -2
  99. package/elements/switch-indicator-light/switch-indicator-light-element.js +92 -15
  100. package/elements/switch-indicator-light/switch-indicator-light-element.metadata.json +1 -1
  101. package/elements/switch-indicator-light/word-indicator-light-operator.d.ts +1 -0
  102. package/elements/switch-indicator-light/word-indicator-light-operator.js +10 -3
  103. package/elements/switch-indicator-light/word-indicator-light-operator.metadata.json +1 -1
  104. package/elements/video/video-element.d.ts +10 -0
  105. package/elements/video/video-element.js +139 -21
  106. package/elements/video/video-element.metadata.json +1 -1
  107. package/elements/view-operation/view-operation.element.d.ts +28 -2
  108. package/elements/view-operation/view-operation.element.js +172 -8
  109. package/elements/view-operation/view-operation.element.metadata.json +1 -1
  110. package/elements/weather/weater-element.js +0 -1
  111. package/gui/gui-context.d.ts +25 -2
  112. package/gui/gui-host.d.ts +1 -1
  113. package/gui/gui-view.d.ts +19 -2
  114. package/gui/gui-view.js +147 -12
  115. package/gui/gui-view.metadata.json +1 -1
  116. package/gui/gui.component.d.ts +3 -0
  117. package/gui/gui.component.js +15 -2
  118. package/gui/gui.component.metadata.json +1 -1
  119. package/localization/localization.service.d.ts +11 -0
  120. package/localization/localization.service.js +16 -5
  121. package/localization/localization.service.metadata.json +1 -1
  122. package/localization/localization.service.zh_CN.js +12 -1
  123. package/localization/localization.service.zh_CN.metadata.json +1 -1
  124. package/modal/alert/alert-modal.component.js +1 -1
  125. package/modal/alert/alert-modal.component.less +13 -5
  126. package/modal/alert/alert-modal.component.metadata.json +1 -1
  127. package/modal/confirm-operation/confirm-operation-modal.component.html +2 -2
  128. package/modal/confirm-operation/confirm-operation-modal.component.js +2 -2
  129. package/modal/confirm-operation/confirm-operation-modal.component.less +10 -7
  130. package/modal/confirm-operation/confirm-operation-modal.component.metadata.json +1 -1
  131. package/modal/verify-password/verify-password-modal.component.d.ts +6 -2
  132. package/modal/verify-password/verify-password-modal.component.html +2 -2
  133. package/modal/verify-password/verify-password-modal.component.js +11 -5
  134. package/modal/verify-password/verify-password-modal.component.less +9 -0
  135. package/modal/verify-password/verify-password-modal.component.metadata.json +1 -1
  136. package/modal/write-character/write-character-modal-args.d.ts +3 -1
  137. package/modal/write-character/write-character-modal-args.js +2 -1
  138. package/modal/write-character/write-character-modal-args.metadata.json +1 -1
  139. package/modal/write-character/write-character-modal.component.d.ts +4 -2
  140. package/modal/write-character/write-character-modal.component.html +4 -4
  141. package/modal/write-character/write-character-modal.component.js +11 -5
  142. package/modal/write-character/write-character-modal.component.less +15 -0
  143. package/modal/write-character/write-character-modal.component.metadata.json +1 -1
  144. package/modal/write-value/write-value-modal-args.d.ts +5 -1
  145. package/modal/write-value/write-value-modal-args.js +3 -1
  146. package/modal/write-value/write-value-modal-args.metadata.json +1 -1
  147. package/modal/write-value/write-value-modal.component.d.ts +16 -8
  148. package/modal/write-value/write-value-modal.component.html +10 -5
  149. package/modal/write-value/write-value-modal.component.js +103 -21
  150. package/modal/write-value/write-value-modal.component.less +16 -1
  151. package/modal/write-value/write-value-modal.component.metadata.json +1 -1
  152. package/model/alarm/alarm.model.d.ts +14 -0
  153. package/model/alarm/alarm.model.js +0 -0
  154. package/model/alarm/alarm.model.metadata.json +1 -0
  155. package/model/bar-graph/bar-graph.d.ts +4 -0
  156. package/model/base/font-setting-model.d.ts +22 -1
  157. package/model/base/font-setting-model.metadata.json +1 -1
  158. package/model/base/readable-model.d.ts +4 -0
  159. package/model/character-display/character-display.d.ts +1 -0
  160. package/model/datetime-display/datetime-display.d.ts +1 -0
  161. package/model/historical-curve/historical-curve-axis-settings.d.ts +11 -0
  162. package/model/historical-curve/historical-curve-axis-settings.js +5 -0
  163. package/model/historical-curve/historical-curve-axis-settings.metadata.json +1 -1
  164. package/model/historical-curve/historical-curve-chanel.model.d.ts +8 -0
  165. package/model/historical-curve/historical-curve.display-settings.d.ts +1 -0
  166. package/model/meter/meter.d.ts +4 -0
  167. package/model/numerical-display/numerical-display.d.ts +1 -0
  168. package/model/numerical-display/numerical-operation.d.ts +10 -2
  169. package/model/ring-graph/ring-graph.model.d.ts +8 -0
  170. package/model/scroll-alarm/scroll-alarm.model.d.ts +21 -0
  171. package/model/scroll-alarm/scroll-alarm.model.js +0 -0
  172. package/model/scroll-alarm/scroll-alarm.model.metadata.json +1 -0
  173. package/model/settings/popup/view-popup-location-type.d.ts +2 -1
  174. package/model/settings/popup/view-popup-location-type.js +1 -0
  175. package/model/settings/popup/view-popup-location-type.metadata.json +1 -1
  176. package/model/shared/state/state.d.ts +1 -0
  177. package/model/shared/state/state.js +1 -0
  178. package/model/shared/state/state.metadata.json +1 -1
  179. package/model/shared/text/font.d.ts +1 -1
  180. package/model/shared/text/text.d.ts +3 -0
  181. package/model/switch-indicator-light/switch-indicator-light.d.ts +3 -0
  182. package/model/view-operation/view-operation-element.model.d.ts +7 -1
  183. package/package.json +1 -1
  184. package/public_api.js +1 -0
  185. package/remote/communication/variable/remote-variable-communicator.d.ts +30 -0
  186. package/remote/communication/variable/remote-variable-communicator.js +193 -3
  187. package/remote/communication/variable/remote-variable-communicator.metadata.json +1 -1
  188. package/remote/communication/variable/remote-variable-protocol.d.ts +8 -0
  189. package/service/index.d.ts +4 -0
  190. package/service/index.js +1 -0
  191. package/service/index.metadata.json +1 -1
  192. package/service/language.service.d.ts +37 -0
  193. package/service/language.service.js +0 -0
  194. package/service/language.service.metadata.json +1 -0
  195. package/service/released-variable/index.d.ts +1 -0
  196. package/service/released-variable/index.js +0 -0
  197. package/service/released-variable/index.metadata.json +1 -0
  198. package/service/released-variable/released-variable.service.d.ts +4 -0
  199. package/service/released-variable/released-variable.service.js +0 -0
  200. package/service/released-variable/released-variable.service.metadata.json +1 -0
  201. package/service/system-text-library.service.d.ts +77 -0
  202. package/service/system-text-library.service.js +29 -0
  203. package/service/system-text-library.service.metadata.json +1 -0
  204. package/service/text-library.service.d.ts +49 -0
  205. package/service/text-library.service.js +0 -0
  206. package/service/text-library.service.metadata.json +1 -0
  207. package/shared/gui-consts.d.ts +2 -0
  208. package/shared/gui-consts.js +3 -1
  209. package/shared/gui-consts.metadata.json +1 -1
  210. package/utils/data-type/fbox-data-type.service.js +40 -0
  211. package/utils/numerical-operation.service.js +28 -4
  212. package/view/popup-view.service.d.ts +1 -1
@@ -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,183 @@ 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();
74
+ }
75
+ getIconScale() {
76
+ const initSize = this.model.displaySetting.initSize;
77
+ const size = this.model.displaySetting.size;
78
+ if (!initSize || !initSize.width || !initSize.height) {
79
+ return 1;
80
+ }
81
+ const scaleX = size.width / initSize.width;
82
+ const scaleY = size.height / initSize.height;
83
+ return Math.min(scaleX, scaleY);
51
84
  }
52
85
  dispose() {
53
86
  clearInterval(this.refreshIntervalId);
54
87
  if (this.chartElement) {
55
88
  this.chartElement.tooltip.hidden(true);
56
89
  }
90
+ if (this.resizeEventListener) {
91
+ this.resizeEventListener.clear();
92
+ }
93
+ if (this.isAndroid) {
94
+ window.removeEventListener('native.keyboardshow', this.setNeedResize);
95
+ window.removeEventListener('native.keyboardhide', this.setNeedResize);
96
+ }
97
+ // 清理下拉相关事件监听及 DOM
98
+ if (this._outsideClickHandler) {
99
+ document.removeEventListener('click', this._outsideClickHandler);
100
+ this._outsideClickHandler = null;
101
+ }
102
+ if (this._fullscreenChangeHandler) {
103
+ document.removeEventListener('fullscreenchange', this._fullscreenChangeHandler);
104
+ document.removeEventListener('webkitfullscreenchange', this._fullscreenChangeHandler);
105
+ this._fullscreenChangeHandler = null;
106
+ }
107
+ if (this._resizeHideHandler) {
108
+ window.removeEventListener('resize', this._resizeHideHandler);
109
+ this._resizeHideHandler = null;
110
+ }
111
+ if (this.dropdownListEl && this.dropdownListEl.parentNode) {
112
+ this.dropdownListEl.parentNode.removeChild(this.dropdownListEl);
113
+ this.dropdownListEl = null;
114
+ }
115
+ // 取消语种变化订阅
116
+ if (this.languageChangeSubscription) {
117
+ this.languageChangeSubscription.unsubscribe();
118
+ this.languageChangeSubscription = undefined;
119
+ }
57
120
  this.logger.debug(`[GUI]Dispose Histoical Curve Refresh Interval:${d3.time.format('%x %X')(new Date())}`);
58
121
  }
122
+ initKeyboardListener() {
123
+ this.isAndroid = !!navigator.userAgent.match(/(Android)/i);
124
+ if (this.isAndroid) {
125
+ window.addEventListener('native.keyboardshow', this.setNeedResize);
126
+ window.addEventListener('native.keyboardhide', this.setNeedResize);
127
+ }
128
+ }
129
+ /**
130
+ * 订阅语种变化事件
131
+ */
132
+ subscribeLanguageChange() {
133
+ var _a;
134
+ if (this.guiContext && this.guiContext.languageChanged$) {
135
+ this.languageChangeSubscription = this.guiContext.languageChanged$.subscribe(() => {
136
+ // 只更新时间段选择器的文案,不重新查询数据
137
+ this.updateLanguageTexts();
138
+ });
139
+ }
140
+ // 订阅文本库缓存更新事件,解决首次加载时文本库未就绪导致下拉选项为空的问题
141
+ if ((_a = this.systemTextLibraryService) === null || _a === void 0 ? void 0 : _a.cacheUpdated) {
142
+ const cacheSubscription = this.systemTextLibraryService.cacheUpdated.subscribe(() => {
143
+ this.updateLanguageTexts();
144
+ });
145
+ if (this.languageChangeSubscription) {
146
+ this.languageChangeSubscription.add(cacheSubscription);
147
+ }
148
+ else {
149
+ this.languageChangeSubscription = cacheSubscription;
150
+ }
151
+ }
152
+ }
153
+ /**
154
+ * 更新语种相关的文案(时间段选择器)
155
+ */
156
+ updateLanguageTexts() {
157
+ const updatedTimePeriods = this.getValidTimePeriods();
158
+ this.timePeriods = updatedTimePeriods;
159
+ // 更新下拉列表各选项文字
160
+ if (this.dropdownListEl) {
161
+ const items = this.dropdownListEl.querySelectorAll('.hc-dropdown-item');
162
+ items.forEach((item, i) => {
163
+ if (i < updatedTimePeriods.length) {
164
+ item.textContent = updatedTimePeriods[i].name;
165
+ }
166
+ });
167
+ }
168
+ // 更新触发按钮文字(显示当前选中项的最新语言文案)
169
+ if (this.dropdownTriggerEl) {
170
+ const selected = updatedTimePeriods.find(p => p.key === Number(this.currentTimePeriod));
171
+ if (selected) {
172
+ const textNode = Array.from(this.dropdownTriggerEl.childNodes).find(n => n.nodeType === Node.TEXT_NODE);
173
+ if (textNode) {
174
+ textNode.textContent = selected.name;
175
+ }
176
+ else {
177
+ this.dropdownTriggerEl.insertBefore(document.createTextNode(selected.name), this.dropdownTriggerEl.firstChild);
178
+ }
179
+ }
180
+ }
181
+ }
182
+ /**
183
+ * 获取当前语种的 culture 代码
184
+ */
185
+ getCurrentCulture() {
186
+ var _a, _b, _c, _d, _e, _f;
187
+ // 获取当前语种ID
188
+ 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;
189
+ // 确定要使用的语种代码(culture)
190
+ const defaultLanguage = ((_d = this.languageService) === null || _d === void 0 ? void 0 : _d.getDefaultLanguage()) || 'zh-CN';
191
+ if (currentLanguageId === null || currentLanguageId === undefined) {
192
+ // 设备未设置当前语种,使用默认语种
193
+ return defaultLanguage;
194
+ }
195
+ else {
196
+ // 设备已设置当前语种,获取对应的语种代码
197
+ const currentLanguage = (_f = (_e = this.guiContext) === null || _e === void 0 ? void 0 : _e.getLanguageCultureById) === null || _f === void 0 ? void 0 : _f.call(_e, currentLanguageId);
198
+ if (currentLanguage) {
199
+ return currentLanguage;
200
+ }
201
+ else {
202
+ // 无法获取语种代码,使用默认语种
203
+ return defaultLanguage;
204
+ }
205
+ }
206
+ }
59
207
  getValidTimePeriods() {
60
208
  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 });
209
+ timePeriods.push({ key: 6, name: this.getTimePeriodText(TIME_PERIOD_KEYS.LAST_THIRTY_MINUTES) });
210
+ timePeriods.push({ key: 1, name: this.getTimePeriodText(TIME_PERIOD_KEYS.LAST_ONE_HOUR) });
211
+ timePeriods.push({ key: 7, name: this.getTimePeriodText(TIME_PERIOD_KEYS.LAST_EIGHT_HOURS) });
212
+ timePeriods.push({ key: 2, name: this.getTimePeriodText(TIME_PERIOD_KEYS.LAST_TWENTY_FOUR_HOURS) });
213
+ timePeriods.push({ key: 3, name: this.getTimePeriodText(TIME_PERIOD_KEYS.LAST_SEVEN_DAYS) });
214
+ timePeriods.push({ key: 4, name: this.getTimePeriodText(TIME_PERIOD_KEYS.LAST_THIRTY_DAYS) });
215
+ timePeriods.push({ key: 5, name: this.getTimePeriodText(TIME_PERIOD_KEYS.LAST_ONE_YEAR) });
216
+ timePeriods.push({ key: 8, name: this.getTimePeriodText(TIME_PERIOD_KEYS.CUSTOM) });
66
217
  return timePeriods;
67
218
  }
219
+ /**
220
+ * 获取时间段文案(从系统文本库获取多语种翻译)
221
+ */
222
+ getTimePeriodText(textKey) {
223
+ const currentCulture = this.getCurrentCulture();
224
+ const systemType = SYSTEM_TEXT_LIBRARY_TYPES.COMPONENT_BUILTIN;
225
+ if (this.systemTextLibraryService) {
226
+ const translation = this.systemTextLibraryService.getSystemTextValue(systemType, textKey, currentCulture);
227
+ if (translation) {
228
+ return translation;
229
+ }
230
+ }
231
+ return '';
232
+ }
68
233
  updateTimeRange(timePeriodType) {
69
234
  this.currentTimePeriod = +timePeriodType;
70
235
  this.updateQueryTimeRange();
71
236
  }
72
237
  updateQueryTimeRange() {
238
+ if (this.currentTimePeriod === 8) {
239
+ if (this.customStartTime && this.customEndTime) {
240
+ this.startTime = this.customStartTime.clone();
241
+ this.endTime = this.customEndTime.clone();
242
+ }
243
+ return;
244
+ }
73
245
  this.endTime = moment();
74
246
  switch (this.currentTimePeriod) {
75
247
  case 1:
@@ -84,13 +256,29 @@ export class HistoricalCurveElement extends ConditionalDisplayElement {
84
256
  case 5:
85
257
  this.startTime = moment().subtract(1, 'years');
86
258
  break;
259
+ case 6:
260
+ this.startTime = moment().subtract(30, 'minutes');
261
+ break;
262
+ case 7:
263
+ this.startTime = moment().subtract(8, 'hours');
264
+ break;
87
265
  default:
88
266
  this.startTime = moment().subtract(1, 'days');
89
267
  }
90
268
  }
91
269
  reRenderElement(startTime, endTime, limit, rangeType) {
270
+ // 清理图表实例
271
+ if (this.chartElement) {
272
+ // 隐藏 tooltip
273
+ if (this.chartElement.tooltip) {
274
+ this.chartElement.tooltip.hidden(true);
275
+ }
276
+ // 清理图表引用
277
+ this.chartElement = null;
278
+ }
279
+ // 清除 DOM 元素
92
280
  this.rootElement.selectAll('*').remove();
93
- this.chartElement = null;
281
+ // 重新渲染
94
282
  this.renderElement(startTime, endTime, limit, rangeType);
95
283
  }
96
284
  renderElement(startTime, endTime, limit, rangeType) {
@@ -122,28 +310,103 @@ export class HistoricalCurveElement extends ConditionalDisplayElement {
122
310
  }
123
311
  });
124
312
  }
313
+ setupTooltipAutoHide(chart) {
314
+ const chartContainer = this.rootElement.select('.nv-focus').node();
315
+ if (!chartContainer || !chart)
316
+ return;
317
+ let timeoutId;
318
+ // 鼠标移入图表时显示 tooltip
319
+ chartContainer.addEventListener('mouseover', () => {
320
+ hideTooltipAfterDelay();
321
+ });
322
+ const clearTooltipTimeout = () => {
323
+ if (timeoutId) {
324
+ clearTimeout(timeoutId);
325
+ timeoutId = null;
326
+ }
327
+ };
328
+ const hideTooltipAfterDelay = () => {
329
+ clearTooltipTimeout();
330
+ timeoutId = setTimeout(() => {
331
+ chart.tooltip.hidden(true);
332
+ }, 2000); // 2秒延迟
333
+ };
334
+ }
125
335
  renderChart(result) {
126
336
  const chartWidth = this.model.displaySetting.size.width;
127
- const chartHeight = this.model.displaySetting.size.height - this.displayOption.operationAreaHeight - this.displayOption.operationAreaMarginTop;
337
+ const scale = this.getIconScale();
338
+ // 图表主体与底部操作区之间的预留高度,需要与操作区渲染时使用的同一个 scale 保持一致,
339
+ // 否则图表按未缩放高度占满后,缩放后的操作区会超出元件整体高度
340
+ const chartHeight = this.model.displaySetting.size.height - (this.displayOption.operationAreaHeight + this.displayOption.operationAreaMarginTop) * scale;
128
341
  const data = new Array();
129
342
  each(this.model.dataSetting.channels, (channel, key) => {
130
343
  const values = new Array();
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, scale);
351
+ }
352
+ else {
353
+ chart = this.getLineChart(chartWidth, chartHeight, data, scale);
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
- getLineChart(chartWidth, chartHeight, data) {
403
+ getLineChart(chartWidth, chartHeight, data, scale = 1) {
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 });
@@ -151,10 +414,23 @@ export class HistoricalCurveElement extends ConditionalDisplayElement {
151
414
  chart.focus.showXAxis(false);
152
415
  }
153
416
  this.renderCommonProperty(chart, chartWidth, chartHeight, data);
154
- this.renderOperationArea(chartWidth, chartHeight);
417
+ this.renderOperationArea(chartWidth, chartHeight, scale);
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
- getMultiBarWithFocusChart(chartWidth, chartHeight, data) {
433
+ getMultiBarWithFocusChart(chartWidth, chartHeight, data, scale = 1) {
158
434
  const chart = nv.models.multiBarWithFocusChart()
159
435
  .margin({
160
436
  top: 0, bottom: 0, left: this.displayOption.marginLeft, right: this.displayOption.marginRight
@@ -168,13 +444,16 @@ 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);
174
453
  }
175
454
  chart.xAxis.tickFormat(d => this.timeFormat(d, '%x %X'));
176
455
  this.renderCommonProperty(chart, chartWidth, chartHeight, data);
177
- this.renderOperationArea(chartWidth, chartHeight);
456
+ this.renderOperationArea(chartWidth, chartHeight, scale);
178
457
  const focusContext = this.rootElement.select('.nv-context');
179
458
  focusContext.select('.nv-barsWrap.nvd3-svg').attr('transform', 'translate(0,30)');
180
459
  focusContext.select('.nv-brushBackground').attr('transform', 'translate(0,30)');
@@ -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,417 @@ 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
- renderOperationArea(chartWidth, chartHeight) {
229
- const operationArea = this.rootElement.append('g').attr('transform', `translate(0,${chartHeight + this.displayOption.operationAreaMarginTop})`)
230
- .append('foreignObject').attr('width', chartWidth).attr('height', this.displayOption.operationAreaHeight).attr('fill', 'none')
231
- .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);
617
+ renderOperationArea(chartWidth, chartHeight, scale = 1) {
618
+ const backgroundColor = this.model.displaySetting.axisSetting.filterBackgroudColor || 'inherit';
619
+ const operationArea = this.rootElement.append('g').attr('transform', `translate(0,${chartHeight + this.displayOption.operationAreaMarginTop * scale})`)
620
+ .append('foreignObject').attr('width', chartWidth).attr('height', this.displayOption.operationAreaHeight * scale).attr('fill', 'none')
621
+ .append('xhtml:div').style('height', (this.displayOption.operationAreaHeight * scale - 4) + 'px').style('overflow', 'hidden').style('margin-top', '4px');
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 * scale) + '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', (parseFloat(this.displayOption.operationSelectFontSize) * scale) + 'px')
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 * scale - 8) + 'px')
646
+ .style('min-height', (this.displayOption.operationAreaHeight * scale - 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
+ // 字号/行高在打开时动态根据 trigger 的视口实际尺寸设置,见 dropdownTrigger.on('click') 处理
662
+ .style('background', tp.key === Number(this.currentTimePeriod) ? '#1890ff' : 'transparent')
663
+ .style('color', tp.key === Number(this.currentTimePeriod) ? '#fff' : '#333')
664
+ .text(tp.name)
665
+ .on('mouseover', function () {
666
+ const el = d3.select(this);
667
+ if (!el.classed('hc-dropdown-selected')) {
668
+ el.style('background', '#e6f7ff').style('color', '#333');
669
+ }
670
+ })
671
+ .on('mouseout', function () {
672
+ const el = d3.select(this);
673
+ if (!el.classed('hc-dropdown-selected')) {
674
+ el.style('background', 'transparent').style('color', '#333');
675
+ }
676
+ })
677
+ .on('click', () => {
678
+ dropdownListEl.style.display = 'none';
679
+ // 从 this.timePeriods 实时查找,确保语种切换后显示最新文案
680
+ const currentPeriod = this.timePeriods.find(p => p.key === tp.key);
681
+ const currentName = currentPeriod ? currentPeriod.name : tp.name;
682
+ dropdownTrigger.text(currentName);
683
+ // 更新选中状态
684
+ dropdownList.selectAll('.hc-dropdown-item')
685
+ .classed('hc-dropdown-selected', false)
686
+ .style('background', 'transparent')
687
+ .style('color', '#333');
688
+ d3.select(d3.event.currentTarget)
689
+ .classed('hc-dropdown-selected', true)
690
+ .style('background', '#1890ff')
691
+ .style('color', '#fff');
692
+ if (tp.key === 8) {
693
+ const isSimulation = !this.releasedVariableService;
694
+ if (!isSimulation) {
695
+ this.showCustomTimeRangeModal();
696
+ }
697
+ return;
698
+ }
699
+ this.updateTimeRange(tp.key);
700
+ this.reRenderElement(this.startTime, this.endTime, this.displayOption.dataLimit, HistoricalCurveTimeRange.BeginOpenEndOpen);
701
+ });
237
702
  });
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));
240
- const buttonWidth = this.displayOption.operationButtonWidth + 'px', buttonHeight = this.displayOption.operationButtonHeight + 'px';
703
+ const hideDropdown = () => { dropdownListEl.style.display = 'none'; };
704
+ dropdownTrigger.on('click', () => {
705
+ const isVisible = dropdownListEl.style.display !== 'none';
706
+ if (isVisible) {
707
+ hideDropdown();
708
+ }
709
+ else {
710
+ const triggerNode = dropdownTrigger.node();
711
+ const triggerRect = triggerNode.getBoundingClientRect();
712
+ // trigger 在视口中的实际高度(已经过画布 transform 缩放),用于对齐下拉项尺寸。
713
+ // 不能在渲染时写死设计像素,因为下拉挂在 body 上不受画布 transform 影响。
714
+ const itemHeight = triggerRect.height;
715
+ // 字号取行高的 60%,保证文字在行内垂直居中且不溢出
716
+ const itemFontSize = Math.round(itemHeight * 0.6);
717
+ dropdownList.selectAll('.hc-dropdown-item')
718
+ .style('font-size', itemFontSize + 'px')
719
+ .style('line-height', itemHeight + 'px')
720
+ .style('min-height', itemHeight + 'px');
721
+ // 设置宽度至少和 trigger 一样宽,先展示再测宽度
722
+ dropdownListEl.style.display = 'block';
723
+ dropdownListEl.style.minWidth = triggerRect.width + 'px';
724
+ dropdownListEl.style.left = triggerRect.left + 'px';
725
+ const listHeight = dropdownListEl.offsetHeight;
726
+ const spaceBelow = window.innerHeight - triggerRect.bottom;
727
+ if (spaceBelow < listHeight) {
728
+ // 向上展开
729
+ dropdownListEl.style.top = '';
730
+ dropdownListEl.style.bottom = (window.innerHeight - triggerRect.top) + 'px';
731
+ }
732
+ else {
733
+ // 向下展开
734
+ dropdownListEl.style.bottom = '';
735
+ dropdownListEl.style.top = triggerRect.bottom + 'px';
736
+ }
737
+ }
738
+ });
739
+ // 点击外部收起
740
+ this._outsideClickHandler = (event) => {
741
+ const triggerNode = dropdownTrigger.node();
742
+ if (!dropdownListEl.contains(event.target) && !triggerNode.contains(event.target)) {
743
+ hideDropdown();
744
+ }
745
+ };
746
+ document.addEventListener('click', this._outsideClickHandler);
747
+ // 全屏切换(含点击全屏按钮和 F11)时收起
748
+ this._fullscreenChangeHandler = () => { hideDropdown(); };
749
+ document.addEventListener('fullscreenchange', this._fullscreenChangeHandler);
750
+ document.addEventListener('webkitfullscreenchange', this._fullscreenChangeHandler);
751
+ // F11 退出全屏时 keydown 被浏览器拦截,用 resize 兜底
752
+ this._resizeHideHandler = () => { hideDropdown(); };
753
+ window.addEventListener('resize', this._resizeHideHandler);
754
+ // 滚动时收起
755
+ document.addEventListener('scroll', hideDropdown, true);
756
+ const buttonWidth = (this.displayOption.operationButtonWidth * scale) + 'px', buttonHeight = (this.displayOption.operationButtonHeight * scale) + 'px';
241
757
  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)')
758
+ .style('float', 'right').style('background-image', 'url(assets/img/black_last_page.png)')
759
+ .style('background-color', backgroundColor).style('background-size', 'cover')
243
760
  .on('click', () => { this.loadLastPage(); });
244
761
  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)')
246
- .style('margin', `0 ${this.displayOption.operationButtonMargin}px 0 0`)
762
+ .style('float', 'right').style('background-image', 'url(assets/img/black_next_page.png)')
763
+ .style('margin', `0 ${this.displayOption.operationButtonMargin * scale}px 0 0`)
764
+ .style('background-color', backgroundColor).style('background-size', 'cover')
247
765
  .on('click', () => { this.loadNextPage(); });
248
766
  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)')
250
- .style('margin', `0 ${this.displayOption.operationButtonMargin}px 0 0`)
767
+ .style('float', 'right').style('background-image', 'url(assets/img/black_previous_page.png)')
768
+ .style('margin', `0 ${this.displayOption.operationButtonMargin * scale}px 0 0`)
769
+ .style('background-color', backgroundColor).style('background-size', 'cover')
251
770
  .on('click', () => { this.loadPreviousPage(); });
252
771
  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)')
254
- .style('margin', `0 ${this.displayOption.operationButtonMargin}px 0 0`)
772
+ .style('float', 'right').style('background-image', 'url(assets/img/black_first_page.png)')
773
+ .style('margin', `0 ${this.displayOption.operationButtonMargin * scale}px 0 0`)
774
+ .style('background-color', backgroundColor).style('background-size', 'cover')
255
775
  .on('click', () => { this.loadFirstPage(); });
256
776
  }
257
777
  timeFormat(datetime, specifier) {
258
778
  return d3.time.format(specifier)(new Date(datetime));
259
779
  }
780
+ fmtDatetimeLocal(d) {
781
+ const p2 = (n) => (n < 10 ? '0' : '') + n;
782
+ return d.getFullYear() + '-' + p2(d.getMonth() + 1) + '-' + p2(d.getDate())
783
+ + 'T' + p2(d.getHours()) + ':' + p2(d.getMinutes()) + ':' + p2(d.getSeconds());
784
+ }
785
+ showCustomTimeRangeModal() {
786
+ const now = new Date();
787
+ const defaultStart = this.customStartTime ? this.customStartTime.toDate()
788
+ : new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
789
+ const defaultEnd = this.customEndTime ? this.customEndTime.toDate()
790
+ : new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59);
791
+ if (this.guiContext && this.guiContext.showCustomTimeRangeModal) {
792
+ this.guiContext.showCustomTimeRangeModal({ startTime: defaultStart, endTime: defaultEnd }).then(result => {
793
+ if (result) {
794
+ const momentAny = moment;
795
+ this.customStartTime = momentAny(result.startTime);
796
+ this.customEndTime = momentAny(result.endTime);
797
+ this.updateTimeRange(8);
798
+ this.reRenderElement(this.startTime, this.endTime, this.displayOption.dataLimit, HistoricalCurveTimeRange.BeginOpenEndOpen);
799
+ }
800
+ });
801
+ return;
802
+ }
803
+ this.showCustomTimeRangeModalFallback();
804
+ }
805
+ showCustomTimeRangeModalFallback() {
806
+ const prevTimePeriod = this.currentTimePeriod;
807
+ const now = new Date();
808
+ const defaultStart = this.fmtDatetimeLocal(this.customStartTime ? this.customStartTime.toDate()
809
+ : new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0));
810
+ const defaultEnd = this.fmtDatetimeLocal(this.customEndTime ? this.customEndTime.toDate()
811
+ : new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59));
812
+ // 临时将 <html lang> 切换为当前应用语言,使原生 datetime-local
813
+ // 弹出日历的 UI(星期/月份文字等)跟随应用语种显示,关闭时还原
814
+ const currentLang = this.getCurrentCulture();
815
+ const prevDocLang = document.documentElement.lang;
816
+ document.documentElement.lang = currentLang;
817
+ const overlay = document.createElement('div');
818
+ overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:9999;';
819
+ // 部分浏览器可直接读取元素上的 lang 属性来本地化原生控件
820
+ overlay.lang = currentLang;
821
+ const modal = document.createElement('div');
822
+ let modalCss = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);'
823
+ + 'background:#fff;border-radius:4px;';
824
+ if (this.isMobileMode) {
825
+ modalCss += 'width:90%;';
826
+ }
827
+ else {
828
+ modalCss += 'min-width:460px;';
829
+ }
830
+ modalCss += 'box-shadow:0 4px 12px rgba(0,0,0,0.15);font-family:inherit;font-size:14px;';
831
+ modal.style.cssText = modalCss;
832
+ const header = document.createElement('div');
833
+ header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid #e8e8e8;';
834
+ const title = document.createElement('span');
835
+ title.style.cssText = 'font-size:16px;font-weight:500;color:#333;';
836
+ title.textContent = this.localization.customTimeRange;
837
+ const closeBtn = document.createElement('span');
838
+ closeBtn.style.cssText = 'cursor:pointer;font-size:18px;color:#999;line-height:1;';
839
+ closeBtn.textContent = '×';
840
+ header.appendChild(title);
841
+ header.appendChild(closeBtn);
842
+ const body = document.createElement('div');
843
+ body.style.cssText = 'padding:20px 16px;display:flex;align-items:center;flex-wrap:wrap;gap:8px;';
844
+ const label = document.createElement('span');
845
+ label.style.cssText = 'color:#333;white-space:nowrap;';
846
+ label.textContent = this.localization.timeRange + ':';
847
+ const inputStyle = 'border:1px solid #d9d9d9;border-radius:4px;padding:4px 8px;font-size:14px;height:32px;outline:none;color:#333;';
848
+ const startInput = document.createElement('input');
849
+ startInput.type = 'datetime-local';
850
+ startInput.setAttribute('step', '1');
851
+ startInput.setAttribute('lang', currentLang);
852
+ startInput.value = defaultStart;
853
+ startInput.style.cssText = inputStyle;
854
+ const separator = document.createElement('span');
855
+ separator.textContent = ' - ';
856
+ separator.style.cssText = 'color:#333;';
857
+ const endInput = document.createElement('input');
858
+ endInput.type = 'datetime-local';
859
+ endInput.setAttribute('step', '1');
860
+ endInput.setAttribute('lang', currentLang);
861
+ endInput.value = defaultEnd;
862
+ endInput.style.cssText = inputStyle;
863
+ body.appendChild(label);
864
+ body.appendChild(startInput);
865
+ body.appendChild(separator);
866
+ body.appendChild(endInput);
867
+ const footer = document.createElement('div');
868
+ footer.style.cssText = 'display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid #e8e8e8;';
869
+ const cancelBtn = document.createElement('button');
870
+ cancelBtn.textContent = this.localization.cancel;
871
+ cancelBtn.style.cssText = 'padding:5px 16px;border:1px solid #d9d9d9;border-radius:4px;background:#fff;cursor:pointer;font-size:14px;color:#333;';
872
+ const saveBtn = document.createElement('button');
873
+ saveBtn.textContent = this.localization.submit;
874
+ saveBtn.style.cssText = 'padding:5px 16px;border:none;border-radius:4px;background:#1890ff;color:#fff;cursor:pointer;font-size:14px;';
875
+ footer.appendChild(cancelBtn);
876
+ footer.appendChild(saveBtn);
877
+ modal.appendChild(header);
878
+ modal.appendChild(body);
879
+ modal.appendChild(footer);
880
+ overlay.appendChild(modal);
881
+ document.body.appendChild(overlay);
882
+ const close = (revert) => {
883
+ // 还原弹窗打开前的 <html lang>,避免影响页面其他部分
884
+ document.documentElement.lang = prevDocLang;
885
+ document.body.removeChild(overlay);
886
+ if (revert) {
887
+ const selectEl = this.$element && this.$element.find('select');
888
+ if (selectEl && selectEl.length) {
889
+ selectEl.val(prevTimePeriod.toString());
890
+ }
891
+ }
892
+ };
893
+ closeBtn.addEventListener('click', () => close(true));
894
+ cancelBtn.addEventListener('click', () => close(true));
895
+ saveBtn.addEventListener('click', () => {
896
+ const startVal = startInput.value;
897
+ const endVal = endInput.value;
898
+ if (!startVal) {
899
+ startInput.style.borderColor = 'red';
900
+ return;
901
+ }
902
+ const startDate = new Date(startVal);
903
+ const endDate = endVal
904
+ ? new Date(endVal)
905
+ : new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate(), 23, 59, 59);
906
+ if (isNaN(startDate.getTime())) {
907
+ startInput.style.borderColor = 'red';
908
+ return;
909
+ }
910
+ if (endDate.getTime() <= startDate.getTime()) {
911
+ endInput.style.borderColor = 'red';
912
+ return;
913
+ }
914
+ const momentAny = moment;
915
+ this.customStartTime = momentAny(startDate);
916
+ this.customEndTime = momentAny(endDate);
917
+ close(false);
918
+ this.updateTimeRange(8);
919
+ this.reRenderElement(this.startTime, this.endTime, this.displayOption.dataLimit, HistoricalCurveTimeRange.BeginOpenEndOpen);
920
+ });
921
+ }
260
922
  loadFirstPage() {
261
923
  this.updateQueryTimeRange();
262
924
  this.reRenderElement(this.startTime, this.endTime, this.displayOption.dataLimit, HistoricalCurveTimeRange.BeginOpenEndOpen);