Showing
33 changed files
with
1714 additions
and
235 deletions
1 | +import cloneDeep from 'lodash-es/cloneDeep'; | ||
2 | +import { DeviceAlarmConfig } from '.'; | ||
3 | +import { | ||
4 | + ConfigType, | ||
5 | + CreateComponentType, | ||
6 | + PublicComponentOptions, | ||
7 | + PublicPresetOptions, | ||
8 | +} from '/@/views/visual/packages/index.type'; | ||
9 | +import { PublicConfigClass, componentInitConfig } from '/@/views/visual/packages/publicConfig'; | ||
10 | +import { ComponentConfigFieldEnum } from '/@/views/visual/packages/enum'; | ||
11 | + | ||
12 | +export const option: PublicPresetOptions = { | ||
13 | + multipleDataSourceComponent: true, | ||
14 | + // componetDesign: false, | ||
15 | + [ComponentConfigFieldEnum.OPEN_COLOR]: '#00F43D', | ||
16 | + [ComponentConfigFieldEnum.CLOSE_COLOR]: '#FF0000', | ||
17 | + [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: true, | ||
18 | +}; | ||
19 | + | ||
20 | +export default class Config extends PublicConfigClass implements CreateComponentType { | ||
21 | + public key: string = DeviceAlarmConfig.key; | ||
22 | + | ||
23 | + public attr = { ...componentInitConfig }; | ||
24 | + | ||
25 | + public componentConfig: ConfigType = cloneDeep(DeviceAlarmConfig); | ||
26 | + | ||
27 | + public persetOption = cloneDeep(option); | ||
28 | + | ||
29 | + public option: PublicComponentOptions; | ||
30 | + | ||
31 | + constructor(option: PublicComponentOptions) { | ||
32 | + super(); | ||
33 | + this.option = { ...option }; | ||
34 | + } | ||
35 | +} |
1 | +<script lang="ts" setup> | ||
2 | + import { ComponentConfigFieldEnum } from '/@/views/visual/packages/enum'; | ||
3 | + import { useForm, BasicForm } from '/@/components/Form'; | ||
4 | + import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type'; | ||
5 | + import { option } from './config'; | ||
6 | + | ||
7 | + const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({ | ||
8 | + schemas: [ | ||
9 | + // { | ||
10 | + // field: ComponentConfigFieldEnum.OPEN_COLOR, | ||
11 | + // label: '设备正常颜色', | ||
12 | + // component: 'ColorPicker', | ||
13 | + // changeEvent: 'update:value', | ||
14 | + // componentProps: { | ||
15 | + // defaultValue: option.openColor, | ||
16 | + // }, | ||
17 | + // }, | ||
18 | + // { | ||
19 | + // field: ComponentConfigFieldEnum.CLOSE_COLOR, | ||
20 | + // label: '设备告警颜色', | ||
21 | + // component: 'ColorPicker', | ||
22 | + // changeEvent: 'update:value', | ||
23 | + // componentProps: { | ||
24 | + // defaultValue: option.closeColor, | ||
25 | + // }, | ||
26 | + // }, | ||
27 | + { | ||
28 | + field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME, | ||
29 | + label: '显示设备名称', | ||
30 | + component: 'Checkbox', | ||
31 | + defaultValue: option.showDeviceName, | ||
32 | + }, | ||
33 | + ], | ||
34 | + showActionButtonGroup: false, | ||
35 | + labelWidth: 120, | ||
36 | + baseColProps: { | ||
37 | + span: 12, | ||
38 | + }, | ||
39 | + }); | ||
40 | + | ||
41 | + const getFormValues = () => { | ||
42 | + return getFieldsValue(); | ||
43 | + }; | ||
44 | + | ||
45 | + const setFormValues = (data: Recordable) => { | ||
46 | + return setFieldsValue(data); | ||
47 | + }; | ||
48 | + | ||
49 | + defineExpose({ | ||
50 | + getFormValues, | ||
51 | + setFormValues, | ||
52 | + resetFormValues: resetFields, | ||
53 | + } as PublicFormInstaceType); | ||
54 | +</script> | ||
55 | + | ||
56 | +<template> | ||
57 | + <BasicForm @register="register" /> | ||
58 | +</template> |
1 | +<script lang="ts" setup> | ||
2 | + // import { commonDataSourceSchemas } from '../../../config/common.config'; | ||
3 | + import { BasicForm, useForm } from '/@/components/Form'; | ||
4 | + import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type'; | ||
5 | + import { formSchemas } from '../DeviceAlarmHistory/datasource.config'; | ||
6 | + | ||
7 | + const [register, { getFieldsValue, setFieldsValue, validate, resetFields }] = useForm({ | ||
8 | + labelWidth: 0, | ||
9 | + showActionButtonGroup: false, | ||
10 | + layout: 'horizontal', | ||
11 | + labelCol: { span: 0 }, | ||
12 | + schemas: formSchemas(), | ||
13 | + }); | ||
14 | + | ||
15 | + const getFormValues = () => { | ||
16 | + return getFieldsValue(); | ||
17 | + }; | ||
18 | + | ||
19 | + const setFormValues = (record: Recordable) => { | ||
20 | + return setFieldsValue(record); | ||
21 | + }; | ||
22 | + | ||
23 | + defineExpose({ | ||
24 | + getFormValues, | ||
25 | + setFormValues, | ||
26 | + validate, | ||
27 | + resetFormValues: resetFields, | ||
28 | + } as PublicFormInstaceType); | ||
29 | +</script> | ||
30 | + | ||
31 | +<template> | ||
32 | + <BasicForm @register="register" /> | ||
33 | +</template> |
1 | +// import { ComponentEnum, ComponentNameEnum } from '../index.type'; | ||
2 | +import { useComponentKeys } from '/@/views/visual/packages/hook/useComponentKeys'; | ||
3 | +import { ConfigType, PackagesCategoryEnum } from '/@/views/visual/packages/index.type'; | ||
4 | + | ||
5 | +const componentKeys = useComponentKeys('DeviceAlarm'); | ||
6 | +export const DeviceAlarmConfig: ConfigType = { | ||
7 | + ...componentKeys, | ||
8 | + title: '设备告警', | ||
9 | + package: PackagesCategoryEnum.ALARM, | ||
10 | +}; |
1 | +<script lang="ts" setup> | ||
2 | + import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type'; | ||
3 | + import { option } from './config'; | ||
4 | + import { useComponentScale } from '/@/views/visual/packages/hook/useComponentScale'; | ||
5 | + import { computed, watch } from 'vue'; | ||
6 | + import { ref, onMounted, unref } from 'vue'; | ||
7 | + import { useIntervalFn } from '@vueuse/core'; | ||
8 | + import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime'; | ||
9 | + import { useCustomDataFetch } from '../../../hook/socket/useSocket'; | ||
10 | + import { ReceiveAlarmDataCmdsMessageType } from '../../../hook/socket/useSocket.type'; | ||
11 | + import { useDataBoardContext } from '/@/views/visual/palette/hooks/useDataBoardContext'; | ||
12 | + import { useAlarmContext } from '/@/views/visual/palette/hooks/useAlarmTime'; | ||
13 | + | ||
14 | + interface IStatus { | ||
15 | + text: string; | ||
16 | + color: string; | ||
17 | + } | ||
18 | + | ||
19 | + interface IAlarmStatusList { | ||
20 | + id: string; | ||
21 | + deviceName: string; | ||
22 | + showDeviceName: boolean; | ||
23 | + status: IStatus; | ||
24 | + time: number; | ||
25 | + } | ||
26 | + | ||
27 | + const props = defineProps<{ | ||
28 | + config: ComponentPropsConfigType<typeof option>; | ||
29 | + }>(); | ||
30 | + | ||
31 | + const isOpenClose = ref<boolean>(true); | ||
32 | + | ||
33 | + const { send } = useDataBoardContext(); | ||
34 | + | ||
35 | + const currentCmdId = ref(); | ||
36 | + | ||
37 | + const alarmLevel = (type: string): IStatus => { | ||
38 | + if (type === 'CRITICAL') { | ||
39 | + return { text: '紧急', color: 'alarm_state_critical' }; | ||
40 | + } else if (type === 'MAJOR') { | ||
41 | + return { text: '重要', color: 'alarm_state_major' }; | ||
42 | + } else if (type === 'MINOR') { | ||
43 | + return { text: '次要', color: 'alarm_state_minor' }; | ||
44 | + } else if (type === 'WARNING') { | ||
45 | + return { text: '警告', color: 'alarm_state_warning' }; | ||
46 | + } else { | ||
47 | + return { text: '不确定', color: 'alarm_state_other' }; | ||
48 | + } | ||
49 | + }; | ||
50 | + | ||
51 | + const getDesign = computed(() => { | ||
52 | + const { persetOption = {}, option } = props.config; | ||
53 | + const { dataSource } = option; | ||
54 | + const { showDeviceName: presetShowDeviceName } = persetOption; | ||
55 | + | ||
56 | + return { | ||
57 | + dataSource: dataSource?.map((item) => { | ||
58 | + const { deviceId, deviceName, deviceRename, componentInfo } = item; | ||
59 | + return { | ||
60 | + id: deviceId, | ||
61 | + deviceName: deviceRename || deviceName, | ||
62 | + showDeviceName: componentInfo.showDeviceName ?? presetShowDeviceName, | ||
63 | + }; | ||
64 | + }), | ||
65 | + }; | ||
66 | + }); | ||
67 | + | ||
68 | + const randomFn = () => { | ||
69 | + useIntervalFn(() => { | ||
70 | + isOpenClose.value = !unref(isOpenClose); | ||
71 | + }, 4000); | ||
72 | + }; | ||
73 | + | ||
74 | + // 发送websocket的格式 | ||
75 | + const getMessage = (cmdId: number) => { | ||
76 | + const message = { | ||
77 | + alarmDataCmds: [ | ||
78 | + { | ||
79 | + query: { | ||
80 | + entityFilter: { | ||
81 | + type: 'entityList', | ||
82 | + resolveMultiple: true, | ||
83 | + entityType: 'DEVICE', | ||
84 | + entityList: unref(getDesign).dataSource?.map((item) => item.id), | ||
85 | + }, | ||
86 | + pageLink: { | ||
87 | + page: 0, | ||
88 | + pageSize: unref(alarmForm)?.pageSize, | ||
89 | + textSearch: null, | ||
90 | + searchPropagatedAlarms: false, | ||
91 | + statusList: [], | ||
92 | + severityList: [], | ||
93 | + typeList: [], | ||
94 | + sortOrder: { | ||
95 | + key: { | ||
96 | + key: 'createdTime', | ||
97 | + type: 'ALARM_FIELD', | ||
98 | + }, | ||
99 | + direction: 'DESC', | ||
100 | + }, | ||
101 | + timeWindow: unref(alarmForm)?.time, | ||
102 | + }, | ||
103 | + alarmFields: [ | ||
104 | + { | ||
105 | + type: 'ALARM_FIELD', | ||
106 | + key: 'createdTime', | ||
107 | + }, | ||
108 | + { | ||
109 | + type: 'ALARM_FIELD', | ||
110 | + key: 'originator', | ||
111 | + }, | ||
112 | + { | ||
113 | + type: 'ALARM_FIELD', | ||
114 | + key: 'type', | ||
115 | + }, | ||
116 | + { | ||
117 | + type: 'ALARM_FIELD', | ||
118 | + key: 'severity', | ||
119 | + }, | ||
120 | + { | ||
121 | + type: 'ALARM_FIELD', | ||
122 | + key: 'status', | ||
123 | + }, | ||
124 | + ], | ||
125 | + entityFields: [], | ||
126 | + latestValues: [], | ||
127 | + }, | ||
128 | + cmdId, | ||
129 | + }, | ||
130 | + ], | ||
131 | + }; | ||
132 | + return message; | ||
133 | + }; | ||
134 | + | ||
135 | + const alarmStatusList = ref<IAlarmStatusList[]>([ | ||
136 | + { | ||
137 | + id: '1', | ||
138 | + deviceName: '设备', | ||
139 | + status: { text: '紧急', color: 'alarm_state_major' }, | ||
140 | + time: 1689574726, | ||
141 | + showDeviceName: true, | ||
142 | + }, | ||
143 | + ]); | ||
144 | + | ||
145 | + const { alarmForm } = useAlarmContext(); | ||
146 | + | ||
147 | + watch( | ||
148 | + () => alarmForm?.value, | ||
149 | + () => { | ||
150 | + send?.(JSON.stringify(getMessage(unref(currentCmdId)))); | ||
151 | + }, | ||
152 | + { immediate: false } | ||
153 | + ); | ||
154 | + | ||
155 | + const transformMessage = (cmdId: number) => { | ||
156 | + currentCmdId.value = cmdId; | ||
157 | + send?.(JSON.stringify(getMessage(cmdId))); | ||
158 | + }; | ||
159 | + | ||
160 | + const getReduce = (data) => { | ||
161 | + const list = data.reduce((acc, obj) => { | ||
162 | + const found = acc.find((item) => item.entityId.id == obj.entityId.id); | ||
163 | + if (!found) { | ||
164 | + acc.push(obj); | ||
165 | + } | ||
166 | + return acc; | ||
167 | + }, []); | ||
168 | + | ||
169 | + data.forEach((item) => { | ||
170 | + list?.forEach((item1) => { | ||
171 | + if (item.entityId.id == item1.entityId.id) { | ||
172 | + item1.time = Number(item1.createdTime > item.createdTime) | ||
173 | + ? item1.createdTime | ||
174 | + : item.createdTime; | ||
175 | + } | ||
176 | + }); | ||
177 | + }); | ||
178 | + | ||
179 | + return list; | ||
180 | + }; | ||
181 | + | ||
182 | + const updateFn = (message: ReceiveAlarmDataCmdsMessageType) => { | ||
183 | + alarmStatusList.value = unref(getDesign).dataSource?.map((item) => { | ||
184 | + return { | ||
185 | + id: item.id, | ||
186 | + deviceName: item.deviceName, | ||
187 | + showDeviceName: item.showDeviceName, | ||
188 | + status: { text: '', color: '' }, | ||
189 | + time: 0, | ||
190 | + }; | ||
191 | + }) as any; | ||
192 | + const { data } = message; | ||
193 | + const alarmList = data?.data; | ||
194 | + const uniData = getReduce(alarmList); //去重得到最新的事件对象 | ||
195 | + | ||
196 | + if (!data?.data.length) return; | ||
197 | + uniData.forEach((item) => { | ||
198 | + alarmStatusList.value?.forEach((item1) => { | ||
199 | + if (item.entityId.id == item1.id) { | ||
200 | + item1.status = alarmLevel(item.severity); | ||
201 | + item1.time = item.createdTime; | ||
202 | + } | ||
203 | + }); | ||
204 | + }); | ||
205 | + }; | ||
206 | + | ||
207 | + onMounted(() => { | ||
208 | + !props.config.option.uuid && randomFn(); | ||
209 | + }); | ||
210 | + | ||
211 | + useCustomDataFetch(props, transformMessage, updateFn); | ||
212 | + | ||
213 | + const { getScale } = useComponentScale(props); | ||
214 | +</script> | ||
215 | + | ||
216 | +<template> | ||
217 | + <main :style="getScale" class="w-full h-full flex justify-center items-center"> | ||
218 | + <!-- <DeviceName :config="config" class="text-center" /> --> | ||
219 | + <div | ||
220 | + v-for="item in alarmStatusList" | ||
221 | + :key="item.id" | ||
222 | + class="flex flex-col justify-center items-center" | ||
223 | + > | ||
224 | + <div class="text-gray-500 text-sm truncate" | ||
225 | + >{{ | ||
226 | + item.status.text | ||
227 | + ? item.showDeviceName | ||
228 | + ? item.deviceName + '-' | ||
229 | + : '' | ||
230 | + : '当前设备未发现告警' | ||
231 | + }}{{ item.status.text }}</div | ||
232 | + > | ||
233 | + <div :class="item.status.color"></div> | ||
234 | + <UpdateTime :time="item.time" /> | ||
235 | + </div> | ||
236 | + </main> | ||
237 | +</template> | ||
238 | +<style lang="less" scoped> | ||
239 | + .alarm_state_critical { | ||
240 | + background: #cf1322; | ||
241 | + box-shadow: 0 -1px 7px 1px rgba(0, 0, 0, 0.2), inset 0 -1px 9px #304701, 0 2px 12px #cf1322; | ||
242 | + width: 60px; | ||
243 | + height: 60px; | ||
244 | + border-radius: 50%; | ||
245 | + margin: 10px 0; | ||
246 | + } | ||
247 | + | ||
248 | + .alarm_state_major { | ||
249 | + background: #ff6e03; | ||
250 | + box-shadow: 0 -1px 7px 1px rgba(0, 0, 0, 0.2), inset 0 -1px 9px #304701, 0 2px 12px #ff6e03; | ||
251 | + width: 60px; | ||
252 | + height: 60px; | ||
253 | + border-radius: 50%; | ||
254 | + margin: 10px 0; | ||
255 | + } | ||
256 | + | ||
257 | + .alarm_state_minor { | ||
258 | + background: #ff0; | ||
259 | + box-shadow: 0 -1px 7px 1px rgba(0, 0, 0, 0.2), inset 0 -1px 9px #304701, 0 2px 12px #ff0; | ||
260 | + width: 60px; | ||
261 | + height: 60px; | ||
262 | + border-radius: 50%; | ||
263 | + margin: 10px 0; | ||
264 | + } | ||
265 | + | ||
266 | + .alarm_state_warning { | ||
267 | + background: #edf760; | ||
268 | + box-shadow: 0 -1px 7px 1px rgba(0, 0, 0, 0.2), inset 0 -1px 9px #304701, 0 2px 12px #edf760; | ||
269 | + width: 60px; | ||
270 | + height: 60px; | ||
271 | + border-radius: 50%; | ||
272 | + margin: 10px 0; | ||
273 | + } | ||
274 | + | ||
275 | + .alarm_state_other { | ||
276 | + background: #d3adf7; | ||
277 | + box-shadow: 0 -1px 7px 1px rgba(0, 0, 0, 0.2), inset 0 -1px 9px #304701, 0 2px 12px #d3adf7; | ||
278 | + width: 60px; | ||
279 | + height: 60px; | ||
280 | + border-radius: 50%; | ||
281 | + margin: 10px 0; | ||
282 | + } | ||
283 | +</style> |
1 | +import cloneDeep from 'lodash-es/cloneDeep'; | ||
2 | +import { DeviceAlarmHistoryConfig } from '.'; | ||
3 | +import { | ||
4 | + ConfigType, | ||
5 | + CreateComponentType, | ||
6 | + PublicComponentOptions, | ||
7 | + PublicPresetOptions, | ||
8 | +} from '../../../index.type'; | ||
9 | +import { PublicConfigClass, componentInitConfig } from '../../../publicConfig'; | ||
10 | +import { ComponentConfigFieldEnum } from '../../../enum'; | ||
11 | + | ||
12 | +export const option: PublicPresetOptions = { | ||
13 | + multipleDataSourceComponent: true, | ||
14 | + componetDesign: false, | ||
15 | + [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: false, | ||
16 | + [ComponentConfigFieldEnum.UNIT]: '℃', | ||
17 | +}; | ||
18 | + | ||
19 | +export default class Config extends PublicConfigClass implements CreateComponentType { | ||
20 | + public key: string = DeviceAlarmHistoryConfig.key; | ||
21 | + | ||
22 | + public attr = { ...componentInitConfig }; | ||
23 | + | ||
24 | + public componentConfig: ConfigType = cloneDeep(DeviceAlarmHistoryConfig); | ||
25 | + | ||
26 | + public persetOption = cloneDeep(option); | ||
27 | + | ||
28 | + public option: PublicComponentOptions; | ||
29 | + | ||
30 | + constructor(option: PublicComponentOptions) { | ||
31 | + super(); | ||
32 | + this.option = { ...option }; | ||
33 | + } | ||
34 | +} |
1 | +<script lang="ts" setup> | ||
2 | + import { ComponentConfigFieldEnum } from '../../../enum'; | ||
3 | + import { option } from './config'; | ||
4 | + import { useForm, BasicForm } from '/@/components/Form'; | ||
5 | + import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type'; | ||
6 | + import { ComponentInfo } from '/@/views/visual/palette/types'; | ||
7 | + | ||
8 | + const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({ | ||
9 | + schemas: [ | ||
10 | + { | ||
11 | + field: ComponentConfigFieldEnum.UNIT, | ||
12 | + label: '数值单位', | ||
13 | + component: 'Input', | ||
14 | + defaultValue: option.unit, | ||
15 | + }, | ||
16 | + // { | ||
17 | + // field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME, | ||
18 | + // label: '显示设备名称', | ||
19 | + // component: 'Checkbox', | ||
20 | + // defaultValue: option.showDeviceName, | ||
21 | + // }, | ||
22 | + ], | ||
23 | + showActionButtonGroup: false, | ||
24 | + labelWidth: 120, | ||
25 | + baseColProps: { | ||
26 | + span: 12, | ||
27 | + }, | ||
28 | + }); | ||
29 | + | ||
30 | + const getFormValues = () => { | ||
31 | + // return getFieldsValue(); | ||
32 | + const item = getFieldsValue(); | ||
33 | + return { | ||
34 | + unit: item[ComponentConfigFieldEnum.UNIT], | ||
35 | + showDeviceName: item[ComponentConfigFieldEnum.SHOW_DEVICE_NAME], | ||
36 | + } as ComponentInfo; | ||
37 | + }; | ||
38 | + | ||
39 | + const setFormValues = (data: Recordable) => { | ||
40 | + // return setFieldsValue(data); | ||
41 | + const { unit, fontColor, showDeviceName } = data; | ||
42 | + | ||
43 | + const value = { | ||
44 | + [ComponentConfigFieldEnum.UNIT]: unit, | ||
45 | + [ComponentConfigFieldEnum.FONT_COLOR]: fontColor, | ||
46 | + [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: showDeviceName, | ||
47 | + }; | ||
48 | + return setFieldsValue(value); | ||
49 | + }; | ||
50 | + | ||
51 | + defineExpose({ | ||
52 | + getFormValues, | ||
53 | + setFormValues, | ||
54 | + resetFormValues: resetFields, | ||
55 | + } as PublicFormInstaceType); | ||
56 | +</script> | ||
57 | + | ||
58 | +<template> | ||
59 | + <BasicForm @register="register" /> | ||
60 | +</template> |
1 | +import { FormSchema } from '/@/components/Form'; | ||
2 | +import { DataSourceField } from '../../../config/common.config'; | ||
3 | +import { DeviceTypeEnum } from '/@/api/device/model/deviceModel'; | ||
4 | +import { findDictItemByCode } from '/@/api/system/dict'; | ||
5 | +import { getDeviceProfile } from '/@/api/alarm/position'; | ||
6 | +import { useSelectWidgetMode } from '/@/views/visual/dataSourceBindPanel/useContext'; | ||
7 | +import { DataActionModeEnum } from '/@/enums/toolEnum'; | ||
8 | +import { unref } from 'vue'; | ||
9 | +import { getMeetTheConditionsDevice } from '/@/api/dataBoard'; | ||
10 | +import { MasterDeviceList } from '/@/api/dataBoard/model'; | ||
11 | + | ||
12 | +export const formSchemas = (): FormSchema[] => { | ||
13 | + const mode = useSelectWidgetMode(); | ||
14 | + const isUpdate = unref(mode) === DataActionModeEnum.UPDATE; | ||
15 | + return [ | ||
16 | + { | ||
17 | + field: DataSourceField.DEVICE_NAME, | ||
18 | + component: 'Input', | ||
19 | + label: '设备名', | ||
20 | + show: false, | ||
21 | + }, | ||
22 | + { | ||
23 | + field: DataSourceField.DEVICE_TYPE, | ||
24 | + component: 'ApiSelect', | ||
25 | + label: '设备类型', | ||
26 | + colProps: { span: 8 }, | ||
27 | + rules: [{ message: '请选择设备类型', required: true }], | ||
28 | + componentProps: ({ formActionType }) => { | ||
29 | + const { setFieldsValue } = formActionType; | ||
30 | + return { | ||
31 | + api: findDictItemByCode, | ||
32 | + params: { | ||
33 | + dictCode: 'device_type', | ||
34 | + }, | ||
35 | + valueField: 'itemValue', | ||
36 | + labelField: 'itemText', | ||
37 | + placeholder: '请选择设备类型', | ||
38 | + onChange: (value: DeviceTypeEnum) => { | ||
39 | + setFieldsValue({ | ||
40 | + [DataSourceField.IS_GATEWAY_DEVICE]: value === DeviceTypeEnum.GATEWAY, | ||
41 | + [DataSourceField.DEVICE_PROFILE_ID]: null, | ||
42 | + [DataSourceField.DEVICE_ID]: null, | ||
43 | + [DataSourceField.ATTRIBUTE]: null, | ||
44 | + [DataSourceField.ATTRIBUTE_NAME]: null, | ||
45 | + [DataSourceField.TRANSPORT_TYPE]: null, | ||
46 | + }); | ||
47 | + }, | ||
48 | + getPopupContainer: () => document.body, | ||
49 | + }; | ||
50 | + }, | ||
51 | + }, | ||
52 | + { | ||
53 | + field: DataSourceField.DEVICE_PROFILE_ID, | ||
54 | + component: 'ApiSelect', | ||
55 | + label: '产品', | ||
56 | + colProps: { span: 8 }, | ||
57 | + rules: [{ required: true, message: '产品为必填项' }], | ||
58 | + componentProps: ({ formActionType, formModel }) => { | ||
59 | + const { setFieldsValue } = formActionType; | ||
60 | + const deviceType = formModel[DataSourceField.DEVICE_TYPE]; | ||
61 | + return { | ||
62 | + api: async () => { | ||
63 | + if (!deviceType) return []; | ||
64 | + const list = await getDeviceProfile(deviceType); | ||
65 | + if (isUpdate) { | ||
66 | + const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID]; | ||
67 | + const record = list.find((item) => item.id === deviceProfileId); | ||
68 | + setFieldsValue({ [DataSourceField.TRANSPORT_TYPE]: record?.transportType }); | ||
69 | + } | ||
70 | + return list; | ||
71 | + }, | ||
72 | + labelField: 'name', | ||
73 | + valueField: 'id', | ||
74 | + placeholder: '请选择产品', | ||
75 | + onChange: (_, option = {} as Record<'transportType', string>) => { | ||
76 | + setFieldsValue({ | ||
77 | + [DataSourceField.DEVICE_ID]: null, | ||
78 | + [DataSourceField.ATTRIBUTE]: null, | ||
79 | + [DataSourceField.ATTRIBUTE_NAME]: null, | ||
80 | + [DataSourceField.TRANSPORT_TYPE]: option[DataSourceField.TRANSPORT_TYPE], | ||
81 | + }); | ||
82 | + }, | ||
83 | + getPopupContainer: () => document.body, | ||
84 | + }; | ||
85 | + }, | ||
86 | + }, | ||
87 | + { | ||
88 | + field: DataSourceField.ORIGINATION_ID, | ||
89 | + component: 'OrgTreeSelect', | ||
90 | + label: '组织', | ||
91 | + colProps: { span: 8 }, | ||
92 | + rules: [{ required: true, message: '组织为必填项' }], | ||
93 | + componentProps({ formActionType }) { | ||
94 | + const { setFieldsValue } = formActionType; | ||
95 | + return { | ||
96 | + placeholder: '请选择组织', | ||
97 | + onChange() { | ||
98 | + setFieldsValue({ | ||
99 | + [DataSourceField.DEVICE_ID]: null, | ||
100 | + }); | ||
101 | + }, | ||
102 | + showCreate: false, | ||
103 | + getPopupContainer: () => document.body, | ||
104 | + }; | ||
105 | + }, | ||
106 | + }, | ||
107 | + { | ||
108 | + field: DataSourceField.DEVICE_PROFILE_ID, | ||
109 | + component: 'Input', | ||
110 | + label: '', | ||
111 | + show: false, | ||
112 | + }, | ||
113 | + { | ||
114 | + field: DataSourceField.DEVICE_ID, | ||
115 | + component: 'ApiSelect', | ||
116 | + label: '设备', | ||
117 | + colProps: { span: 8 }, | ||
118 | + rules: [{ required: true, message: '设备名称为必填项' }], | ||
119 | + componentProps({ formModel, formActionType }) { | ||
120 | + const { setFieldsValue } = formActionType; | ||
121 | + const organizationId = formModel[DataSourceField.ORIGINATION_ID]; | ||
122 | + const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID]; | ||
123 | + const deviceType = formModel[DataSourceField.DEVICE_TYPE]; | ||
124 | + | ||
125 | + return { | ||
126 | + api: async () => { | ||
127 | + if (organizationId) { | ||
128 | + try { | ||
129 | + const data = await getMeetTheConditionsDevice({ | ||
130 | + organizationId, | ||
131 | + deviceProfileId, | ||
132 | + deviceType, | ||
133 | + }); | ||
134 | + if (data) | ||
135 | + return data.map((item) => ({ | ||
136 | + ...item, | ||
137 | + label: item.alias || item.name, | ||
138 | + value: item.tbDeviceId, | ||
139 | + deviceType: item.deviceType, | ||
140 | + })); | ||
141 | + } catch (error) {} | ||
142 | + } | ||
143 | + return []; | ||
144 | + }, | ||
145 | + onChange(_value, record: MasterDeviceList) { | ||
146 | + setFieldsValue({ | ||
147 | + [DataSourceField.DEVICE_NAME]: record?.label, | ||
148 | + }); | ||
149 | + }, | ||
150 | + placeholder: '请选择设备', | ||
151 | + getPopupContainer: () => document.body, | ||
152 | + }; | ||
153 | + }, | ||
154 | + }, | ||
155 | + { | ||
156 | + field: DataSourceField.DEVICE_RENAME, | ||
157 | + component: 'Input', | ||
158 | + label: '设备名', | ||
159 | + colProps: { span: 8 }, | ||
160 | + componentProps: { | ||
161 | + placeholder: '设备重命名', | ||
162 | + }, | ||
163 | + }, | ||
164 | + ]; | ||
165 | +}; |
1 | +<script lang="ts" setup> | ||
2 | + // import { commonDataSourceSchemas } from '../../../config/common.config'; | ||
3 | + import { CreateComponentType } from '../../../index.type'; | ||
4 | + import { BasicForm, useForm } from '/@/components/Form'; | ||
5 | + import { | ||
6 | + PublicComponentValueType, | ||
7 | + PublicFormInstaceType, | ||
8 | + } from '/@/views/visual/dataSourceBindPanel/index.type'; | ||
9 | + | ||
10 | + import { formSchemas } from './datasource.config'; | ||
11 | + | ||
12 | + defineProps<{ | ||
13 | + values: PublicComponentValueType; | ||
14 | + componentConfig: CreateComponentType; | ||
15 | + }>(); | ||
16 | + | ||
17 | + const [register, { getFieldsValue, setFieldsValue, validate, resetFields }] = useForm({ | ||
18 | + labelWidth: 0, | ||
19 | + showActionButtonGroup: false, | ||
20 | + layout: 'horizontal', | ||
21 | + labelCol: { span: 0 }, | ||
22 | + schemas: formSchemas(), | ||
23 | + }); | ||
24 | + | ||
25 | + const getFormValues = () => { | ||
26 | + return getFieldsValue(); | ||
27 | + }; | ||
28 | + | ||
29 | + const setFormValues = (record: Recordable) => { | ||
30 | + return setFieldsValue(record); | ||
31 | + }; | ||
32 | + | ||
33 | + defineExpose({ | ||
34 | + getFormValues, | ||
35 | + setFormValues, | ||
36 | + validate, | ||
37 | + resetFormValues: resetFields, | ||
38 | + } as PublicFormInstaceType); | ||
39 | +</script> | ||
40 | + | ||
41 | +<template> | ||
42 | + <BasicForm @register="register" /> | ||
43 | +</template> |
1 | +import { useComponentKeys } from '/@/views/visual/packages/hook/useComponentKeys'; | ||
2 | +import { ConfigType, PackagesCategoryEnum } from '/@/views/visual/packages/index.type'; | ||
3 | + | ||
4 | +const componentKeys = useComponentKeys('DeviceAlarmHistory'); | ||
5 | + | ||
6 | +export const DeviceAlarmHistoryConfig: ConfigType = { | ||
7 | + ...componentKeys, | ||
8 | + title: '设备告警历史', | ||
9 | + package: PackagesCategoryEnum.ALARM, | ||
10 | +}; |
1 | +<script lang="ts" setup> | ||
2 | + import { computed, unref, ref, watch } from 'vue'; | ||
3 | + import { ComponentPropsConfigType } from '../../../index.type'; | ||
4 | + import { option } from './config'; | ||
5 | + import { DeviceName } from '/@/views/visual/commonComponents/DeviceName'; | ||
6 | + import { BasicTable, useTable, BasicColumn } from '/@/components/Table'; | ||
7 | + import { formatToDateTime } from '/@/utils/dateUtil'; | ||
8 | + import { onMounted } from 'vue'; | ||
9 | + import { nextTick } from 'vue'; | ||
10 | + import { useComponentScale } from '../../../hook/useComponentScale'; | ||
11 | + import { h } from 'vue'; | ||
12 | + import { Tag } from 'ant-design-vue'; | ||
13 | + import { useDataBoardContext } from '/@/views/visual/palette/hooks/useDataBoardContext'; | ||
14 | + import { useCustomDataFetch } from '../../../hook/socket/useSocket'; | ||
15 | + import { ReceiveAlarmDataCmdsMessageType } from '../../../hook/socket/useSocket.type'; | ||
16 | + import { useAlarmContext } from '/@/views/visual/palette/hooks/useAlarmTime'; | ||
17 | + | ||
18 | + import { getMessage } from '../config'; | ||
19 | + | ||
20 | + const props = defineProps<{ | ||
21 | + config: ComponentPropsConfigType<typeof option>; | ||
22 | + }>(); | ||
23 | + | ||
24 | + interface IList { | ||
25 | + [key: string]: string | number; | ||
26 | + } | ||
27 | + | ||
28 | + const alarmLevel = (type: string): IList => { | ||
29 | + if (type === 'CRITICAL') { | ||
30 | + return { text: '紧急', color: 'red' }; | ||
31 | + } else if (type === 'MAJOR') { | ||
32 | + return { text: '重要', color: 'pink' }; | ||
33 | + } else if (type === 'MINOR') { | ||
34 | + return { text: '次要', color: 'warning' }; | ||
35 | + } else if (type === 'WARNING') { | ||
36 | + return { text: '警告', color: 'warning' }; | ||
37 | + } else { | ||
38 | + return { text: '不确定', color: 'default' }; | ||
39 | + } | ||
40 | + }; | ||
41 | + | ||
42 | + const statusType = (type: string): string => { | ||
43 | + if (type === 'CLEARED_UNACK') { | ||
44 | + return '清除未确认'; | ||
45 | + } else if (type === 'CLEARED_ACK') { | ||
46 | + return '清除已确认'; | ||
47 | + } else if (type === 'ACTIVE_ACK') { | ||
48 | + return '激活已确认'; | ||
49 | + } else { | ||
50 | + return '激活未确认'; | ||
51 | + } | ||
52 | + }; | ||
53 | + | ||
54 | + const { send } = useDataBoardContext(); | ||
55 | + | ||
56 | + const currentCmdId = ref(); | ||
57 | + | ||
58 | + const alarmColumns: BasicColumn[] = [ | ||
59 | + { | ||
60 | + title: '状态', | ||
61 | + dataIndex: 'severity', | ||
62 | + ellipsis: true, | ||
63 | + width: 80, | ||
64 | + customRender: ({ record }) => { | ||
65 | + const { severity } = record; | ||
66 | + const { text, color } = alarmLevel(severity); | ||
67 | + return h(Tag, { color }, () => text); | ||
68 | + }, | ||
69 | + }, | ||
70 | + { title: '设备', dataIndex: 'device', ellipsis: true, width: 120 }, | ||
71 | + { title: '告警场景', dataIndex: 'type', ellipsis: true, width: 80 }, | ||
72 | + { | ||
73 | + title: '状态', | ||
74 | + dataIndex: 'status', | ||
75 | + ellipsis: true, | ||
76 | + width: 80, | ||
77 | + format: (text) => statusType(text), | ||
78 | + }, | ||
79 | + { | ||
80 | + title: '时间', | ||
81 | + dataIndex: 'time', | ||
82 | + ellipsis: true, | ||
83 | + width: 110, | ||
84 | + format(text) { | ||
85 | + return formatToDateTime(text, 'YYYY-MM-DD HH:mm:ss'); | ||
86 | + }, | ||
87 | + }, | ||
88 | + ]; | ||
89 | + const devicealarmList = ref([ | ||
90 | + { severity: 0, device: '温湿度', type: '规则', status: 50, time: 1688974276000 }, | ||
91 | + { severity: 1, device: '温湿度1', type: '规则1', status: 350, time: 1688994276000 }, | ||
92 | + { severity: 0, device: '温湿度2', type: '规则2', status: 150, time: 1688174276000 }, | ||
93 | + { severity: 0, device: '温湿度3', type: '规则2', status: 150, time: 1688174276000 }, | ||
94 | + { severity: 0, device: '温湿度4', type: '规则2', status: 150, time: 1688174276000 }, | ||
95 | + { severity: 0, device: '温湿度5', type: '规则2', status: 150, time: 1688174276000 }, | ||
96 | + ]); | ||
97 | + | ||
98 | + const [registerTable, { setProps, redoHeight, setTableData }] = useTable({ | ||
99 | + showIndexColumn: false, | ||
100 | + showTableSetting: false, | ||
101 | + canResize: true, | ||
102 | + size: 'small', | ||
103 | + maxHeight: 144, | ||
104 | + dataSource: devicealarmList, | ||
105 | + columns: alarmColumns, | ||
106 | + }); | ||
107 | + | ||
108 | + const getDesign = computed(() => { | ||
109 | + const { persetOption, option } = props.config; | ||
110 | + const { dataSource = [] } = option || {}; | ||
111 | + const { unit: presetUnit, presetShowDeviceName } = persetOption || {}; | ||
112 | + return { | ||
113 | + dataSource: dataSource?.map((item) => { | ||
114 | + const { unit, showDeviceName } = item.componentInfo || {}; | ||
115 | + const { attribute, attributeName, attributeRename, deviceName, deviceRename, deviceId } = | ||
116 | + item; | ||
117 | + return { | ||
118 | + unit: unit ?? presetUnit, | ||
119 | + attribute, | ||
120 | + attributeRename, | ||
121 | + attributeName, | ||
122 | + showDeviceName: showDeviceName ?? presetShowDeviceName, | ||
123 | + deviceName, | ||
124 | + deviceRename, | ||
125 | + id: deviceId, | ||
126 | + }; | ||
127 | + }), | ||
128 | + }; | ||
129 | + }); | ||
130 | + | ||
131 | + // const getMessage = (cmdId: number) => { | ||
132 | + // const message = { | ||
133 | + // alarmDataCmds: [ | ||
134 | + // { | ||
135 | + // query: { | ||
136 | + // entityFilter: { | ||
137 | + // type: 'entityList', | ||
138 | + // resolveMultiple: true, | ||
139 | + // entityType: 'DEVICE', | ||
140 | + // entityList: unref(getDesign).dataSource?.map((item) => item.id), | ||
141 | + // }, | ||
142 | + // pageLink: { | ||
143 | + // page: 0, | ||
144 | + // pageSize: unref(alarmForm)?.pageSize, | ||
145 | + // textSearch: null, | ||
146 | + // searchPropagatedAlarms: false, | ||
147 | + // statusList: [], | ||
148 | + // severityList: [], | ||
149 | + // typeList: [], | ||
150 | + // sortOrder: { | ||
151 | + // key: { | ||
152 | + // key: 'createdTime', | ||
153 | + // type: 'ALARM_FIELD', | ||
154 | + // }, | ||
155 | + // direction: 'DESC', | ||
156 | + // }, | ||
157 | + // timeWindow: unref(alarmForm)?.time, | ||
158 | + // }, | ||
159 | + // alarmFields: [ | ||
160 | + // { | ||
161 | + // type: 'ALARM_FIELD', | ||
162 | + // key: 'createdTime', | ||
163 | + // }, | ||
164 | + // { | ||
165 | + // type: 'ALARM_FIELD', | ||
166 | + // key: 'originator', | ||
167 | + // }, | ||
168 | + // { | ||
169 | + // type: 'ALARM_FIELD', | ||
170 | + // key: 'type', | ||
171 | + // }, | ||
172 | + // { | ||
173 | + // type: 'ALARM_FIELD', | ||
174 | + // key: 'severity', | ||
175 | + // }, | ||
176 | + // { | ||
177 | + // type: 'ALARM_FIELD', | ||
178 | + // key: 'status', | ||
179 | + // }, | ||
180 | + // ], | ||
181 | + // entityFields: [], | ||
182 | + // latestValues: [], | ||
183 | + // }, | ||
184 | + // cmdId, | ||
185 | + // }, | ||
186 | + // ], | ||
187 | + // }; | ||
188 | + // return message; | ||
189 | + // }; | ||
190 | + | ||
191 | + const { alarmForm } = useAlarmContext(); | ||
192 | + | ||
193 | + watch( | ||
194 | + () => alarmForm?.value, | ||
195 | + () => { | ||
196 | + send?.(JSON.stringify(getMessage(unref(currentCmdId), unref(getDesign), unref(alarmForm)))); | ||
197 | + }, | ||
198 | + { immediate: false } | ||
199 | + ); | ||
200 | + | ||
201 | + const transformMessage = (cmdId: number) => { | ||
202 | + currentCmdId.value = cmdId; | ||
203 | + | ||
204 | + send?.(JSON.stringify(getMessage(cmdId, unref(getDesign), unref(alarmForm)))); | ||
205 | + }; | ||
206 | + | ||
207 | + const updateFn = (message: ReceiveAlarmDataCmdsMessageType) => { | ||
208 | + const { data } = message || {}; | ||
209 | + const tableList = ref<any>( | ||
210 | + data?.data.map((item) => { | ||
211 | + return { | ||
212 | + time: item.createdTime, | ||
213 | + device: item.originatorName, | ||
214 | + severity: item.severity, | ||
215 | + type: item.type, | ||
216 | + status: item.status, | ||
217 | + }; | ||
218 | + }) | ||
219 | + ); | ||
220 | + setTableData(unref(tableList)); | ||
221 | + }; | ||
222 | + | ||
223 | + useCustomDataFetch(props, transformMessage, updateFn); | ||
224 | + | ||
225 | + onMounted(async () => { | ||
226 | + await nextTick(); | ||
227 | + resize(); | ||
228 | + }); | ||
229 | + | ||
230 | + const resize = async () => { | ||
231 | + const { height } = unref(getContainerSize); | ||
232 | + height && setProps({ maxHeight: height - 100, scroll: { x: 470, y: height - 100 } }); | ||
233 | + await nextTick(); | ||
234 | + redoHeight(); | ||
235 | + }; | ||
236 | + | ||
237 | + const { getContainerSize } = useComponentScale(props, resize); | ||
238 | +</script> | ||
239 | + | ||
240 | +<template> | ||
241 | + <main class="flex flex-col justify-center items-center w-full h-full"> | ||
242 | + <DeviceName :config="config" /> | ||
243 | + <div class="w-full h-full"> | ||
244 | + <BasicTable autoCreateKey style="flex: auto" @register="registerTable" /> | ||
245 | + </div> | ||
246 | + </main> | ||
247 | +</template> |
1 | +export const getMessage = (cmdId, getDesign, alarmForm) => { | ||
2 | + const entityList = [...new Set(getDesign.dataSource?.map((item) => item.id))]; | ||
3 | + const message = { | ||
4 | + alarmDataCmds: [ | ||
5 | + { | ||
6 | + query: { | ||
7 | + entityFilter: { | ||
8 | + type: 'entityList', | ||
9 | + resolveMultiple: true, | ||
10 | + entityType: 'DEVICE', | ||
11 | + entityList: entityList, | ||
12 | + }, | ||
13 | + pageLink: { | ||
14 | + page: 0, | ||
15 | + pageSize: alarmForm?.pageSize, | ||
16 | + textSearch: null, | ||
17 | + searchPropagatedAlarms: false, | ||
18 | + statusList: [], | ||
19 | + severityList: [], | ||
20 | + typeList: [], | ||
21 | + sortOrder: { | ||
22 | + key: { | ||
23 | + key: 'createdTime', | ||
24 | + type: 'ALARM_FIELD', | ||
25 | + }, | ||
26 | + direction: 'DESC', | ||
27 | + }, | ||
28 | + timeWindow: alarmForm?.time, | ||
29 | + }, | ||
30 | + alarmFields: [ | ||
31 | + { | ||
32 | + type: 'ALARM_FIELD', | ||
33 | + key: 'createdTime', | ||
34 | + }, | ||
35 | + { | ||
36 | + type: 'ALARM_FIELD', | ||
37 | + key: 'originator', | ||
38 | + }, | ||
39 | + { | ||
40 | + type: 'ALARM_FIELD', | ||
41 | + key: 'type', | ||
42 | + }, | ||
43 | + { | ||
44 | + type: 'ALARM_FIELD', | ||
45 | + key: 'severity', | ||
46 | + }, | ||
47 | + { | ||
48 | + type: 'ALARM_FIELD', | ||
49 | + key: 'status', | ||
50 | + }, | ||
51 | + ], | ||
52 | + entityFields: [], | ||
53 | + latestValues: [], | ||
54 | + }, | ||
55 | + cmdId, | ||
56 | + }, | ||
57 | + ], | ||
58 | + }; | ||
59 | + return message; | ||
60 | +}; |
@@ -52,7 +52,6 @@ | @@ -52,7 +52,6 @@ | ||
52 | 52 | ||
53 | const handleBlur = async () => { | 53 | const handleBlur = async () => { |
54 | if (unref(oldSliderValue) !== unref(sliderValue)) { | 54 | if (unref(oldSliderValue) !== unref(sliderValue)) { |
55 | - console.log('effect'); | ||
56 | const flag = await sendCommand(props.config.option, unref(sliderValue)); | 55 | const flag = await sendCommand(props.config.option, unref(sliderValue)); |
57 | flag | 56 | flag |
58 | ? ((sliderValue.value = unref(sliderValue)), | 57 | ? ((sliderValue.value = unref(sliderValue)), |
@@ -67,10 +67,8 @@ | @@ -67,10 +67,8 @@ | ||
67 | }, | 67 | }, |
68 | })) | 68 | })) |
69 | ); | 69 | ); |
70 | - // console.log(unref(series), 'series'); | ||
71 | 70 | ||
72 | const options = (): EChartsOption => { | 71 | const options = (): EChartsOption => { |
73 | - // getStageColor(gradientInfo); | ||
74 | return { | 72 | return { |
75 | tooltip: { | 73 | tooltip: { |
76 | trigger: 'item', | 74 | trigger: 'item', |
@@ -8,15 +8,6 @@ import { | @@ -8,15 +8,6 @@ import { | ||
8 | } from '../../../index.type'; | 8 | } from '../../../index.type'; |
9 | import { PublicConfigClass, componentInitConfig } from '../../../publicConfig'; | 9 | import { PublicConfigClass, componentInitConfig } from '../../../publicConfig'; |
10 | import { ComponentConfigFieldEnum } from '../../../enum'; | 10 | import { ComponentConfigFieldEnum } from '../../../enum'; |
11 | - | ||
12 | -export enum Gradient { | ||
13 | - FIRST = 'first', | ||
14 | - SECOND = 'second', | ||
15 | -} | ||
16 | -export enum GradientColor { | ||
17 | - FIRST = '#07ffd6', | ||
18 | - SECOND = '#5eff10', | ||
19 | -} | ||
20 | export const option: PublicPresetOptions = { | 11 | export const option: PublicPresetOptions = { |
21 | multipleDataSourceComponent: true, | 12 | multipleDataSourceComponent: true, |
22 | [ComponentConfigFieldEnum.FONT_COLOR]: '#FD7347', | 13 | [ComponentConfigFieldEnum.FONT_COLOR]: '#FD7347', |
@@ -6,6 +6,8 @@ | @@ -6,6 +6,8 @@ | ||
6 | import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime'; | 6 | import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime'; |
7 | import { useComponentScale } from '../../../hook/useComponentScale'; | 7 | import { useComponentScale } from '../../../hook/useComponentScale'; |
8 | import { DeviceName } from '/@/views/visual/commonComponents/DeviceName'; | 8 | import { DeviceName } from '/@/views/visual/commonComponents/DeviceName'; |
9 | + import { useReceiveMessage } from '../../../hook/useReceiveMessage'; | ||
10 | + import { useReceiveValue } from '../../../hook/useReceiveValue'; | ||
9 | import { useMultipleDataFetch } from '../../../hook/socket/useSocket'; | 11 | import { useMultipleDataFetch } from '../../../hook/socket/useSocket'; |
10 | import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type'; | 12 | import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type'; |
11 | 13 | ||
@@ -22,33 +24,45 @@ | @@ -22,33 +24,45 @@ | ||
22 | const getDesign = computed(() => { | 24 | const getDesign = computed(() => { |
23 | const { persetOption, option } = props.config; | 25 | const { persetOption, option } = props.config; |
24 | const { dataSource = [] } = option || {}; | 26 | const { dataSource = [] } = option || {}; |
25 | - const { unit: presetUnit, fontColor: presetFontColor } = persetOption || {}; | 27 | + const { |
28 | + unit: presetUnit, | ||
29 | + fontColor: presetFontColor, | ||
30 | + showDeviceName: persetShowDeviceName, | ||
31 | + } = persetOption || {}; | ||
26 | return { | 32 | return { |
27 | dataSource: dataSource?.map((item) => { | 33 | dataSource: dataSource?.map((item) => { |
28 | - const { unit, fontColor } = item.componentInfo || {}; | ||
29 | - const { attribute, attributeRename } = item; | 34 | + const { unit, fontColor, showDeviceName } = item.componentInfo || {}; |
35 | + const { attribute, attributeName, attributeRename, deviceId, deviceName, deviceRename } = | ||
36 | + item; | ||
30 | return { | 37 | return { |
31 | unit: unit ?? presetUnit, | 38 | unit: unit ?? presetUnit, |
32 | fontColor: fontColor ?? presetFontColor, | 39 | fontColor: fontColor ?? presetFontColor, |
33 | attribute, | 40 | attribute, |
41 | + attributeName, | ||
34 | attributeRename, | 42 | attributeRename, |
43 | + deviceName, | ||
44 | + deviceRename, | ||
45 | + showDeviceName: showDeviceName ?? persetShowDeviceName, | ||
46 | + id: deviceId, | ||
35 | }; | 47 | }; |
36 | }), | 48 | }), |
37 | }; | 49 | }; |
38 | }); | 50 | }); |
51 | + | ||
39 | const seriesList = [ | 52 | const seriesList = [ |
40 | { value: 120, name: '123', itemStyle: { color: '#02E5F0' } }, | 53 | { value: 120, name: '123', itemStyle: { color: '#02E5F0' } }, |
41 | { value: 150, name: '456', itemStyle: { color: '#028CF0' } }, | 54 | { value: 150, name: '456', itemStyle: { color: '#028CF0' } }, |
42 | { value: 40, name: '789', itemStyle: { color: '#F09202' } }, | 55 | { value: 40, name: '789', itemStyle: { color: '#F09202' } }, |
43 | ]; | 56 | ]; |
44 | const data = ['温度', '湿度', '温度1']; | 57 | const data = ['温度', '湿度', '温度1']; |
58 | + | ||
45 | const options = (): EChartsOption => { | 59 | const options = (): EChartsOption => { |
46 | - // getStageColor(gradientInfo); | ||
47 | return { | 60 | return { |
48 | color: ['#3398DB'], | 61 | color: ['#3398DB'], |
49 | tooltip: { | 62 | tooltip: { |
50 | // 提示框 | 63 | // 提示框 |
51 | - trigger: 'axis', | 64 | + trigger: 'item', |
65 | + confine: true, | ||
52 | axisPointer: { | 66 | axisPointer: { |
53 | type: 'shadow', | 67 | type: 'shadow', |
54 | }, | 68 | }, |
@@ -59,9 +73,6 @@ | @@ -59,9 +73,6 @@ | ||
59 | axisTick: { | 73 | axisTick: { |
60 | alignWithLabel: true, | 74 | alignWithLabel: true, |
61 | }, | 75 | }, |
62 | - // axisPointer: { | ||
63 | - // type: 'line', | ||
64 | - // }, | ||
65 | }, | 76 | }, |
66 | grid: { | 77 | grid: { |
67 | top: '15%', | 78 | top: '15%', |
@@ -104,32 +115,40 @@ | @@ -104,32 +115,40 @@ | ||
104 | } as EChartsOption); | 115 | } as EChartsOption); |
105 | }; | 116 | }; |
106 | 117 | ||
107 | - const updateFn: MultipleDataFetchUpdateFn = (message) => { | ||
108 | - const { data = {} } = message; | ||
109 | - const { dataSource } = unref(getDesign); | ||
110 | - const series = dataSource.map((item) => { | ||
111 | - const { attribute, attributeRename, fontColor, unit } = item; | ||
112 | - const [latest] = data[attribute] || []; | ||
113 | - const [_timespan, value] = latest || []; | ||
114 | - | ||
115 | - return { | ||
116 | - value, | ||
117 | - name: attributeRename ?? attribute, | ||
118 | - itemStyle: { color: fontColor }, | ||
119 | - tooltip: { | ||
120 | - valueFormatter(value) { | ||
121 | - return `${value} ${unit ?? ''}`; | ||
122 | - }, | 118 | + const { forEachGroupMessage } = useReceiveMessage(); |
119 | + const { getNumberValue } = useReceiveValue(); | ||
120 | + | ||
121 | + const series = ref( | ||
122 | + unref(getDesign).dataSource.map((item) => ({ | ||
123 | + value: 0, | ||
124 | + name: `${item.showDeviceName ? `${item.deviceRename || item.deviceName}-` : ''}${ | ||
125 | + item.attributeRename || item.attributeName || item.attribute | ||
126 | + }`, | ||
127 | + attribute: item.attribute, | ||
128 | + id: item.id, | ||
129 | + itemStyle: { | ||
130 | + color: item.fontColor, | ||
131 | + }, | ||
132 | + tooltip: { | ||
133 | + valueFormatter(value) { | ||
134 | + return `${value} ${item.unit ?? ''}`; | ||
123 | }, | 135 | }, |
124 | - } as SeriesOption['data']; | ||
125 | - }); | ||
126 | - const xAxisData = series.map((item) => { | ||
127 | - const { name } = item as any; | ||
128 | - return name; | 136 | + }, |
137 | + })) | ||
138 | + ); | ||
139 | + | ||
140 | + const updateFn: MultipleDataFetchUpdateFn = (message, deviceId, attribute) => { | ||
141 | + forEachGroupMessage(message, deviceId, attribute, (attribute, value, timespan) => { | ||
142 | + series.value.forEach((item) => { | ||
143 | + if (item.id == deviceId && item.attribute === attribute) { | ||
144 | + item.value = getNumberValue(value); | ||
145 | + } | ||
146 | + }); | ||
147 | + time.value = timespan; | ||
129 | }); | 148 | }); |
130 | - // } | ||
131 | - // console.log(message, 'message', series, 'series', sum); | ||
132 | - updateChart(series, xAxisData); | 149 | + const xAxisData = unref(series).map((item) => item.name); |
150 | + | ||
151 | + updateChart(toRaw(unref(series)), xAxisData); | ||
133 | }; | 152 | }; |
134 | 153 | ||
135 | useMultipleDataFetch(props, updateFn); | 154 | useMultipleDataFetch(props, updateFn); |
@@ -6,6 +6,8 @@ | @@ -6,6 +6,8 @@ | ||
6 | import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime'; | 6 | import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime'; |
7 | import { useComponentScale } from '../../../hook/useComponentScale'; | 7 | import { useComponentScale } from '../../../hook/useComponentScale'; |
8 | import { DeviceName } from '/@/views/visual/commonComponents/DeviceName'; | 8 | import { DeviceName } from '/@/views/visual/commonComponents/DeviceName'; |
9 | + import { useReceiveMessage } from '../../../hook/useReceiveMessage'; | ||
10 | + import { useReceiveValue } from '../../../hook/useReceiveValue'; | ||
9 | import { useMultipleDataFetch } from '../../../hook/socket/useSocket'; | 11 | import { useMultipleDataFetch } from '../../../hook/socket/useSocket'; |
10 | import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type'; | 12 | import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type'; |
11 | 13 | ||
@@ -22,16 +24,26 @@ | @@ -22,16 +24,26 @@ | ||
22 | const getDesign = computed(() => { | 24 | const getDesign = computed(() => { |
23 | const { persetOption, option } = props.config; | 25 | const { persetOption, option } = props.config; |
24 | const { dataSource = [] } = option || {}; | 26 | const { dataSource = [] } = option || {}; |
25 | - const { unit: presetUnit, fontColor: presetFontColor } = persetOption || {}; | 27 | + const { |
28 | + unit: presetUnit, | ||
29 | + fontColor: presetFontColor, | ||
30 | + showDeviceName: persetShowDeviceName, | ||
31 | + } = persetOption || {}; | ||
26 | return { | 32 | return { |
27 | dataSource: dataSource?.map((item) => { | 33 | dataSource: dataSource?.map((item) => { |
28 | - const { unit, fontColor } = item.componentInfo || {}; | ||
29 | - const { attribute, attributeRename } = item; | 34 | + const { unit, fontColor, showDeviceName } = item.componentInfo || {}; |
35 | + const { attribute, attributeName, attributeRename, deviceId, deviceName, deviceRename } = | ||
36 | + item; | ||
30 | return { | 37 | return { |
31 | unit: unit ?? presetUnit, | 38 | unit: unit ?? presetUnit, |
32 | fontColor: fontColor ?? presetFontColor, | 39 | fontColor: fontColor ?? presetFontColor, |
33 | attribute, | 40 | attribute, |
41 | + attributeName, | ||
34 | attributeRename, | 42 | attributeRename, |
43 | + deviceName, | ||
44 | + deviceRename, | ||
45 | + showDeviceName: showDeviceName ?? persetShowDeviceName, | ||
46 | + id: deviceId, | ||
35 | }; | 47 | }; |
36 | }), | 48 | }), |
37 | }; | 49 | }; |
@@ -48,7 +60,8 @@ | @@ -48,7 +60,8 @@ | ||
48 | color: ['#3398DB'], | 60 | color: ['#3398DB'], |
49 | tooltip: { | 61 | tooltip: { |
50 | // 提示框 | 62 | // 提示框 |
51 | - trigger: 'axis', | 63 | + trigger: 'item', |
64 | + confine: true, | ||
52 | axisPointer: { | 65 | axisPointer: { |
53 | type: 'shadow', | 66 | type: 'shadow', |
54 | }, | 67 | }, |
@@ -104,32 +117,39 @@ | @@ -104,32 +117,39 @@ | ||
104 | } as EChartsOption); | 117 | } as EChartsOption); |
105 | }; | 118 | }; |
106 | 119 | ||
107 | - const updateFn: MultipleDataFetchUpdateFn = (message) => { | ||
108 | - const { data = {} } = message; | ||
109 | - const { dataSource } = unref(getDesign); | ||
110 | - const series = dataSource.map((item) => { | ||
111 | - const { attribute, attributeRename, fontColor, unit } = item; | ||
112 | - const [latest] = data[attribute] || []; | ||
113 | - const [_timespan, value] = latest || []; | ||
114 | - | ||
115 | - return { | ||
116 | - value, | ||
117 | - name: attributeRename ?? attribute, | ||
118 | - itemStyle: { color: fontColor }, | ||
119 | - tooltip: { | ||
120 | - valueFormatter(value) { | ||
121 | - return `${value} ${unit ?? ''}`; | ||
122 | - }, | 120 | + const { forEachGroupMessage } = useReceiveMessage(); |
121 | + const { getNumberValue } = useReceiveValue(); | ||
122 | + | ||
123 | + const series = ref( | ||
124 | + unref(getDesign).dataSource.map((item) => ({ | ||
125 | + value: 0, | ||
126 | + name: `${item.showDeviceName ? `${item.deviceRename || item.deviceName}-` : ''}${ | ||
127 | + item.attributeRename || item.attributeName || item.attribute | ||
128 | + }`, | ||
129 | + attribute: item.attribute, | ||
130 | + id: item.id, | ||
131 | + itemStyle: { | ||
132 | + color: item.fontColor, | ||
133 | + }, | ||
134 | + tooltip: { | ||
135 | + valueFormatter(value) { | ||
136 | + return `${value} ${item.unit ?? ''}`; | ||
123 | }, | 137 | }, |
124 | - } as SeriesOption['data']; | ||
125 | - }); | ||
126 | - const yAxisData = series.map((item) => { | ||
127 | - const { name } = item as any; | ||
128 | - return name; | 138 | + }, |
139 | + })) | ||
140 | + ); | ||
141 | + | ||
142 | + const updateFn: MultipleDataFetchUpdateFn = (message, deviceId, attribute) => { | ||
143 | + forEachGroupMessage(message, deviceId, attribute, (attribute, value, timespan) => { | ||
144 | + series.value.forEach((item) => { | ||
145 | + if (item.id == deviceId && item.attribute === attribute) { | ||
146 | + item.value = getNumberValue(value); | ||
147 | + } | ||
148 | + time.value = timespan; | ||
149 | + }); | ||
129 | }); | 150 | }); |
130 | - // } | ||
131 | - // console.log(message, 'message', series, 'series', sum); | ||
132 | - updateChart(series, yAxisData); | 151 | + const xAxisData = unref(series).map((item) => item.name); |
152 | + updateChart(toRaw(unref(series)), xAxisData); | ||
133 | }; | 153 | }; |
134 | 154 | ||
135 | useMultipleDataFetch(props, updateFn); | 155 | useMultipleDataFetch(props, updateFn); |
1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
2 | - import { reactive, ref } from 'vue'; | 2 | + import { computed, unref } from 'vue'; |
3 | import { ComponentPropsConfigType } from '../../../index.type'; | 3 | import { ComponentPropsConfigType } from '../../../index.type'; |
4 | import { option } from './config'; | 4 | import { option } from './config'; |
5 | import { DeviceName } from '/@/views/visual/commonComponents/DeviceName'; | 5 | import { DeviceName } from '/@/views/visual/commonComponents/DeviceName'; |
6 | import { BasicTable, useTable, BasicColumn } from '/@/components/Table'; | 6 | import { BasicTable, useTable, BasicColumn } from '/@/components/Table'; |
7 | - import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type'; | 7 | + import { useReceiveMessage } from '../../../hook/useReceiveMessage'; |
8 | + import { useReceiveValue } from '../../../hook/useReceiveValue'; | ||
9 | + import { formatToDateTime } from '/@/utils/dateUtil'; | ||
10 | + import { nextTick } from 'vue'; | ||
11 | + import { onMounted } from 'vue'; | ||
12 | + import { toRaw } from 'vue'; | ||
13 | + import { useComponentScale } from '../../../hook/useComponentScale'; | ||
8 | import { useMultipleDataFetch } from '../../../hook/socket/useSocket'; | 14 | import { useMultipleDataFetch } from '../../../hook/socket/useSocket'; |
15 | + import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type'; | ||
16 | + | ||
17 | + interface IList { | ||
18 | + [key: string]: string | number; | ||
19 | + } | ||
9 | 20 | ||
10 | const props = defineProps<{ | 21 | const props = defineProps<{ |
11 | config: ComponentPropsConfigType<typeof option>; | 22 | config: ComponentPropsConfigType<typeof option>; |
12 | }>(); | 23 | }>(); |
13 | 24 | ||
14 | - const realTimeList = ref<any>([ | ||
15 | - { attribute: '测试', price: 2, time: '2023-06-29' }, | ||
16 | - { attribute: '测试1', price: 21, time: '2023-06-29' }, | ||
17 | - { attribute: '测试2', price: 213, time: '2023-06-29' }, | ||
18 | - ]); | ||
19 | - const realTimeColumn = reactive<BasicColumn[]>([ | ||
20 | - { title: '属性', dataIndex: 'attribute', width: 80, ellipsis: true }, | ||
21 | - { title: '值', dataIndex: 'price', width: 80, ellipsis: true }, | ||
22 | - { title: '时间', dataIndex: 'time', width: 80, ellipsis: true }, | ||
23 | - ]); | ||
24 | - | ||
25 | - const [registerTable] = useTable({ | ||
26 | - showIndexColumn: false, | ||
27 | - showTableSetting: false, | ||
28 | - dataSource: realTimeList, | ||
29 | - canResize: true, | ||
30 | - maxHeight: 144, | ||
31 | - size: 'small', | ||
32 | - columns: realTimeColumn as any, | 25 | + const [registerTable, { setTableData, setColumns, getDataSource, redoHeight, setProps }] = |
26 | + useTable({ showIndexColumn: false, showTableSetting: false, canResize: true, size: 'small' }); | ||
27 | + | ||
28 | + const getDesign = computed(() => { | ||
29 | + const { persetOption, option } = props.config; | ||
30 | + const { dataSource = [] } = option || {}; | ||
31 | + const { unit: presetUnit, showDeviceName: presetShowDeviceName } = persetOption || {}; | ||
32 | + const columns: BasicColumn[] = dataSource.map((item) => ({ | ||
33 | + title: item.attributeRename || item.attributeName || item.attribute, | ||
34 | + dataIndex: item.attribute, | ||
35 | + width: 80, | ||
36 | + ellipsis: true, | ||
37 | + format(text, record) { | ||
38 | + const value = text ? text + (record.unit || '') : ''; | ||
39 | + return value; | ||
40 | + }, | ||
41 | + })); | ||
42 | + columns.push({ | ||
43 | + title: '时间', | ||
44 | + dataIndex: 'time', | ||
45 | + width: 110, | ||
46 | + ellipsis: true, | ||
47 | + format(text) { | ||
48 | + return formatToDateTime(text, 'YYYY-MM-DD HH:mm:ss'); | ||
49 | + }, | ||
50 | + }); | ||
51 | + return { | ||
52 | + columns, | ||
53 | + dataSource: dataSource?.map((item) => { | ||
54 | + const { unit, showDeviceName } = item.componentInfo || {}; | ||
55 | + const { attribute, attributeName, attributeRename, deviceName, deviceRename, deviceId } = | ||
56 | + item; | ||
57 | + return { | ||
58 | + unit: unit ?? presetUnit, | ||
59 | + attribute, | ||
60 | + attributeRename, | ||
61 | + attributeName, | ||
62 | + showDeviceName: showDeviceName ?? presetShowDeviceName, | ||
63 | + deviceName, | ||
64 | + deviceRename, | ||
65 | + id: deviceId, | ||
66 | + }; | ||
67 | + }), | ||
68 | + }; | ||
33 | }); | 69 | }); |
34 | 70 | ||
35 | - const updateFn: MultipleDataFetchUpdateFn = () => {}; | 71 | + const { forEachGroupMessage } = useReceiveMessage(); |
72 | + const { getNumberValue } = useReceiveValue(); | ||
73 | + | ||
74 | + const updateFn: MultipleDataFetchUpdateFn = async (message, deviceId, attribute) => { | ||
75 | + const list: IList = {}; | ||
76 | + forEachGroupMessage(message, deviceId, attribute, (attribute, value, timespan) => { | ||
77 | + list[attribute] = getNumberValue(value); | ||
78 | + list.time = timespan || list.time; | ||
79 | + }); | ||
80 | + await nextTick(); | ||
81 | + setTableData([list, ...toRaw(unref(getDataSource()))]); | ||
82 | + }; | ||
36 | 83 | ||
37 | useMultipleDataFetch(props, updateFn); | 84 | useMultipleDataFetch(props, updateFn); |
85 | + onMounted(async () => { | ||
86 | + setColumns( | ||
87 | + props.config.option.dataSource | ||
88 | + ? unref(getDesign).columns | ||
89 | + : [ | ||
90 | + { title: '属性', dataIndex: 'attribute', width: 80, ellipsis: true }, | ||
91 | + { title: '时间', dataIndex: 'time', width: 80, ellipsis: true }, | ||
92 | + ] | ||
93 | + ); | ||
94 | + !props.config.option.dataSource && | ||
95 | + setTableData([ | ||
96 | + { attribute: '温度', time: '2023-06-29' }, | ||
97 | + { attribute: '湿度', time: '2023-06-29' }, | ||
98 | + { attribute: '湿度', time: '2023-06-29' }, | ||
99 | + ]); | ||
100 | + | ||
101 | + await nextTick(); | ||
102 | + resize(); | ||
103 | + }); | ||
104 | + const resize = async () => { | ||
105 | + const { height } = unref(getContainerSize); | ||
106 | + height && setProps({ scroll: { x: 190, y: height - 120 } }); | ||
107 | + | ||
108 | + await nextTick(); | ||
109 | + redoHeight(); | ||
110 | + }; | ||
111 | + | ||
112 | + const { getContainerSize } = useComponentScale(props, resize); | ||
38 | </script> | 113 | </script> |
39 | 114 | ||
40 | <template> | 115 | <template> |
41 | - <main class="flex flex-col justify-center items-center"> | 116 | + <main class="flex flex-col justify-center items-center w-full h-full"> |
42 | <DeviceName :config="config" /> | 117 | <DeviceName :config="config" /> |
43 | - <div> | 118 | + <div class="w-full h-full"> |
44 | <!-- <PageWrapper> --> | 119 | <!-- <PageWrapper> --> |
45 | - <BasicTable @register="registerTable" /> | 120 | + <BasicTable autoCreateKey style="flex: auto" @register="registerTable" /> |
46 | <!-- </PageWrapper> --> | 121 | <!-- </PageWrapper> --> |
47 | </div> | 122 | </div> |
48 | </main> | 123 | </main> |
@@ -8,13 +8,6 @@ | @@ -8,13 +8,6 @@ | ||
8 | const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({ | 8 | const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({ |
9 | schemas: [ | 9 | schemas: [ |
10 | { | 10 | { |
11 | - field: ComponentConfigFieldEnum.FONT_COLOR, | ||
12 | - label: '数值字体颜色', | ||
13 | - component: 'ColorPicker', | ||
14 | - changeEvent: 'update:value', | ||
15 | - defaultValue: option.fontColor, | ||
16 | - }, | ||
17 | - { | ||
18 | field: ComponentConfigFieldEnum.UNIT, | 11 | field: ComponentConfigFieldEnum.UNIT, |
19 | label: '数值单位', | 12 | label: '数值单位', |
20 | component: 'Input', | 13 | component: 'Input', |
1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
2 | import { EChartsOption, ECharts, init } from 'echarts'; | 2 | import { EChartsOption, ECharts, init } from 'echarts'; |
3 | - import { unref, ref, onMounted, nextTick, toRaw } from 'vue'; | 3 | + import { unref, ref, onMounted, nextTick, toRaw, computed } from 'vue'; |
4 | import { ComponentPropsConfigType } from '../../../index.type'; | 4 | import { ComponentPropsConfigType } from '../../../index.type'; |
5 | import { option } from './config'; | 5 | import { option } from './config'; |
6 | import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime'; | 6 | import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime'; |
7 | import { useComponentScale } from '../../../hook/useComponentScale'; | 7 | import { useComponentScale } from '../../../hook/useComponentScale'; |
8 | import { DeviceName } from '/@/views/visual/commonComponents/DeviceName'; | 8 | import { DeviceName } from '/@/views/visual/commonComponents/DeviceName'; |
9 | - import { dateFormat } from '/@/utils/common/compUtils'; | 9 | + import { useIntervalFn } from '@vueuse/core'; |
10 | import { useMultipleDataFetch } from '../../../hook/socket/useSocket'; | 10 | import { useMultipleDataFetch } from '../../../hook/socket/useSocket'; |
11 | import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type'; | 11 | import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type'; |
12 | + import { useReceiveMessage } from '../../../hook/useReceiveMessage'; | ||
13 | + import { formatToDateTime } from '/@/utils/dateUtil'; | ||
14 | + interface IList { | ||
15 | + [key: string]: string | number; | ||
16 | + } | ||
12 | 17 | ||
13 | const props = defineProps<{ | 18 | const props = defineProps<{ |
14 | config: ComponentPropsConfigType<typeof option>; | 19 | config: ComponentPropsConfigType<typeof option>; |
15 | }>(); | 20 | }>(); |
16 | - | ||
17 | const chartRefEl = ref<Nullable<HTMLDivElement>>(null); | 21 | const chartRefEl = ref<Nullable<HTMLDivElement>>(null); |
18 | 22 | ||
19 | const chartInstance = ref<Nullable<ECharts>>(null); | 23 | const chartInstance = ref<Nullable<ECharts>>(null); |
20 | - | ||
21 | const time = ref<Nullable<number>>(null); | 24 | const time = ref<Nullable<number>>(null); |
22 | 25 | ||
23 | - function randomData() { | ||
24 | - now.value = now.value + oneDay; | ||
25 | - const newTime = dateFormat(unref(now), 'MM-dd hh:mm:ss'); | ||
26 | - value = value + Math.random() * 21 - 10; | ||
27 | - return { | ||
28 | - name: newTime, | ||
29 | - value: [newTime, Math.round(value)], | ||
30 | - }; | ||
31 | - } | ||
32 | - const data = ref<any>([]); | ||
33 | - const now = ref<number>(1688026367000); | ||
34 | - let oneDay = 5000; //间隔秒数 | ||
35 | - let value = Math.random() * 1000; | ||
36 | - for (let i = 0; i < 10; i++) { | ||
37 | - data.value.push(randomData()); | ||
38 | - } | ||
39 | - setInterval(function () { | ||
40 | - for (let i = 0; i < 1; i++) { | ||
41 | - data.value.shift(); | ||
42 | - data.value.push(randomData()); | ||
43 | - } | ||
44 | - unref(chartInstance)?.setOption(options()); | ||
45 | - }, 1000); | ||
46 | - | ||
47 | - // const getDesign = computed(() => { | ||
48 | - // const { persetOption, option } = props.config; | ||
49 | - // const { dataSource = [] } = option || {}; | ||
50 | - // const { unit: presetUnit, fontColor: presetFontColor } = persetOption || {}; | ||
51 | - // return { | ||
52 | - // dataSource: dataSource?.map((item) => { | ||
53 | - // const { unit, fontColor } = item.componentInfo || {}; | ||
54 | - // const { attribute, attributeRename } = item; | ||
55 | - // return { | ||
56 | - // unit: unit ?? presetUnit, | ||
57 | - // fontColor: fontColor ?? presetFontColor, | ||
58 | - // attribute, | ||
59 | - // attributeRename, | ||
60 | - // }; | ||
61 | - // }), | ||
62 | - // }; | ||
63 | - // }); | 26 | + const updateInterval = ref<number>(1000); //默认每秒更新一次 |
27 | + const maxDataPoints = ref<number>(10); //默认每秒显示10个数据点 | ||
28 | + | ||
29 | + const chartData = ref<{ time: string | number; value: number }[]>([]); | ||
30 | + const legendData = ref<string[]>(['温度']); | ||
31 | + const timeList = ref<string[]>([]); | ||
32 | + | ||
33 | + const generateRandomData = () => { | ||
34 | + const minValue = 0; | ||
35 | + const maxValue = 100; | ||
36 | + const time = new Date().toLocaleTimeString(); | ||
37 | + const value = Math.floor(Math.random() * (maxValue - minValue + 1)) + minValue; | ||
38 | + return { time, value, name: '温度' }; | ||
39 | + }; | ||
64 | 40 | ||
65 | const options = (): EChartsOption => { | 41 | const options = (): EChartsOption => { |
66 | - // getStageColor(gradientInfo); | ||
67 | return { | 42 | return { |
68 | - trigger: 'axis', | ||
69 | - axisPointer: { | ||
70 | - lineStyle: { | ||
71 | - width: 1, | ||
72 | - color: '#019680', | ||
73 | - }, | ||
74 | - }, | ||
75 | tooltip: { | 43 | tooltip: { |
76 | - show: true, | 44 | + // trigger: 'axis', |
77 | }, | 45 | }, |
78 | legend: { | 46 | legend: { |
79 | top: '10%', | 47 | top: '10%', |
80 | left: 'center', | 48 | left: 'center', |
81 | - data: ['温度', '湿度'], | 49 | + data: ['温度'], |
82 | }, | 50 | }, |
83 | grid: { | 51 | grid: { |
84 | - top: '45%', | ||
85 | - left: '20%', | ||
86 | - bottom: '14%', | 52 | + top: '30%', |
53 | + left: '6%', | ||
54 | + right: '16%', | ||
55 | + bottom: '1%', | ||
87 | containLabel: true, | 56 | containLabel: true, |
88 | }, | 57 | }, |
89 | xAxis: { | 58 | xAxis: { |
90 | type: 'category', | 59 | type: 'category', |
91 | - splitLine: { | ||
92 | - show: true, | ||
93 | - lineStyle: { | ||
94 | - width: 1, | ||
95 | - type: 'solid', | ||
96 | - color: 'rgba(226,226,226,0.5)', | ||
97 | - }, | ||
98 | - }, | ||
99 | - data: toRaw(unref(data)).map((item) => item.name), | 60 | + // boundaryGap: false, |
100 | }, | 61 | }, |
101 | yAxis: { | 62 | yAxis: { |
102 | type: 'value', | 63 | type: 'value', |
103 | - boundaryGap: [0, '50%'], | ||
104 | - splitLine: { | ||
105 | - show: false, | ||
106 | - }, | 64 | + boundaryGap: [0, '100%'], |
107 | }, | 65 | }, |
108 | - series: [ | ||
109 | - { | ||
110 | - type: 'line', | ||
111 | - name: '温度', | ||
112 | - stack: 'Total', | ||
113 | - data: unref(data).map((item) => item.value), | ||
114 | - }, | ||
115 | - { | ||
116 | - type: 'line', | ||
117 | - name: '湿度', | ||
118 | - stack: 'Total', | ||
119 | - data: unref(data).map((item) => { | ||
120 | - return Number(item.value[1]) * 0.99; | ||
121 | - }), | ||
122 | - }, | ||
123 | - ], | 66 | + series: [{ type: 'line', name: '温度', data: [] }], |
124 | }; | 67 | }; |
125 | }; | 68 | }; |
126 | 69 | ||
70 | + const random = () => { | ||
71 | + useIntervalFn(() => { | ||
72 | + const newData = generateRandomData(); | ||
73 | + chartData.value.push(newData); | ||
74 | + if (unref(chartData).length > maxDataPoints.value) { | ||
75 | + chartData.value.shift(); | ||
76 | + } | ||
77 | + unref(chartInstance)?.setOption({ | ||
78 | + xAxis: { | ||
79 | + data: toRaw(unref(chartData).map((data) => data.time)), | ||
80 | + }, | ||
81 | + series: [{ data: toRaw(unref(chartData).map((item) => item.value)) }], | ||
82 | + }); | ||
83 | + }, updateInterval); | ||
84 | + }; | ||
85 | + | ||
86 | + const getDesign = computed(() => { | ||
87 | + const { persetOption, option } = props.config; | ||
88 | + const { dataSource = [] } = option || {}; | ||
89 | + const { unit: presetUnit, fontColor: presetFontColor } = persetOption || {}; | ||
90 | + | ||
91 | + return { | ||
92 | + dataSource: dataSource?.map((item) => { | ||
93 | + const { unit, showDeviceName, fontColor } = item.componentInfo || {}; | ||
94 | + console.log(item, 'item'); | ||
95 | + const { attribute, attributeRename, deviceId, attributeName, deviceName, deviceRename } = | ||
96 | + item; | ||
97 | + return { | ||
98 | + unit: unit ?? presetUnit, | ||
99 | + fontColor: fontColor ?? presetFontColor, | ||
100 | + attribute, | ||
101 | + attributeName, | ||
102 | + attributeRename, | ||
103 | + showDeviceName, | ||
104 | + deviceName, | ||
105 | + deviceRename, | ||
106 | + id: deviceId, | ||
107 | + }; | ||
108 | + }), | ||
109 | + }; | ||
110 | + }); | ||
111 | + | ||
127 | const initial = () => { | 112 | const initial = () => { |
128 | chartInstance.value = init(unref(chartRefEl)! as HTMLElement); | 113 | chartInstance.value = init(unref(chartRefEl)! as HTMLElement); |
129 | chartInstance.value.setOption(options()); | 114 | chartInstance.value.setOption(options()); |
130 | }; | 115 | }; |
131 | 116 | ||
132 | - // const updateChart = (data: SeriesOption['data'], yAxisData) => { | ||
133 | - // unref(chartInstance)?.setOption({ | ||
134 | - // series: [{ data }], | ||
135 | - // yAxis: { data: yAxisData }, | ||
136 | - // } as EChartsOption); | ||
137 | - // }; | ||
138 | - | ||
139 | - const updateFn: MultipleDataFetchUpdateFn = () => { | ||
140 | - // console.log(message, 'message'); | ||
141 | - return {}; | 117 | + const series = ref( |
118 | + unref(getDesign).dataSource.map((item) => { | ||
119 | + return { | ||
120 | + type: 'line', | ||
121 | + name: `${item.showDeviceName || item.deviceRename || item.deviceName} - ${ | ||
122 | + item.attributeRename || item.attributeName | ||
123 | + }`, | ||
124 | + data: [] as { name: string; value: number }[], | ||
125 | + id: item.id, | ||
126 | + attribute: item.attribute, | ||
127 | + }; | ||
128 | + }) | ||
129 | + ); | ||
130 | + | ||
131 | + const { forEachGroupMessage } = useReceiveMessage(); | ||
132 | + | ||
133 | + const updateFn: MultipleDataFetchUpdateFn = async (message, deviceId, attribute) => { | ||
134 | + legendData.value = unref(getDesign).dataSource.map((item) => { | ||
135 | + return `${item.deviceRename || item.deviceName} - ${ | ||
136 | + item.attributeRename || item.attributeName | ||
137 | + }`; | ||
138 | + }); | ||
139 | + const list: IList | any = {}; | ||
140 | + forEachGroupMessage(message, deviceId, attribute, (attribute, value, timespan) => { | ||
141 | + list.time = timespan || list.time; | ||
142 | + series.value.forEach((item) => { | ||
143 | + if (item.id === deviceId && item.attribute === attribute) { | ||
144 | + item.data.push({ | ||
145 | + name: formatToDateTime(list.time, 'HH:mm:ss'), | ||
146 | + value: value, | ||
147 | + }); | ||
148 | + if (item.data.length > unref(maxDataPoints)) { | ||
149 | + item.data.shift(); | ||
150 | + } | ||
151 | + } | ||
152 | + }); | ||
153 | + }); | ||
154 | + list.time && timeList.value.push(formatToDateTime(list.time, 'HH:mm:ss')); | ||
155 | + if (unref(timeList).length > unref(maxDataPoints)) { | ||
156 | + timeList.value.shift(); | ||
157 | + } | ||
158 | + await nextTick(); | ||
159 | + unref(chartInstance)?.setOption({ | ||
160 | + xAxis: { | ||
161 | + data: toRaw(unref(timeList)), | ||
162 | + }, | ||
163 | + legend: { | ||
164 | + data: toRaw(unref(legendData)), | ||
165 | + }, | ||
166 | + series: toRaw( | ||
167 | + unref(series).map((item) => { | ||
168 | + const { type, name, data } = item; | ||
169 | + return { | ||
170 | + type, | ||
171 | + name, | ||
172 | + data, | ||
173 | + }; | ||
174 | + }) | ||
175 | + ), | ||
176 | + }); | ||
142 | }; | 177 | }; |
143 | 178 | ||
144 | useMultipleDataFetch(props, updateFn); | 179 | useMultipleDataFetch(props, updateFn); |
145 | - | ||
146 | onMounted(() => { | 180 | onMounted(() => { |
147 | initial(); | 181 | initial(); |
148 | - // !props.config.option.uuid && randomFn(); | ||
149 | - !props.config.option.uuid; | 182 | + !props.config.option.dataSource?.length && random(); |
150 | }); | 183 | }); |
151 | 184 | ||
152 | const resize = async () => { | 185 | const resize = async () => { |
153 | await nextTick(); | 186 | await nextTick(); |
154 | 187 | ||
155 | // 修改echarts大小 | 188 | // 修改echarts大小 |
156 | - unref(chartInstance)?.setOption({ | ||
157 | - legend: { | ||
158 | - textStyle: { | ||
159 | - fontSize: 14 * unref(getRatio), | ||
160 | - }, | ||
161 | - }, | ||
162 | - } as EChartsOption); | 189 | + // unref(chartInstance)?.setOption({ |
190 | + // legend: { | ||
191 | + // textStyle: { | ||
192 | + // fontSize: 14 * unref(getRatio), | ||
193 | + // }, | ||
194 | + // }, | ||
195 | + // } as EChartsOption); | ||
163 | unref(chartInstance)?.resize(); | 196 | unref(chartInstance)?.resize(); |
164 | }; | 197 | }; |
165 | 198 | ||
166 | - const { getRatio } = useComponentScale(props, resize); | 199 | + useComponentScale(props, resize); |
167 | </script> | 200 | </script> |
168 | 201 | ||
169 | <template> | 202 | <template> |
1 | -import { StatisticsComponent1Config } from './StatisticsComponent1'; | ||
2 | -import { StatisticsComponent2Config } from './StatisticsComponent2'; | ||
3 | -// import { StatisticsComponent3Config } from './StatisticsComponent3'; | ||
4 | -// import { StatisticsComponent4Config } from './StatisticsComponent4'; | ||
5 | -// import { StatisticsComponent5Config } from './StatisticsComponent5'; | ||
6 | -// import { StatisticsComponent6Config } from './StatisticsComponent6'; | 1 | +// import { StatisticsComponent1Config } from './StatisticsComponent1'; |
2 | +// import { StatisticsComponent2Config } from './StatisticsComponent2'; | ||
3 | +import { StatisticsComponent3Config } from './StatisticsComponent3'; | ||
4 | +import { StatisticsComponent4Config } from './StatisticsComponent4'; | ||
5 | +import { StatisticsComponent5Config } from './StatisticsComponent5'; | ||
6 | +import { StatisticsComponent6Config } from './StatisticsComponent6'; | ||
7 | 7 | ||
8 | export const STATISTICSList = [ | 8 | export const STATISTICSList = [ |
9 | - StatisticsComponent1Config, | ||
10 | - StatisticsComponent2Config, | ||
11 | - // StatisticsComponent3Config, | ||
12 | - // StatisticsComponent4Config, | ||
13 | - // StatisticsComponent5Config, | ||
14 | - // StatisticsComponent6Config, | 9 | + // StatisticsComponent1Config, |
10 | + // StatisticsComponent2Config, | ||
11 | + StatisticsComponent3Config, | ||
12 | + StatisticsComponent4Config, | ||
13 | + StatisticsComponent5Config, | ||
14 | + StatisticsComponent6Config, | ||
15 | ]; | 15 | ]; |
@@ -180,6 +180,7 @@ class Subscriber { | @@ -180,6 +180,7 @@ class Subscriber { | ||
180 | } | 180 | } |
181 | 181 | ||
182 | if (isEntityDataUpdateMsg(message)) { | 182 | if (isEntityDataUpdateMsg(message)) { |
183 | + this.triggerEntityDataMessage(message); | ||
183 | return; | 184 | return; |
184 | } | 185 | } |
185 | 186 | ||
@@ -248,6 +249,20 @@ class Subscriber { | @@ -248,6 +249,20 @@ class Subscriber { | ||
248 | return; | 249 | return; |
249 | } | 250 | } |
250 | } | 251 | } |
252 | + triggerEntityDataMessage(message: ReceiveEntityDataMessageType) { | ||
253 | + const { cmdId } = message; | ||
254 | + const isCustomSubscribeMessage = this.customSubscribeMap.get(cmdId); | ||
255 | + if (isCustomSubscribeMessage) { | ||
256 | + const updateFn = isCustomSubscribeMessage.updateFn; | ||
257 | + try { | ||
258 | + updateFn?.(message); | ||
259 | + } catch (error) { | ||
260 | + console.error(`Custom subscribe message invoke update function failed`); | ||
261 | + throw error; | ||
262 | + } | ||
263 | + return; | ||
264 | + } | ||
265 | + } | ||
251 | } | 266 | } |
252 | 267 | ||
253 | const subscriber = new Subscriber(); | 268 | const subscriber = new Subscriber(); |
@@ -426,4 +441,8 @@ export const useCustomDataFetch = ( | @@ -426,4 +441,8 @@ export const useCustomDataFetch = ( | ||
426 | immediate: true, | 441 | immediate: true, |
427 | } | 442 | } |
428 | ); | 443 | ); |
444 | + | ||
445 | + return { | ||
446 | + getNextSubscribeId: subscriber.getNextSubscribeId, | ||
447 | + }; | ||
429 | }; | 448 | }; |
@@ -3,6 +3,15 @@ import { ComponentPropsConfigType } from '../index.type'; | @@ -3,6 +3,15 @@ import { ComponentPropsConfigType } from '../index.type'; | ||
3 | import { componentOptionsInitConfig } from '../publicConfig'; | 3 | import { componentOptionsInitConfig } from '../publicConfig'; |
4 | 4 | ||
5 | export const useComponentScale = (props: { config: ComponentPropsConfigType }, onScale?: Fn) => { | 5 | export const useComponentScale = (props: { config: ComponentPropsConfigType }, onScale?: Fn) => { |
6 | + const getContainerSize = computed(() => { | ||
7 | + const { option } = props.config; | ||
8 | + const { widthPx, heightPx } = option; | ||
9 | + return { | ||
10 | + width: widthPx, | ||
11 | + height: heightPx, | ||
12 | + }; | ||
13 | + }); | ||
14 | + | ||
6 | const getRatio = computed(() => { | 15 | const getRatio = computed(() => { |
7 | try { | 16 | try { |
8 | const { option, attr } = props.config; | 17 | const { option, attr } = props.config; |
@@ -47,5 +56,5 @@ export const useComponentScale = (props: { config: ComponentPropsConfigType }, o | @@ -47,5 +56,5 @@ export const useComponentScale = (props: { config: ComponentPropsConfigType }, o | ||
47 | onScale?.(); | 56 | onScale?.(); |
48 | }); | 57 | }); |
49 | 58 | ||
50 | - return { getScale, getScaleRadio, getRatio }; | 59 | + return { getScale, getScaleRadio, getRatio, getContainerSize }; |
51 | }; | 60 | }; |
@@ -21,6 +21,7 @@ export enum PackagesCategoryNameEnum { | @@ -21,6 +21,7 @@ export enum PackagesCategoryNameEnum { | ||
21 | MAP = '地图组件', | 21 | MAP = '地图组件', |
22 | FLOWMETER = '流量计', | 22 | FLOWMETER = '流量计', |
23 | STATISTICS = '统计', | 23 | STATISTICS = '统计', |
24 | + ALARM = '告警', | ||
24 | OTHER = '其他', | 25 | OTHER = '其他', |
25 | } | 26 | } |
26 | 27 | ||
@@ -35,6 +36,7 @@ export enum PackagesCategoryEnum { | @@ -35,6 +36,7 @@ export enum PackagesCategoryEnum { | ||
35 | MAP = 'MAP', | 36 | MAP = 'MAP', |
36 | FLOWMETER = 'FLOWMETER', | 37 | FLOWMETER = 'FLOWMETER', |
37 | STATISTICS = 'STATISTICS', | 38 | STATISTICS = 'STATISTICS', |
39 | + ALARM = 'ALARM', | ||
38 | OTHER = 'OTHER', | 40 | OTHER = 'OTHER', |
39 | } | 41 | } |
40 | 42 | ||
@@ -137,6 +139,7 @@ export interface PackagesType { | @@ -137,6 +139,7 @@ export interface PackagesType { | ||
137 | [PackagesCategoryEnum.CONTROL]: ConfigType[]; | 139 | [PackagesCategoryEnum.CONTROL]: ConfigType[]; |
138 | [PackagesCategoryEnum.MAP]: ConfigType[]; | 140 | [PackagesCategoryEnum.MAP]: ConfigType[]; |
139 | [PackagesCategoryEnum.FLOWMETER]: ConfigType[]; | 141 | [PackagesCategoryEnum.FLOWMETER]: ConfigType[]; |
140 | - // [PackagesCategoryEnum.STATISTICS]: ConfigType[]; | 142 | + [PackagesCategoryEnum.STATISTICS]: ConfigType[]; |
141 | [PackagesCategoryEnum.OTHER]: ConfigType[]; | 143 | [PackagesCategoryEnum.OTHER]: ConfigType[]; |
144 | + [PackagesCategoryEnum.ALARM]: ConfigType[]; | ||
142 | } | 145 | } |
@@ -5,7 +5,8 @@ import { MapList } from './components/Map'; | @@ -5,7 +5,8 @@ import { MapList } from './components/Map'; | ||
5 | import { OtherList } from './components/Other'; | 5 | import { OtherList } from './components/Other'; |
6 | // import { PictureList } from './components/Picture'; | 6 | // import { PictureList } from './components/Picture'; |
7 | import { TextList } from './components/Text'; | 7 | import { TextList } from './components/Text'; |
8 | -// import { STATISTICSList } from './components/Statistics'; | 8 | +import { STATISTICSList } from './components/Statistics'; |
9 | +import { AlarmList } from './components/Alarm'; | ||
9 | import { PackagesCategoryEnum, PackagesType } from './index.type'; | 10 | import { PackagesCategoryEnum, PackagesType } from './index.type'; |
10 | 11 | ||
11 | export const packageList: PackagesType = { | 12 | export const packageList: PackagesType = { |
@@ -15,11 +16,12 @@ export const packageList: PackagesType = { | @@ -15,11 +16,12 @@ export const packageList: PackagesType = { | ||
15 | [PackagesCategoryEnum.CONTROL]: ControlList, | 16 | [PackagesCategoryEnum.CONTROL]: ControlList, |
16 | [PackagesCategoryEnum.MAP]: MapList, | 17 | [PackagesCategoryEnum.MAP]: MapList, |
17 | [PackagesCategoryEnum.FLOWMETER]: FlowmeterList, | 18 | [PackagesCategoryEnum.FLOWMETER]: FlowmeterList, |
18 | - // [PackagesCategoryEnum.STATISTICS]: STATISTICSList, | 19 | + [PackagesCategoryEnum.STATISTICS]: STATISTICSList, |
20 | + [PackagesCategoryEnum.ALARM]: AlarmList, | ||
19 | [PackagesCategoryEnum.OTHER]: OtherList, | 21 | [PackagesCategoryEnum.OTHER]: OtherList, |
20 | }; | 22 | }; |
21 | 23 | ||
22 | /** | 24 | /** |
23 | * @description | 25 | * @description |
24 | */ | 26 | */ |
25 | -export const CUSTOM_SUBSCRIBE_MESSAGE_COMPONENT_KEY_LIST: string[] = []; | 27 | +export const CUSTOM_SUBSCRIBE_MESSAGE_COMPONENT_KEY_LIST: string[] = ['DeviceAlarmHistory']; |
1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
2 | - import { ComponentPropsConfigType, DataFetchUpdateFn } from '/@/views/visual/packages/index.type'; | 2 | + import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type'; |
3 | import { option } from './config'; | 3 | import { option } from './config'; |
4 | - import { useDataFetch } from '/@/views/visual/packages/hook/useSocket'; | 4 | + import { useDataFetch } from '../hook/socket/useSocket'; |
5 | + import { DataFetchUpdateFn } from '../hook/socket/useSocket.type'; | ||
5 | 6 | ||
6 | const props = defineProps<{ | 7 | const props = defineProps<{ |
7 | config: ComponentPropsConfigType<typeof option>; | 8 | config: ComponentPropsConfigType<typeof option>; |
@@ -6,7 +6,7 @@ | @@ -6,7 +6,7 @@ | ||
6 | import { DataActionModeEnum } from '/@/enums/toolEnum'; | 6 | import { DataActionModeEnum } from '/@/enums/toolEnum'; |
7 | import { VisualComponentPermission } from '../..'; | 7 | import { VisualComponentPermission } from '../..'; |
8 | import { Tooltip } from 'ant-design-vue'; | 8 | import { Tooltip } from 'ant-design-vue'; |
9 | - import { MoreOutlined, AreaChartOutlined } from '@ant-design/icons-vue'; | 9 | + import { MoreOutlined, AreaChartOutlined, FieldTimeOutlined } from '@ant-design/icons-vue'; |
10 | import { WidgetDataType } from '../../hooks/useDataSource'; | 10 | import { WidgetDataType } from '../../hooks/useDataSource'; |
11 | import { useMessage } from '/@/hooks/web/useMessage'; | 11 | import { useMessage } from '/@/hooks/web/useMessage'; |
12 | import { addDataComponent, deleteDataComponent } from '/@/api/dataBoard'; | 12 | import { addDataComponent, deleteDataComponent } from '/@/api/dataBoard'; |
@@ -29,6 +29,7 @@ | @@ -29,6 +29,7 @@ | ||
29 | (event: 'ok'): void; | 29 | (event: 'ok'): void; |
30 | (event: 'update', data: WidgetDataType): void; | 30 | (event: 'update', data: WidgetDataType): void; |
31 | (event: 'openTrend', data: WidgetDataType): void; | 31 | (event: 'openTrend', data: WidgetDataType): void; |
32 | + (event: 'openAlarm', data: WidgetDataType): void; | ||
32 | }>(); | 33 | }>(); |
33 | 34 | ||
34 | const { isCustomerUser } = useRole(); | 35 | const { isCustomerUser } = useRole(); |
@@ -88,6 +89,15 @@ | @@ -88,6 +89,15 @@ | ||
88 | return isBoolean(flag) ? flag : true; | 89 | return isBoolean(flag) ? flag : true; |
89 | }); | 90 | }); |
90 | 91 | ||
92 | + const isAlarm = computed(() => { | ||
93 | + const frontId = props.sourceInfo.frontId; | ||
94 | + if (frontId == 'DeviceAlarm' || frontId == 'DeviceAlarmHistory') { | ||
95 | + return true; | ||
96 | + } else { | ||
97 | + return false; | ||
98 | + } | ||
99 | + }); | ||
100 | + | ||
91 | async function handleCopy() { | 101 | async function handleCopy() { |
92 | const id = props.sourceInfo.id; | 102 | const id = props.sourceInfo.id; |
93 | const copyRecord = props.rawDataSource.componentData.find((item) => item.id === id); | 103 | const copyRecord = props.rawDataSource.componentData.find((item) => item.id === id); |
@@ -120,6 +130,10 @@ | @@ -120,6 +130,10 @@ | ||
120 | const handleOpenTrendModal = () => { | 130 | const handleOpenTrendModal = () => { |
121 | emit('openTrend', toRaw(props.sourceInfo)); | 131 | emit('openTrend', toRaw(props.sourceInfo)); |
122 | }; | 132 | }; |
133 | + | ||
134 | + const handleAlarmModal = () => { | ||
135 | + emit('openAlarm', toRaw(props.sourceInfo)); | ||
136 | + }; | ||
123 | </script> | 137 | </script> |
124 | 138 | ||
125 | <template> | 139 | <template> |
@@ -132,8 +146,9 @@ | @@ -132,8 +146,9 @@ | ||
132 | </Tooltip> | 146 | </Tooltip> |
133 | 147 | ||
134 | <div v-if="!getIsSharePage" class="flex items-center w-16 justify-evenly"> | 148 | <div v-if="!getIsSharePage" class="flex items-center w-16 justify-evenly"> |
135 | - <Tooltip v-if="!isCustomerUser && hasTrendQueryIcon" title="趋势"> | ||
136 | - <AreaChartOutlined class="text-lg" @click="handleOpenTrendModal" /> | 149 | + <Tooltip v-if="!isCustomerUser && hasTrendQueryIcon" :title="isAlarm ? '时间' : '趋势'"> |
150 | + <FieldTimeOutlined v-if="isAlarm" class="text-lg" @click="handleAlarmModal" /> | ||
151 | + <AreaChartOutlined v-else class="text-lg" @click="handleOpenTrendModal" /> | ||
137 | </Tooltip> | 152 | </Tooltip> |
138 | <AuthDropDown | 153 | <AuthDropDown |
139 | v-if="!isCustomerUser && dropMenuList.length" | 154 | v-if="!isCustomerUser && dropMenuList.length" |
1 | +import moment from 'moment'; | ||
2 | +import { FormSchema } from '/@/components/Form'; | ||
3 | +import { ColEx } from '/@/components/Form/src/types'; | ||
4 | +import { useGridLayout } from '/@/hooks/component/useGridLayout'; | ||
5 | +import { intervalOption } from '/@/views/device/localtion/cpns/TimePeriodForm/helper'; | ||
6 | +export enum QueryWay { | ||
7 | + LATEST = 'latest', | ||
8 | + TIME_PERIOD = 'timePeriod', | ||
9 | +} | ||
10 | + | ||
11 | +export enum SchemaFiled { | ||
12 | + DEVICE_ID = 'deviceId', | ||
13 | + WAY = 'way', | ||
14 | + TIME_PERIOD = 'timePeriod', | ||
15 | + KEYS = 'keys', | ||
16 | + DATE_RANGE = 'dataRange', | ||
17 | + START_TS = 'startTs', | ||
18 | + END_TS = 'endTs', | ||
19 | + INTERVAL = 'interval', | ||
20 | + LIMIT = 'limit', | ||
21 | + AGG = 'agg', | ||
22 | + ORDER_BY = 'orderBy', | ||
23 | + PAGE_SIZe = 'pageSize', | ||
24 | +} | ||
25 | + | ||
26 | +export enum AggregateDataEnum { | ||
27 | + MIN = 'MIN', | ||
28 | + MAX = 'MAX', | ||
29 | + AVG = 'AVG', | ||
30 | + SUM = 'SUM', | ||
31 | + COUNT = 'COUNT', | ||
32 | + NONE = 'NONE', | ||
33 | +} | ||
34 | +export const formSchema = (): FormSchema[] => { | ||
35 | + return [ | ||
36 | + { | ||
37 | + field: SchemaFiled.WAY, | ||
38 | + label: '查询方式', | ||
39 | + component: 'RadioGroup', | ||
40 | + defaultValue: QueryWay.LATEST, | ||
41 | + componentProps({ formActionType }) { | ||
42 | + const { setFieldsValue } = formActionType; | ||
43 | + return { | ||
44 | + options: [ | ||
45 | + { label: '最后', value: QueryWay.LATEST }, | ||
46 | + { label: '时间段', value: QueryWay.TIME_PERIOD }, | ||
47 | + ], | ||
48 | + onChange(event: ChangeEvent) { | ||
49 | + (event.target as HTMLInputElement).value === QueryWay.LATEST | ||
50 | + ? setFieldsValue({ | ||
51 | + [SchemaFiled.DATE_RANGE]: [], | ||
52 | + [SchemaFiled.START_TS]: null, | ||
53 | + [SchemaFiled.END_TS]: null, | ||
54 | + }) | ||
55 | + : setFieldsValue({ [SchemaFiled.START_TS]: null }); | ||
56 | + }, | ||
57 | + getPopupContainer: () => document.body, | ||
58 | + }; | ||
59 | + }, | ||
60 | + }, | ||
61 | + | ||
62 | + { | ||
63 | + field: SchemaFiled.PAGE_SIZe, | ||
64 | + label: '分页条数', | ||
65 | + component: 'InputNumber', | ||
66 | + defaultValue: 20, | ||
67 | + ifShow: true, | ||
68 | + componentProps() { | ||
69 | + return { | ||
70 | + min: 10, | ||
71 | + max: 50000, | ||
72 | + getPopupContainer: () => document.body, | ||
73 | + }; | ||
74 | + }, | ||
75 | + }, | ||
76 | + { | ||
77 | + field: SchemaFiled.START_TS, | ||
78 | + label: '最后数据', | ||
79 | + component: 'Select', | ||
80 | + ifShow({ values }) { | ||
81 | + return values[SchemaFiled.WAY] === QueryWay.LATEST; | ||
82 | + }, | ||
83 | + defaultValue: 2592000000, | ||
84 | + componentProps({ formActionType }) { | ||
85 | + const { setFieldsValue } = formActionType; | ||
86 | + return { | ||
87 | + options: intervalOption, | ||
88 | + onChange() { | ||
89 | + setFieldsValue({ [SchemaFiled.INTERVAL]: null }); | ||
90 | + }, | ||
91 | + getPopupContainer: () => document.body, | ||
92 | + }; | ||
93 | + }, | ||
94 | + colProps: useGridLayout(2, 2, 2, 2, 2, 2) as unknown as ColEx, | ||
95 | + rules: [{ required: true, message: '最后数据为必选项', type: 'number' }], | ||
96 | + }, | ||
97 | + { | ||
98 | + field: SchemaFiled.DATE_RANGE, | ||
99 | + label: '时间段', | ||
100 | + component: 'RangePicker', | ||
101 | + ifShow({ values }) { | ||
102 | + return values[SchemaFiled.WAY] === QueryWay.TIME_PERIOD; | ||
103 | + }, | ||
104 | + rules: [{ required: true, message: '时间段为必选项' }], | ||
105 | + componentProps({ formActionType }) { | ||
106 | + const { setFieldsValue } = formActionType; | ||
107 | + return { | ||
108 | + showTime: { | ||
109 | + defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')], | ||
110 | + }, | ||
111 | + onChange() { | ||
112 | + setFieldsValue({ [SchemaFiled.INTERVAL]: null }); | ||
113 | + }, | ||
114 | + getPopupContainer: () => document.body, | ||
115 | + }; | ||
116 | + }, | ||
117 | + colProps: useGridLayout(2, 2, 2, 2, 2, 2) as unknown as ColEx, | ||
118 | + }, | ||
119 | + { | ||
120 | + field: SchemaFiled.LIMIT, | ||
121 | + label: '最大条数', | ||
122 | + component: 'InputNumber', | ||
123 | + ifShow({ values }) { | ||
124 | + return values[SchemaFiled.AGG] === AggregateDataEnum.NONE; | ||
125 | + }, | ||
126 | + helpMessage: ['根据查询条件,查出的数据条数不超过这个值'], | ||
127 | + componentProps() { | ||
128 | + return { | ||
129 | + max: 50000, | ||
130 | + min: 7, | ||
131 | + getPopupContainer: () => document.body, | ||
132 | + }; | ||
133 | + }, | ||
134 | + }, | ||
135 | + ]; | ||
136 | +}; |
1 | +export { default as alarmTimeModal } from './index.vue'; |
1 | +<script lang="ts" setup> | ||
2 | + import { ref, unref } from 'vue'; | ||
3 | + import { formSchema, SchemaFiled } from './config'; | ||
4 | + import { useForm } from '/@/components/Form'; | ||
5 | + import { useModalInner } from '/@/components/Modal'; | ||
6 | + import { useGridLayout } from '/@/hooks/component/useGridLayout'; | ||
7 | + import { ColEx } from '/@/components/Form/src/types'; | ||
8 | + import { BasicForm } from '/@/components/Form'; | ||
9 | + import { BasicModal } from '/@/components/Modal'; | ||
10 | + import { nextTick } from 'vue'; | ||
11 | + | ||
12 | + const emit = defineEmits(['register', 'getAlarmForm']); | ||
13 | + // const emit = defineEmits<{ | ||
14 | + // (event: 'getAlarmForm', data: WidgetDataType): void; | ||
15 | + // }>(); | ||
16 | + | ||
17 | + const [registerModal, { closeModal }] = useModalInner(); | ||
18 | + | ||
19 | + const [register, method] = useForm({ | ||
20 | + schemas: formSchema(), | ||
21 | + baseColProps: useGridLayout(1) as unknown as ColEx, | ||
22 | + showSubmitButton: false, | ||
23 | + showResetButton: false, | ||
24 | + rowProps: { | ||
25 | + gutter: 10, | ||
26 | + }, | ||
27 | + labelWidth: 120, | ||
28 | + fieldMapToTime: [ | ||
29 | + [SchemaFiled.DATE_RANGE, [SchemaFiled.START_TS, SchemaFiled.END_TS], 'YYYY-MM-DD HH:ss:mm'], | ||
30 | + ], | ||
31 | + }); | ||
32 | + | ||
33 | + const handleCancel = () => { | ||
34 | + // destory() | ||
35 | + }; | ||
36 | + const alarmForm = ref({ time: 2592000000, pageSize: 10 }); //默认30天 | ||
37 | + const handleSubmit = async () => { | ||
38 | + const { way, pageSize, startTs, endTs } = method.getFieldsValue(); | ||
39 | + if (way == 'timePeriod') { | ||
40 | + alarmForm.value = { | ||
41 | + time: new Date(endTs).getTime() - new Date(startTs).getTime(), | ||
42 | + pageSize, | ||
43 | + }; | ||
44 | + } else { | ||
45 | + alarmForm.value = { | ||
46 | + time: startTs, | ||
47 | + pageSize, | ||
48 | + }; | ||
49 | + } | ||
50 | + emit('getAlarmForm', unref(alarmForm)); | ||
51 | + await nextTick(); | ||
52 | + closeModal(); | ||
53 | + }; | ||
54 | +</script> | ||
55 | + | ||
56 | +<template> | ||
57 | + <BasicModal | ||
58 | + @register="registerModal" | ||
59 | + @cancel="handleCancel" | ||
60 | + @ok="handleSubmit" | ||
61 | + :destroy-on-close="true" | ||
62 | + :show-ok-btn="true" | ||
63 | + cancel-text="关闭" | ||
64 | + width="40%" | ||
65 | + title="历史趋势" | ||
66 | + > | ||
67 | + <section | ||
68 | + class="flex flex-col p-4 h-full w-full min-w-7/10" | ||
69 | + style="color: #f0f2f5; background-color: #f0f2f5" | ||
70 | + > | ||
71 | + <section class="bg-white my-3 p-2"> | ||
72 | + <BasicForm @register="register" /> | ||
73 | + </section> | ||
74 | + </section> | ||
75 | + </BasicModal> | ||
76 | +</template> | ||
77 | + | ||
78 | +<style scoped></style> |
1 | +import { Ref, inject, provide } from 'vue'; | ||
2 | + | ||
3 | +const SymbolKey = Symbol('alarm-info'); | ||
4 | +interface IAlarm { | ||
5 | + pageSize: number; | ||
6 | + time: string | number; | ||
7 | +} | ||
8 | + | ||
9 | +export interface AlarmContextType { | ||
10 | + alarmForm: Ref<IAlarm>; | ||
11 | + getAlarmForm: (value: any) => void; | ||
12 | +} | ||
13 | + | ||
14 | +export const createAlarmContext = (options: AlarmContextType) => { | ||
15 | + provide(SymbolKey, options); | ||
16 | +}; | ||
17 | + | ||
18 | +export const useAlarmContext = () => { | ||
19 | + return inject<AlarmContextType>(SymbolKey) || ({} as Partial<AlarmContextType>); | ||
20 | +}; |
@@ -25,11 +25,13 @@ | @@ -25,11 +25,13 @@ | ||
25 | import { ModalParamsType } from '/#/utils'; | 25 | import { ModalParamsType } from '/#/utils'; |
26 | import { DataActionModeEnum } from '/@/enums/toolEnum'; | 26 | import { DataActionModeEnum } from '/@/enums/toolEnum'; |
27 | import { HistoryTrendModal } from './components/HistoryTrendModal'; | 27 | import { HistoryTrendModal } from './components/HistoryTrendModal'; |
28 | + import { alarmTimeModal } from './components/alarmTimeModal'; | ||
28 | import { watch } from 'vue'; | 29 | import { watch } from 'vue'; |
29 | import { useRootSetting } from '/@/hooks/setting/useRootSetting'; | 30 | import { useRootSetting } from '/@/hooks/setting/useRootSetting'; |
30 | import { ThemeEnum } from '/@/enums/appEnum'; | 31 | import { ThemeEnum } from '/@/enums/appEnum'; |
31 | import { createDataBoardContext } from './hooks/useDataBoardContext'; | 32 | import { createDataBoardContext } from './hooks/useDataBoardContext'; |
32 | import { useSocket } from '/@/views/visual/packages/hook/socket/useSocket'; | 33 | import { useSocket } from '/@/views/visual/packages/hook/socket/useSocket'; |
34 | + import { createAlarmContext } from './hooks/useAlarmTime'; | ||
33 | 35 | ||
34 | const props = defineProps<{ | 36 | const props = defineProps<{ |
35 | value?: Recordable; | 37 | value?: Recordable; |
@@ -73,6 +75,25 @@ | @@ -73,6 +75,25 @@ | ||
73 | openTrendModal(true, { mode: DataActionModeEnum.READ, record: data } as ModalParamsType); | 75 | openTrendModal(true, { mode: DataActionModeEnum.READ, record: data } as ModalParamsType); |
74 | }; | 76 | }; |
75 | 77 | ||
78 | + // 设备告警时间选择 | ||
79 | + const [registerAlarmModal, { openModal: openAlarmModal }] = useModal(); | ||
80 | + const handleOpenAlarm = (data: WidgetDataType) => { | ||
81 | + openAlarmModal(true, { mode: DataActionModeEnum.READ, record: data } as ModalParamsType); | ||
82 | + }; | ||
83 | + | ||
84 | + const alarmForm = ref({ | ||
85 | + time: 2592000000, | ||
86 | + pageSize: 10, | ||
87 | + }); | ||
88 | + const getAlarmForm = (value) => { | ||
89 | + alarmForm.value = { | ||
90 | + time: value.time, | ||
91 | + pageSize: value.pageSize, | ||
92 | + }; | ||
93 | + }; | ||
94 | + | ||
95 | + createAlarmContext({ alarmForm: alarmForm, getAlarmForm }); | ||
96 | + | ||
76 | const { send, close } = useSocket(dataSource); | 97 | const { send, close } = useSocket(dataSource); |
77 | 98 | ||
78 | createDataBoardContext({ send, close }); | 99 | createDataBoardContext({ send, close }); |
@@ -147,6 +168,7 @@ | @@ -147,6 +168,7 @@ | ||
147 | :source-info="item" | 168 | :source-info="item" |
148 | @update="handleUpdateWidget" | 169 | @update="handleUpdateWidget" |
149 | @open-trend="handleOpenTrend" | 170 | @open-trend="handleOpenTrend" |
171 | + @open-alarm="handleOpenAlarm" | ||
150 | @ok="getDataSource" | 172 | @ok="getDataSource" |
151 | /> | 173 | /> |
152 | </template> | 174 | </template> |
@@ -162,9 +184,12 @@ | @@ -162,9 +184,12 @@ | ||
162 | 184 | ||
163 | <DataSourceBindPanel @register="register" :layout="dataSource" @ok="getDataSource" /> | 185 | <DataSourceBindPanel @register="register" :layout="dataSource" @ok="getDataSource" /> |
164 | 186 | ||
187 | + <!-- 趋势 --> | ||
165 | <HistoryTrendModal @register="registerTrendModal" /> | 188 | <HistoryTrendModal @register="registerTrendModal" /> |
189 | + | ||
190 | + <!-- 告警选择时间 --> | ||
191 | + <alarmTimeModal @register="registerAlarmModal" @getAlarmForm="getAlarmForm" /> | ||
166 | </section> | 192 | </section> |
167 | </template> | 193 | </template> |
168 | 194 | ||
169 | <style lang="less" scoped></style> | 195 | <style lang="less" scoped></style> |
170 | -../packages/hook/socket/useSocket |