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 | 52 | |
53 | 53 | const handleBlur = async () => { |
54 | 54 | if (unref(oldSliderValue) !== unref(sliderValue)) { |
55 | - console.log('effect'); | |
56 | 55 | const flag = await sendCommand(props.config.option, unref(sliderValue)); |
57 | 56 | flag |
58 | 57 | ? ((sliderValue.value = unref(sliderValue)), | ... | ... |
... | ... | @@ -8,15 +8,6 @@ import { |
8 | 8 | } from '../../../index.type'; |
9 | 9 | import { PublicConfigClass, componentInitConfig } from '../../../publicConfig'; |
10 | 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 | 11 | export const option: PublicPresetOptions = { |
21 | 12 | multipleDataSourceComponent: true, |
22 | 13 | [ComponentConfigFieldEnum.FONT_COLOR]: '#FD7347', | ... | ... |
... | ... | @@ -6,6 +6,8 @@ |
6 | 6 | import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime'; |
7 | 7 | import { useComponentScale } from '../../../hook/useComponentScale'; |
8 | 8 | import { DeviceName } from '/@/views/visual/commonComponents/DeviceName'; |
9 | + import { useReceiveMessage } from '../../../hook/useReceiveMessage'; | |
10 | + import { useReceiveValue } from '../../../hook/useReceiveValue'; | |
9 | 11 | import { useMultipleDataFetch } from '../../../hook/socket/useSocket'; |
10 | 12 | import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type'; |
11 | 13 | |
... | ... | @@ -22,33 +24,45 @@ |
22 | 24 | const getDesign = computed(() => { |
23 | 25 | const { persetOption, option } = props.config; |
24 | 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 | 32 | return { |
27 | 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 | 37 | return { |
31 | 38 | unit: unit ?? presetUnit, |
32 | 39 | fontColor: fontColor ?? presetFontColor, |
33 | 40 | attribute, |
41 | + attributeName, | |
34 | 42 | attributeRename, |
43 | + deviceName, | |
44 | + deviceRename, | |
45 | + showDeviceName: showDeviceName ?? persetShowDeviceName, | |
46 | + id: deviceId, | |
35 | 47 | }; |
36 | 48 | }), |
37 | 49 | }; |
38 | 50 | }); |
51 | + | |
39 | 52 | const seriesList = [ |
40 | 53 | { value: 120, name: '123', itemStyle: { color: '#02E5F0' } }, |
41 | 54 | { value: 150, name: '456', itemStyle: { color: '#028CF0' } }, |
42 | 55 | { value: 40, name: '789', itemStyle: { color: '#F09202' } }, |
43 | 56 | ]; |
44 | 57 | const data = ['温度', '湿度', '温度1']; |
58 | + | |
45 | 59 | const options = (): EChartsOption => { |
46 | - // getStageColor(gradientInfo); | |
47 | 60 | return { |
48 | 61 | color: ['#3398DB'], |
49 | 62 | tooltip: { |
50 | 63 | // 提示框 |
51 | - trigger: 'axis', | |
64 | + trigger: 'item', | |
65 | + confine: true, | |
52 | 66 | axisPointer: { |
53 | 67 | type: 'shadow', |
54 | 68 | }, |
... | ... | @@ -59,9 +73,6 @@ |
59 | 73 | axisTick: { |
60 | 74 | alignWithLabel: true, |
61 | 75 | }, |
62 | - // axisPointer: { | |
63 | - // type: 'line', | |
64 | - // }, | |
65 | 76 | }, |
66 | 77 | grid: { |
67 | 78 | top: '15%', |
... | ... | @@ -104,32 +115,40 @@ |
104 | 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 | 154 | useMultipleDataFetch(props, updateFn); | ... | ... |
... | ... | @@ -6,6 +6,8 @@ |
6 | 6 | import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime'; |
7 | 7 | import { useComponentScale } from '../../../hook/useComponentScale'; |
8 | 8 | import { DeviceName } from '/@/views/visual/commonComponents/DeviceName'; |
9 | + import { useReceiveMessage } from '../../../hook/useReceiveMessage'; | |
10 | + import { useReceiveValue } from '../../../hook/useReceiveValue'; | |
9 | 11 | import { useMultipleDataFetch } from '../../../hook/socket/useSocket'; |
10 | 12 | import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type'; |
11 | 13 | |
... | ... | @@ -22,16 +24,26 @@ |
22 | 24 | const getDesign = computed(() => { |
23 | 25 | const { persetOption, option } = props.config; |
24 | 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 | 32 | return { |
27 | 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 | 37 | return { |
31 | 38 | unit: unit ?? presetUnit, |
32 | 39 | fontColor: fontColor ?? presetFontColor, |
33 | 40 | attribute, |
41 | + attributeName, | |
34 | 42 | attributeRename, |
43 | + deviceName, | |
44 | + deviceRename, | |
45 | + showDeviceName: showDeviceName ?? persetShowDeviceName, | |
46 | + id: deviceId, | |
35 | 47 | }; |
36 | 48 | }), |
37 | 49 | }; |
... | ... | @@ -48,7 +60,8 @@ |
48 | 60 | color: ['#3398DB'], |
49 | 61 | tooltip: { |
50 | 62 | // 提示框 |
51 | - trigger: 'axis', | |
63 | + trigger: 'item', | |
64 | + confine: true, | |
52 | 65 | axisPointer: { |
53 | 66 | type: 'shadow', |
54 | 67 | }, |
... | ... | @@ -104,32 +117,39 @@ |
104 | 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 | 155 | useMultipleDataFetch(props, updateFn); | ... | ... |
1 | 1 | <script lang="ts" setup> |
2 | - import { reactive, ref } from 'vue'; | |
2 | + import { computed, unref } from 'vue'; | |
3 | 3 | import { ComponentPropsConfigType } from '../../../index.type'; |
4 | 4 | import { option } from './config'; |
5 | 5 | import { DeviceName } from '/@/views/visual/commonComponents/DeviceName'; |
6 | 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 | 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 | 21 | const props = defineProps<{ |
11 | 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 | 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 | 113 | </script> |
39 | 114 | |
40 | 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 | 117 | <DeviceName :config="config" /> |
43 | - <div> | |
118 | + <div class="w-full h-full"> | |
44 | 119 | <!-- <PageWrapper> --> |
45 | - <BasicTable @register="registerTable" /> | |
120 | + <BasicTable autoCreateKey style="flex: auto" @register="registerTable" /> | |
46 | 121 | <!-- </PageWrapper> --> |
47 | 122 | </div> |
48 | 123 | </main> | ... | ... |
... | ... | @@ -8,13 +8,6 @@ |
8 | 8 | const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({ |
9 | 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 | 11 | field: ComponentConfigFieldEnum.UNIT, |
19 | 12 | label: '数值单位', |
20 | 13 | component: 'Input', | ... | ... |
1 | 1 | <script lang="ts" setup> |
2 | 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 | 4 | import { ComponentPropsConfigType } from '../../../index.type'; |
5 | 5 | import { option } from './config'; |
6 | 6 | import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime'; |
7 | 7 | import { useComponentScale } from '../../../hook/useComponentScale'; |
8 | 8 | import { DeviceName } from '/@/views/visual/commonComponents/DeviceName'; |
9 | - import { dateFormat } from '/@/utils/common/compUtils'; | |
9 | + import { useIntervalFn } from '@vueuse/core'; | |
10 | 10 | import { useMultipleDataFetch } from '../../../hook/socket/useSocket'; |
11 | 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 | 18 | const props = defineProps<{ |
14 | 19 | config: ComponentPropsConfigType<typeof option>; |
15 | 20 | }>(); |
16 | - | |
17 | 21 | const chartRefEl = ref<Nullable<HTMLDivElement>>(null); |
18 | 22 | |
19 | 23 | const chartInstance = ref<Nullable<ECharts>>(null); |
20 | - | |
21 | 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 | 41 | const options = (): EChartsOption => { |
66 | - // getStageColor(gradientInfo); | |
67 | 42 | return { |
68 | - trigger: 'axis', | |
69 | - axisPointer: { | |
70 | - lineStyle: { | |
71 | - width: 1, | |
72 | - color: '#019680', | |
73 | - }, | |
74 | - }, | |
75 | 43 | tooltip: { |
76 | - show: true, | |
44 | + // trigger: 'axis', | |
77 | 45 | }, |
78 | 46 | legend: { |
79 | 47 | top: '10%', |
80 | 48 | left: 'center', |
81 | - data: ['温度', '湿度'], | |
49 | + data: ['温度'], | |
82 | 50 | }, |
83 | 51 | grid: { |
84 | - top: '45%', | |
85 | - left: '20%', | |
86 | - bottom: '14%', | |
52 | + top: '30%', | |
53 | + left: '6%', | |
54 | + right: '16%', | |
55 | + bottom: '1%', | |
87 | 56 | containLabel: true, |
88 | 57 | }, |
89 | 58 | xAxis: { |
90 | 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 | 62 | yAxis: { |
102 | 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 | 112 | const initial = () => { |
128 | 113 | chartInstance.value = init(unref(chartRefEl)! as HTMLElement); |
129 | 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 | 179 | useMultipleDataFetch(props, updateFn); |
145 | - | |
146 | 180 | onMounted(() => { |
147 | 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 | 185 | const resize = async () => { |
153 | 186 | await nextTick(); |
154 | 187 | |
155 | 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 | 196 | unref(chartInstance)?.resize(); |
164 | 197 | }; |
165 | 198 | |
166 | - const { getRatio } = useComponentScale(props, resize); | |
199 | + useComponentScale(props, resize); | |
167 | 200 | </script> |
168 | 201 | |
169 | 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 | 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 | 180 | } |
181 | 181 | |
182 | 182 | if (isEntityDataUpdateMsg(message)) { |
183 | + this.triggerEntityDataMessage(message); | |
183 | 184 | return; |
184 | 185 | } |
185 | 186 | |
... | ... | @@ -248,6 +249,20 @@ class Subscriber { |
248 | 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 | 268 | const subscriber = new Subscriber(); |
... | ... | @@ -426,4 +441,8 @@ export const useCustomDataFetch = ( |
426 | 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 | 3 | import { componentOptionsInitConfig } from '../publicConfig'; |
4 | 4 | |
5 | 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 | 15 | const getRatio = computed(() => { |
7 | 16 | try { |
8 | 17 | const { option, attr } = props.config; |
... | ... | @@ -47,5 +56,5 @@ export const useComponentScale = (props: { config: ComponentPropsConfigType }, o |
47 | 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 | 21 | MAP = '地图组件', |
22 | 22 | FLOWMETER = '流量计', |
23 | 23 | STATISTICS = '统计', |
24 | + ALARM = '告警', | |
24 | 25 | OTHER = '其他', |
25 | 26 | } |
26 | 27 | |
... | ... | @@ -35,6 +36,7 @@ export enum PackagesCategoryEnum { |
35 | 36 | MAP = 'MAP', |
36 | 37 | FLOWMETER = 'FLOWMETER', |
37 | 38 | STATISTICS = 'STATISTICS', |
39 | + ALARM = 'ALARM', | |
38 | 40 | OTHER = 'OTHER', |
39 | 41 | } |
40 | 42 | |
... | ... | @@ -137,6 +139,7 @@ export interface PackagesType { |
137 | 139 | [PackagesCategoryEnum.CONTROL]: ConfigType[]; |
138 | 140 | [PackagesCategoryEnum.MAP]: ConfigType[]; |
139 | 141 | [PackagesCategoryEnum.FLOWMETER]: ConfigType[]; |
140 | - // [PackagesCategoryEnum.STATISTICS]: ConfigType[]; | |
142 | + [PackagesCategoryEnum.STATISTICS]: ConfigType[]; | |
141 | 143 | [PackagesCategoryEnum.OTHER]: ConfigType[]; |
144 | + [PackagesCategoryEnum.ALARM]: ConfigType[]; | |
142 | 145 | } | ... | ... |
... | ... | @@ -5,7 +5,8 @@ import { MapList } from './components/Map'; |
5 | 5 | import { OtherList } from './components/Other'; |
6 | 6 | // import { PictureList } from './components/Picture'; |
7 | 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 | 10 | import { PackagesCategoryEnum, PackagesType } from './index.type'; |
10 | 11 | |
11 | 12 | export const packageList: PackagesType = { |
... | ... | @@ -15,11 +16,12 @@ export const packageList: PackagesType = { |
15 | 16 | [PackagesCategoryEnum.CONTROL]: ControlList, |
16 | 17 | [PackagesCategoryEnum.MAP]: MapList, |
17 | 18 | [PackagesCategoryEnum.FLOWMETER]: FlowmeterList, |
18 | - // [PackagesCategoryEnum.STATISTICS]: STATISTICSList, | |
19 | + [PackagesCategoryEnum.STATISTICS]: STATISTICSList, | |
20 | + [PackagesCategoryEnum.ALARM]: AlarmList, | |
19 | 21 | [PackagesCategoryEnum.OTHER]: OtherList, |
20 | 22 | }; |
21 | 23 | |
22 | 24 | /** |
23 | 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 | 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 | 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 | 7 | const props = defineProps<{ |
7 | 8 | config: ComponentPropsConfigType<typeof option>; | ... | ... |
... | ... | @@ -6,7 +6,7 @@ |
6 | 6 | import { DataActionModeEnum } from '/@/enums/toolEnum'; |
7 | 7 | import { VisualComponentPermission } from '../..'; |
8 | 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 | 10 | import { WidgetDataType } from '../../hooks/useDataSource'; |
11 | 11 | import { useMessage } from '/@/hooks/web/useMessage'; |
12 | 12 | import { addDataComponent, deleteDataComponent } from '/@/api/dataBoard'; |
... | ... | @@ -29,6 +29,7 @@ |
29 | 29 | (event: 'ok'): void; |
30 | 30 | (event: 'update', data: WidgetDataType): void; |
31 | 31 | (event: 'openTrend', data: WidgetDataType): void; |
32 | + (event: 'openAlarm', data: WidgetDataType): void; | |
32 | 33 | }>(); |
33 | 34 | |
34 | 35 | const { isCustomerUser } = useRole(); |
... | ... | @@ -88,6 +89,15 @@ |
88 | 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 | 101 | async function handleCopy() { |
92 | 102 | const id = props.sourceInfo.id; |
93 | 103 | const copyRecord = props.rawDataSource.componentData.find((item) => item.id === id); |
... | ... | @@ -120,6 +130,10 @@ |
120 | 130 | const handleOpenTrendModal = () => { |
121 | 131 | emit('openTrend', toRaw(props.sourceInfo)); |
122 | 132 | }; |
133 | + | |
134 | + const handleAlarmModal = () => { | |
135 | + emit('openAlarm', toRaw(props.sourceInfo)); | |
136 | + }; | |
123 | 137 | </script> |
124 | 138 | |
125 | 139 | <template> |
... | ... | @@ -132,8 +146,9 @@ |
132 | 146 | </Tooltip> |
133 | 147 | |
134 | 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 | 152 | </Tooltip> |
138 | 153 | <AuthDropDown |
139 | 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 | 25 | import { ModalParamsType } from '/#/utils'; |
26 | 26 | import { DataActionModeEnum } from '/@/enums/toolEnum'; |
27 | 27 | import { HistoryTrendModal } from './components/HistoryTrendModal'; |
28 | + import { alarmTimeModal } from './components/alarmTimeModal'; | |
28 | 29 | import { watch } from 'vue'; |
29 | 30 | import { useRootSetting } from '/@/hooks/setting/useRootSetting'; |
30 | 31 | import { ThemeEnum } from '/@/enums/appEnum'; |
31 | 32 | import { createDataBoardContext } from './hooks/useDataBoardContext'; |
32 | 33 | import { useSocket } from '/@/views/visual/packages/hook/socket/useSocket'; |
34 | + import { createAlarmContext } from './hooks/useAlarmTime'; | |
33 | 35 | |
34 | 36 | const props = defineProps<{ |
35 | 37 | value?: Recordable; |
... | ... | @@ -73,6 +75,25 @@ |
73 | 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 | 97 | const { send, close } = useSocket(dataSource); |
77 | 98 | |
78 | 99 | createDataBoardContext({ send, close }); |
... | ... | @@ -147,6 +168,7 @@ |
147 | 168 | :source-info="item" |
148 | 169 | @update="handleUpdateWidget" |
149 | 170 | @open-trend="handleOpenTrend" |
171 | + @open-alarm="handleOpenAlarm" | |
150 | 172 | @ok="getDataSource" |
151 | 173 | /> |
152 | 174 | </template> |
... | ... | @@ -162,9 +184,12 @@ |
162 | 184 | |
163 | 185 | <DataSourceBindPanel @register="register" :layout="dataSource" @ok="getDataSource" /> |
164 | 186 | |
187 | + <!-- 趋势 --> | |
165 | 188 | <HistoryTrendModal @register="registerTrendModal" /> |
189 | + | |
190 | + <!-- 告警选择时间 --> | |
191 | + <alarmTimeModal @register="registerAlarmModal" @getAlarmForm="getAlarmForm" /> | |
166 | 192 | </section> |
167 | 193 | </template> |
168 | 194 | |
169 | 195 | <style lang="less" scoped></style> |
170 | -../packages/hook/socket/useSocket | ... | ... |