@flexem/fc-gui 3.0.0-alpha.17 → 3.0.0-alpha.171

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