Commit 5b3085cb9fd515887a3b618e81a3deef89f6833f
Merge branch 'perf/main_dev' of http://git.yunteng.com/yunteng/thingskit-scada into fix/zfl
Showing
10 changed files
with
73 additions
and
62 deletions
| ... | ... | @@ -16,7 +16,7 @@ enum Api { | 
| 16 | 16 | RPC_ONEWAY = '/rpc/oneway', | 
| 17 | 17 | |
| 18 | 18 | GEN_MODBUS_COMMAND = '/js/modbus', | 
| 19 | - GET_DEVICE = '/device/', // 获取设备详情 | |
| 19 | + GET_DEVICE_DETAIL = '/device/', // 获取设备详情 | |
| 20 | 20 | } | 
| 21 | 21 | |
| 22 | 22 | export interface GenModbusCommandType { | 
| ... | ... | @@ -125,7 +125,7 @@ export const getDeviceActiveTime = (entityId: string) => { | 
| 125 | 125 | export const getDeviceInfo = (deviceId: string) => { | 
| 126 | 126 | return defHttp.get( | 
| 127 | 127 | { | 
| 128 | - url: `${Api.GET_DEVICE}${deviceId}`, | |
| 128 | + url: `${Api.GET_DEVICE_DETAIL}${deviceId}`, | |
| 129 | 129 | }, | 
| 130 | 130 | ) | 
| 131 | 131 | } | ... | ... | 
| 1 | -import type { ThingsModel } from '@/api/device/model' | |
| 1 | +import type { ThingsModelItemType } from '@/api/device/model' | |
| 2 | 2 | import type { ImageSelectorDataType } from '@/core/Library/components/ImageSelector' | 
| 3 | 3 | import type { CommandWayEnum } from '@/enums/commandEnum' | 
| 4 | 4 | import type { ActTypeEnum, AggregateTypeEnum, CommandDeliveryWayEnum, DeviceTypeEnum, EventActionTypeEnum, EventTypeEnum, SocketSubscriberEnum, TransportTypeEnum } from '@/enums/datasource' | 
| ... | ... | @@ -79,11 +79,20 @@ export interface AlarmListOptionType { | 
| 79 | 79 | |
| 80 | 80 | } | 
| 81 | 81 | |
| 82 | +export interface DeviceInfoType { | |
| 83 | + code?: string | |
| 84 | + transportType?: string | |
| 85 | + deviceType?: string | |
| 86 | + deviceProfileId?: string | |
| 87 | + deviceName?: string | |
| 88 | + codeType?: string | |
| 89 | +} | |
| 90 | + | |
| 82 | 91 | export interface NodeDataDataSourceJsonType { | 
| 83 | 92 | deviceId: string | 
| 84 | 93 | deviceProfileId: string | 
| 85 | 94 | attr: string | 
| 86 | - attrInfo: ThingsModel | |
| 95 | + attrInfo: ThingsModelItemType | |
| 87 | 96 | |
| 88 | 97 | deviceType?: DeviceTypeEnum | 
| 89 | 98 | transportType?: TransportTypeEnum | 
| ... | ... | @@ -92,13 +101,7 @@ export interface NodeDataDataSourceJsonType { | 
| 92 | 101 | chartOption?: ChartOptionType | 
| 93 | 102 | videoOption?: VideoOptionType | 
| 94 | 103 | alarmListOption?: AlarmListOptionType | 
| 95 | - deviceInfo?: { | |
| 96 | - code?: string | |
| 97 | - transportType?: string | |
| 98 | - deviceType?: string | |
| 99 | - deviceProfileId?: string | |
| 100 | - label?: string | |
| 101 | - } | |
| 104 | + deviceInfo?: DeviceInfoType | |
| 102 | 105 | circularFlowMeterOption?: FlowMeterColorItemType[] // 圆形水球图数据暂定any | 
| 103 | 106 | rectFlowMeterOption?: FlowMeterColorItemType[] // 方形水球图颜色配置数据暂定any | 
| 104 | 107 | |
| ... | ... | @@ -126,7 +129,7 @@ export interface DoubleClickEventDataType { | 
| 126 | 129 | serviceCommand?: Recordable | 
| 127 | 130 | way?: CommandWayEnum | 
| 128 | 131 | customCommand?: string | 
| 129 | - deviceInfo?: any | |
| 132 | + deviceInfo?: DeviceInfoType | |
| 130 | 133 | commandWay?: CommandDeliveryWayEnum | 
| 131 | 134 | callType?: string | 
| 132 | 135 | operationPassword?: { | 
| ... | ... | @@ -161,7 +164,7 @@ export interface BasicActDataType { | 
| 161 | 164 | attr: string | 
| 162 | 165 | enable?: boolean | 
| 163 | 166 | deviceProfileId?: string | number | 
| 164 | - attrInfo?: ThingsModel | |
| 167 | + attrInfo?: ThingsModelItemType | |
| 165 | 168 | } | 
| 166 | 169 | |
| 167 | 170 | export interface FlashActDataType extends BasicActDataType { | ... | ... | 
| 1 | 1 | <script lang="ts" setup> | 
| 2 | 2 | import { computed, ref, unref, watch, watchEffect } from 'vue' | 
| 3 | 3 | import { Select } from 'ant-design-vue' | 
| 4 | -import { isFunction, isString } from '@wry-smile/utils-is' | |
| 4 | +import { isFunction } from '@wry-smile/utils-is' | |
| 5 | 5 | import { get } from 'lodash-es' | 
| 6 | -import type { DefaultOptionType, SelectValue } from 'ant-design-vue/es/select' | |
| 6 | +import type { DefaultOptionType, SelectProps, SelectValue } from 'ant-design-vue/es/select' | |
| 7 | 7 | import { Icon } from '@iconify/vue' | 
| 8 | 8 | import { useRuleFormItem } from '@/hooks/component/useFormItem' | 
| 9 | 9 | |
| ... | ... | @@ -18,21 +18,20 @@ const props = withDefaults( | 
| 18 | 18 | api?: ((arg?: any) => Promise<DefaultOptionType[]>) | null | 
| 19 | 19 | params?: any | 
| 20 | 20 | resultField?: string | 
| 21 | - labelField?: string | string[] | |
| 22 | - valueField?: string | |
| 21 | + aliasField?: string | |
| 23 | 22 | immediate?: boolean | 
| 24 | 23 | alwaysLoad?: boolean | 
| 25 | 24 | options?: DefaultOptionType[] | 
| 25 | + fieldNames?: SelectProps['fieldNames'] | |
| 26 | 26 | }>(), | 
| 27 | 27 | { | 
| 28 | 28 | api: null, | 
| 29 | 29 | params: () => ({}), | 
| 30 | 30 | resultFiled: '', | 
| 31 | - labelField: 'label', | |
| 32 | - valueFiled: 'value', | |
| 33 | 31 | immediate: true, | 
| 34 | 32 | alwaysLoad: false, | 
| 35 | 33 | options: () => ([]), | 
| 34 | + fieldNames: () => ({ label: 'label', value: 'value', options: 'options' }), | |
| 36 | 35 | }, | 
| 37 | 36 | ) | 
| 38 | 37 | |
| ... | ... | @@ -47,39 +46,29 @@ const emitData = ref<any[]>([]) | 
| 47 | 46 | const [state] = useRuleFormItem(props, 'value', 'change', emitData) | 
| 48 | 47 | |
| 49 | 48 | const getOptions = computed(() => { | 
| 50 | - const { labelField: _labelField = 'label', valueField = 'value', numberToString } = props | |
| 49 | + const { numberToString, fieldNames } = props | |
| 50 | + const { value: valueField = 'value' } = fieldNames | |
| 51 | 51 | |
| 52 | 52 | const data = unref(optionsRef).reduce((prev, next: any) => { | 
| 53 | 53 | if (next) { | 
| 54 | 54 | const value = get(next, valueField) | 
| 55 | - const { label } = getAliasLabelInfo(next, _labelField) | |
| 55 | + | |
| 56 | 56 | prev.push({ | 
| 57 | - // ...omit(next, [labelField, valueField]), | |
| 58 | 57 | ...next, | 
| 59 | - label, | |
| 60 | - value: numberToString ? `${value}` : value, | |
| 61 | - }) | |
| 58 | + [valueField]: numberToString ? `${value}` : value, | |
| 59 | + } as DefaultOptionType) | |
| 62 | 60 | } | 
| 63 | 61 | return prev | 
| 64 | 62 | }, [] as DefaultOptionType[]) | 
| 63 | + | |
| 65 | 64 | return data.length > 0 ? data : props.options | 
| 66 | 65 | }) | 
| 67 | 66 | |
| 68 | -function getAliasLabelInfo(data: any, labelField: string | string[]) { | |
| 69 | - labelField = isString(labelField) ? [labelField] : labelField | |
| 70 | - | |
| 71 | - let value | |
| 72 | - let labelKey = labelField[0] | |
| 73 | - for (const label of labelField) { | |
| 74 | - value = get(data, label) | |
| 75 | - if (value) { | |
| 76 | - labelKey = label | |
| 77 | - break | |
| 78 | - } | |
| 79 | - } | |
| 80 | - | |
| 81 | - return { labelField: labelKey, label: value } | |
| 82 | -} | |
| 67 | +const getFieldNames = computed<SelectProps['fieldNames']>(() => { | |
| 68 | + const { fieldNames, aliasField } = props | |
| 69 | + const { label, value, options } = fieldNames | |
| 70 | + return { label: aliasField || label, value, options } | |
| 71 | +}) | |
| 83 | 72 | |
| 84 | 73 | watchEffect(() => { | 
| 85 | 74 | props.immediate && !props.alwaysLoad && fetch() | 
| ... | ... | @@ -149,7 +138,11 @@ function handleChange(value: SelectValue, ...args: any[]) { | 
| 149 | 138 | |
| 150 | 139 | <template> | 
| 151 | 140 | <Select | 
| 152 | - v-bind="$attrs" v-model:value="state" :options="getOptions" @dropdown-visible-change="handleFetch" | |
| 141 | + v-bind="$attrs" | |
| 142 | + v-model:value="state" | |
| 143 | + :options="getOptions" | |
| 144 | + :field-names="getFieldNames" | |
| 145 | + @dropdown-visible-change="handleFetch" | |
| 153 | 146 | @change="handleChange" | 
| 154 | 147 | > | 
| 155 | 148 | <template v-for="item in (Object.keys($slots as Slots) as SlotName[])" #[item]="data"> | ... | ... | 
| ... | ... | @@ -181,8 +181,7 @@ export const getFormSchemas = (event: EventTypeEnum): FormSchema[] => { | 
| 181 | 181 | return { | 
| 182 | 182 | api: getThingsModelServices, | 
| 183 | 183 | params: deviceProfileId, | 
| 184 | - labelField: 'functionName', | |
| 185 | - valueField: 'identifier', | |
| 184 | + fieldNames: { label: 'functionName', value: 'identifier' }, | |
| 186 | 185 | onSelect(value: string, options: ThingsModel) { | 
| 187 | 186 | formModel[FormFieldsEnum.THINGS_MODEL] = value ? options : null | 
| 188 | 187 | formModel.callType = options.callType | ... | ... | 
| ... | ... | @@ -3,9 +3,10 @@ import { getDeviceAttributes, getListByDeviceProfileIds } from '@/api/device' | 
| 3 | 3 | import type { DeviceItemType, ThingsModelItemType } from '@/api/device/model' | 
| 4 | 4 | import type { FormSchema } from '@/components/Form' | 
| 5 | 5 | import { ComponentEnum } from '@/components/Form/src/enum' | 
| 6 | -import { ContentDataFieldsEnum, ContentDataFieldsNameEnum } from '@/enums/datasource' | |
| 6 | +import { ContentDataFieldsEnum, ContentDataFieldsNameEnum, DataTypeEnum } from '@/enums/datasource' | |
| 7 | 7 | import { useContentDataStoreWithOut } from '@/store/modules/contentData' | 
| 8 | 8 | import type { ProductAndDevice } from '@/api/content/model' | 
| 9 | +import { ControlComponentEnum } from '@/core/Library/packages/Control' | |
| 9 | 10 | |
| 10 | 11 | const contentDataStore = useContentDataStoreWithOut() | 
| 11 | 12 | export const formSchemas = (componentKey?: string): FormSchema[] => { | 
| ... | ... | @@ -51,15 +52,15 @@ export const formSchemas = (componentKey?: string): FormSchema[] => { | 
| 51 | 52 | deviceProfileIds: (unref(contentDataStore.getProductAndDevice) || []).map((item: ProductAndDevice) => item?.profileId), | 
| 52 | 53 | organizationId, | 
| 53 | 54 | }, | 
| 54 | - labelField: ['alias', 'name'], | |
| 55 | - valueField: 'tbDeviceId', | |
| 55 | + aliasField: 'alias', | |
| 56 | + fieldNames: { label: 'name', value: 'tbDeviceId' }, | |
| 56 | 57 | onSelect(value: string, option: DeviceItemType) { | 
| 57 | 58 | formModel[ContentDataFieldsEnum.DEVICE_PROFILE_ID] = value ? option.deviceProfileId : null | 
| 58 | - formModel[ContentDataFieldsEnum.DEVICE_INFO] = value && option ? { code: option.code, transportType: option.transportType, deviceType: option.deviceType, deviceProfileId: option.deviceProfileId, label: option.alias || option.name } : null | |
| 59 | + formModel[ContentDataFieldsEnum.DEVICE_INFO] = value && option ? { code: option.code, transportType: option.transportType, deviceType: option.deviceType, deviceProfileId: option.deviceProfileId, deviceName: option.alias || option.name } : null | |
| 59 | 60 | formModel[ContentDataFieldsEnum.ATTR] = null | 
| 60 | 61 | }, | 
| 61 | - filterOption: (inputValue: string, option: Record<'label' | 'value', string>) => | |
| 62 | - option.label.includes(inputValue), | |
| 62 | + filterOption: (inputValue: string, option: DeviceItemType) => | |
| 63 | + option.alias?.includes?.(inputValue) || option.name?.includes?.(inputValue), | |
| 63 | 64 | } | 
| 64 | 65 | }, | 
| 65 | 66 | }, | 
| ... | ... | @@ -75,21 +76,20 @@ export const formSchemas = (componentKey?: string): FormSchema[] => { | 
| 75 | 76 | api: async (params: string) => { | 
| 76 | 77 | if (!deviceProfileId) return [] | 
| 77 | 78 | const options = await getDeviceAttributes(params) | 
| 78 | - if (componentKey === 'Switch') { // 开关只返回bool | |
| 79 | + if (componentKey === ControlComponentEnum.SWITCH) { // 开关只返回bool | |
| 79 | 80 | return options.filter((item) => { | 
| 80 | - return item.detail.dataType.type === 'BOOL' | |
| 81 | + return item.detail.dataType.type === DataTypeEnum.BOOL | |
| 81 | 82 | }) | 
| 82 | 83 | } | 
| 83 | 84 | return options | 
| 84 | 85 | }, | 
| 85 | 86 | params: deviceProfileId, | 
| 86 | - labelField: 'name', | |
| 87 | - valueField: 'identifier', | |
| 87 | + fieldNames: { label: 'name', value: 'identifier' }, | |
| 88 | 88 | onSelect(value: string, option: ThingsModelItemType) { | 
| 89 | 89 | formModel[ContentDataFieldsEnum.ATTR_INFO] = value && option ? toRaw(unref(option)) : null | 
| 90 | 90 | }, | 
| 91 | - filterOption: (inputValue: string, option: Record<'label' | 'value', string>) => | |
| 92 | - option.label.includes(inputValue), | |
| 91 | + filterOption: (inputValue: string, option: ThingsModelItemType) => | |
| 92 | + option.name.includes(inputValue), | |
| 93 | 93 | } | 
| 94 | 94 | }, | 
| 95 | 95 | }, | ... | ... | 
| ... | ... | @@ -121,14 +121,14 @@ export const formSchemas: FormSchema[] = [ | 
| 121 | 121 | deviceProfileIds: unref(contentDataStore.getProductIds), | 
| 122 | 122 | organizationId, | 
| 123 | 123 | }, | 
| 124 | - labelField: ['alias', 'name'], | |
| 125 | - valueField: 'id', | |
| 124 | + aliasField: 'alias', | |
| 125 | + fieldNames: { label: 'name', value: 'id' }, | |
| 126 | 126 | maxTagCount: 4, | 
| 127 | 127 | onSelect(value: string, option: DeviceItemType) { | 
| 128 | 128 | formModel[AlarmListFieldsEnum.DEVICE_PROFILE_ID] = value ? option.deviceProfileId : null | 
| 129 | 129 | }, | 
| 130 | - filterOption: (inputValue: string, option: Record<'label' | 'value', string>) => | |
| 131 | - option.label.includes(inputValue), | |
| 130 | + filterOption: (inputValue: string, option: DeviceItemType) => | |
| 131 | + option.alias?.includes?.(inputValue) || option.name?.includes?.(inputValue), | |
| 132 | 132 | } | 
| 133 | 133 | }, | 
| 134 | 134 | }, | ... | ... | 
| 1 | 1 | const data = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] | 
| 2 | 2 | const seriesList = [ | 
| 3 | - | |
| 4 | 3 | { value: 120, name: '123', itemStyle: { color: '#5470c6' } }, | 
| 5 | 4 | { value: 150, name: '456', itemStyle: { color: '#5470c6' } }, | 
| 6 | 5 | { value: 40, name: '789', itemStyle: { color: '#5470c6' } }, | ... | ... | 
| ... | ... | @@ -10,6 +10,7 @@ import type { SubscriptionData } from '@/core/websocket/type/message' | 
| 10 | 10 | import { SocketSubscriberEnum } from '@/enums/datasource' | 
| 11 | 11 | import { formatToDateTime } from '@/utils/dateUtil' | 
| 12 | 12 | import { useLatestMessageValue } from '@/core/Library/hook/useLatestMessageValue' | 
| 13 | +import type { CommandSource } from '@/core/websocket/processor' | |
| 13 | 14 | |
| 14 | 15 | const props = defineProps<{ | 
| 15 | 16 | config: CreateComponentType | 
| ... | ... | @@ -49,7 +50,10 @@ function sliceData(data: any[], maxLength = 20) { | 
| 49 | 50 | return data | 
| 50 | 51 | } | 
| 51 | 52 | |
| 52 | -const handlerTimeSeriesData = (message: SubscriptionData, attr: string) => { | |
| 53 | +const handlerTimeSeriesData = (commandSource: CommandSource, message: SubscriptionData, attr: string) => { | |
| 54 | + const { data } = commandSource | |
| 55 | + const { attrInfo, deviceInfo } = data as NodeDataDataSourceJsonType | |
| 56 | + const { deviceName } = deviceInfo || {} | |
| 53 | 57 | const { ts, latestValue } = useLatestMessageValue(message, attr) | 
| 54 | 58 | const option = unref(chartInstance)?.getOption() as EChartsOption | 
| 55 | 59 | const xAxisData = ((option.xAxis as XAXisComponentOption[])?.[0] as { data: string[] }).data | 
| ... | ... | @@ -57,6 +61,9 @@ const handlerTimeSeriesData = (message: SubscriptionData, attr: string) => { | 
| 57 | 61 | xAxisData.push(formatToDateTime(ts)) | 
| 58 | 62 | seriesData.push(latestValue) | 
| 59 | 63 | unref(chartInstance)?.setOption({ | 
| 64 | + title: { | |
| 65 | + text: `${deviceName}-${attrInfo.name}`, | |
| 66 | + }, | |
| 60 | 67 | xAxis: { data: sliceData(xAxisData) }, | 
| 61 | 68 | series: { data: sliceData(seriesData) }, | 
| 62 | 69 | } as EChartsOption) | 
| ... | ... | @@ -69,7 +76,7 @@ const { onMessage } = useOnMessage({ | 
| 69 | 76 | const { queryType } = chartOption || {} | 
| 70 | 77 | |
| 71 | 78 | if (queryType === SocketSubscriberEnum.TS_SUB_CMDS) | 
| 72 | - handlerTimeSeriesData(message.data, attr) | |
| 79 | + handlerTimeSeriesData(commandSource, message.data, attr) | |
| 73 | 80 | else if (queryType === SocketSubscriberEnum.HISTORY_CMDS) | 
| 74 | 81 | handleHistoryData(message.data, attr) | 
| 75 | 82 | }, | ... | ... |