Showing
26 changed files
with
247 additions
and
91 deletions
| 1 | +import { DeviceProfileModel } from '../../device/model/deviceModel'; | |
| 1 | 2 | import { HistoryData } from './model'; |
| 2 | 3 | import { defHttp } from '/@/utils/http/axios'; |
| 3 | 4 | |
| 4 | 5 | // 获取设备配置 |
| 5 | 6 | export const getDeviceProfile = () => { |
| 6 | - return defHttp.get({ | |
| 7 | + return defHttp.get<DeviceProfileModel[]>({ | |
| 7 | 8 | url: '/device_profile/me/list', |
| 8 | 9 | }); |
| 9 | 10 | }; | ... | ... |
| ... | ... | @@ -166,9 +166,10 @@ export const getShareBoardComponentInfo = (params: { boardId: string; tenantId: |
| 166 | 166 | * @param params |
| 167 | 167 | * @returns |
| 168 | 168 | */ |
| 169 | -export const getAllDeviceByOrg = (params: string) => { | |
| 169 | +export const getAllDeviceByOrg = (organizationId: string, deviceProfileId?: string) => { | |
| 170 | 170 | return defHttp.get<MasterDeviceList[]>({ |
| 171 | - url: `${DeviceUrl.GET_DEVICE}/${params}`, | |
| 171 | + url: `${DeviceUrl.GET_DEVICE}/${organizationId}`, | |
| 172 | + params: { deviceProfileId }, | |
| 172 | 173 | }); |
| 173 | 174 | }; |
| 174 | 175 | ... | ... |
| ... | ... | @@ -65,7 +65,7 @@ export const releaseModel = (deviceProfileId: string) => { |
| 65 | 65 | |
| 66 | 66 | export const getModelServices = (params: { deviceProfileId: string }) => { |
| 67 | 67 | const { deviceProfileId } = params; |
| 68 | - return defHttp.get({ | |
| 68 | + return defHttp.get<ModelOfMatterParams[]>({ | |
| 69 | 69 | url: `${ModelOfMatter.GET_MODEL_SERVICE}/${deviceProfileId}`, |
| 70 | 70 | }); |
| 71 | 71 | }; | ... | ... |
| ... | ... | @@ -56,7 +56,7 @@ |
| 56 | 56 | |
| 57 | 57 | const handleBeforeUpload = (file: File) => { |
| 58 | 58 | if (file.size > props.maxSize) { |
| 59 | - createMessage.warning(`文件大小超过${Math.floor(props.maxSize / 1024)}mb`); | |
| 59 | + createMessage.warning(`文件大小超过${Math.floor(props.maxSize / 1024 / 1024)}mb`); | |
| 60 | 60 | return false; |
| 61 | 61 | } |
| 62 | 62 | handleUpload(file); | ... | ... |
| 1 | -import { h } from 'vue'; | |
| 2 | 1 | import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel'; |
| 3 | 2 | import { findDictItemByCode } from '/@/api/system/dict'; |
| 4 | 3 | import { FormSchema } from '/@/components/Table'; |
| ... | ... | @@ -22,7 +21,6 @@ const validateValueRange = (_rule, value: Record<'min' | 'max', number>, _callba |
| 22 | 21 | }; |
| 23 | 22 | |
| 24 | 23 | const validateJSON = (_rule, value: ModelOfMatterParams[], _callback) => { |
| 25 | - console.log('validate json', value); | |
| 26 | 24 | if (value.length) { |
| 27 | 25 | return Promise.resolve(); |
| 28 | 26 | } |
| ... | ... | @@ -141,10 +139,15 @@ export const formSchemas: FormSchema[] = [ |
| 141 | 139 | const { setFieldsValue } = formActionType; |
| 142 | 140 | return { |
| 143 | 141 | placeholder: '请选择单位', |
| 144 | - api: findDictItemByCode, | |
| 142 | + api: async (params) => { | |
| 143 | + const list = await findDictItemByCode(params); | |
| 144 | + list.map((item) => (item.itemText = `${item.itemText} / ${item.itemValue}`)); | |
| 145 | + return list; | |
| 146 | + }, | |
| 145 | 147 | params: { |
| 146 | 148 | dictCode: 'attribute_unit', |
| 147 | 149 | }, |
| 150 | + labelInValue: true, | |
| 148 | 151 | labelField: 'itemText', |
| 149 | 152 | valueField: 'itemValue', |
| 150 | 153 | onChange(_, record: Record<'label' | 'value', string>) { |
| ... | ... | @@ -165,14 +168,6 @@ export const formSchemas: FormSchema[] = [ |
| 165 | 168 | ifShow: ({ values }) => |
| 166 | 169 | values[FormField.TYPE] === DateTypeEnum.IS_NUMBER_INT || |
| 167 | 170 | values[FormField.TYPE] === DateTypeEnum.IS_NUMBER_DOUBLE, |
| 168 | - renderComponentContent: () => { | |
| 169 | - return { | |
| 170 | - option: (params: Record<'label' | 'value', string>) => { | |
| 171 | - const { label, value } = params; | |
| 172 | - return h('span', `${label} / ${value}`); | |
| 173 | - }, | |
| 174 | - }; | |
| 175 | - }, | |
| 176 | 171 | }, |
| 177 | 172 | { |
| 178 | 173 | field: FormField.BOOL_CLOSE, | ... | ... |
| ... | ... | @@ -34,7 +34,6 @@ |
| 34 | 34 | let { width = 300, height = 150 } = unref(getOptions); |
| 35 | 35 | width = isNumber(width) ? (`${width}px` as unknown as number) : width; |
| 36 | 36 | height = isNumber(height) ? (`${height}px` as unknown as number) : height; |
| 37 | - console.log({ getOptions }); | |
| 38 | 37 | return { width, height } as CSSProperties; |
| 39 | 38 | }); |
| 40 | 39 | ... | ... |
src/hooks/web/useAsyncScript.ts
0 → 100644
| 1 | +import { onUnmounted, ref } from 'vue'; | |
| 2 | + | |
| 3 | +interface ScriptOptions { | |
| 4 | + src: string; | |
| 5 | +} | |
| 6 | + | |
| 7 | +export function useAsyncScript(opts: ScriptOptions) { | |
| 8 | + const isLoading = ref(false); | |
| 9 | + const error = ref(false); | |
| 10 | + const success = ref(false); | |
| 11 | + let script: HTMLScriptElement; | |
| 12 | + | |
| 13 | + const toPromise = () => { | |
| 14 | + return new Promise((resolve, reject) => { | |
| 15 | + script = document.createElement('script'); | |
| 16 | + script.type = 'text/javascript'; | |
| 17 | + script.onload = function () { | |
| 18 | + isLoading.value = false; | |
| 19 | + success.value = true; | |
| 20 | + error.value = false; | |
| 21 | + resolve(''); | |
| 22 | + }; | |
| 23 | + | |
| 24 | + script.onerror = function (err) { | |
| 25 | + isLoading.value = false; | |
| 26 | + success.value = false; | |
| 27 | + error.value = true; | |
| 28 | + reject(err); | |
| 29 | + }; | |
| 30 | + | |
| 31 | + script.src = opts.src; | |
| 32 | + document.head.appendChild(script); | |
| 33 | + }); | |
| 34 | + }; | |
| 35 | + | |
| 36 | + onUnmounted(() => { | |
| 37 | + script && script.remove(); | |
| 38 | + }); | |
| 39 | + | |
| 40 | + return { | |
| 41 | + isLoading, | |
| 42 | + error, | |
| 43 | + success, | |
| 44 | + toPromise, | |
| 45 | + }; | |
| 46 | +} | ... | ... |
| ... | ... | @@ -62,6 +62,7 @@ export function useECharts( |
| 62 | 62 | } |
| 63 | 63 | nextTick(() => { |
| 64 | 64 | useTimeoutFn(() => { |
| 65 | + console.log(chartInstance); | |
| 65 | 66 | if (!chartInstance) { |
| 66 | 67 | initCharts(getDarkMode.value as 'default'); |
| 67 | 68 | |
| ... | ... | @@ -70,6 +71,7 @@ export function useECharts( |
| 70 | 71 | clear && chartInstance?.clear(); |
| 71 | 72 | |
| 72 | 73 | chartInstance?.setOption(unref(getOptions)); |
| 74 | + chartInstance = null; | |
| 73 | 75 | }, 30); |
| 74 | 76 | }); |
| 75 | 77 | } | ... | ... |
| ... | ... | @@ -29,7 +29,7 @@ |
| 29 | 29 | import { cloneDeep } from 'lodash'; |
| 30 | 30 | import { usePermission } from '/@/hooks/web/usePermission'; |
| 31 | 31 | |
| 32 | - const listColumn = ref(4); | |
| 32 | + const listColumn = ref(5); | |
| 33 | 33 | |
| 34 | 34 | const { createMessage } = useMessage(); |
| 35 | 35 | |
| ... | ... | @@ -209,7 +209,7 @@ |
| 209 | 209 | <template #cover> |
| 210 | 210 | <div class="h-full w-full !flex justify-center items-center text-center"> |
| 211 | 211 | <img |
| 212 | - class="w-36 h-36" | |
| 212 | + class="w-full h-36" | |
| 213 | 213 | alt="example" |
| 214 | 214 | :src="item.thumbnail || configurationSrc" |
| 215 | 215 | @click="handlePreview(item)" | ... | ... |
| ... | ... | @@ -57,7 +57,7 @@ |
| 57 | 57 | </div> |
| 58 | 58 | <div v-if="deviceDetail?.deviceInfo?.address" class="mt-4"> |
| 59 | 59 | <p>设备位置</p> |
| 60 | - <div ref="wrapRef" style="height: 550px; width: 100%"></div> | |
| 60 | + <div ref="mapWrapRef" style="height: 550px; width: 100%"></div> | |
| 61 | 61 | </div> |
| 62 | 62 | </div> |
| 63 | 63 | </template> |
| ... | ... | @@ -65,7 +65,7 @@ |
| 65 | 65 | import { defineComponent, ref, unref, nextTick } from 'vue'; |
| 66 | 66 | import { Image, Tooltip } from 'ant-design-vue'; |
| 67 | 67 | import { descSchema } from '../../config/detail.config'; |
| 68 | - import { useScript } from '/@/hooks/web/useScript'; | |
| 68 | + import { useAsyncScript } from '/@/hooks/web/useAsyncScript'; | |
| 69 | 69 | import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard'; |
| 70 | 70 | import { useMessage } from '/@/hooks/web/useMessage'; |
| 71 | 71 | import { BAI_DU_MAP_URL } from '/@/utils/fnUtils'; |
| ... | ... | @@ -77,6 +77,7 @@ |
| 77 | 77 | import { DeviceTypeEnum } from '/@/api/device/model/deviceModel'; |
| 78 | 78 | |
| 79 | 79 | import wz from '/@/assets/images/wz.png'; |
| 80 | + import { useAsyncQueue } from '../../../localtion/useAsyncQueue'; | |
| 80 | 81 | export default defineComponent({ |
| 81 | 82 | components: { |
| 82 | 83 | Image, |
| ... | ... | @@ -101,13 +102,30 @@ |
| 101 | 102 | }); |
| 102 | 103 | |
| 103 | 104 | // 地图 |
| 104 | - const wrapRef = ref<HTMLDivElement | null>(null); | |
| 105 | - const { toPromise } = useScript({ src: BAI_DU_MAP_URL }); | |
| 105 | + const mapWrapRef = ref<HTMLDivElement>(); | |
| 106 | 106 | |
| 107 | - async function initMap(longitude, latitude, address) { | |
| 108 | - await toPromise(); | |
| 107 | + const { executeFlag, setTask } = useAsyncQueue(); | |
| 108 | + const { toPromise } = useAsyncScript({ src: BAI_DU_MAP_URL }); | |
| 109 | + | |
| 110 | + async function initMap(longitude: number, latitude: number, address: string) { | |
| 111 | + if (!(window as any).BMap) { | |
| 112 | + setTask(() => markerMap(longitude, latitude, address)); | |
| 113 | + let interval: Nullable<NodeJS.Timer> = setInterval(() => { | |
| 114 | + if ((window as any).BMap) { | |
| 115 | + executeFlag.value = true; | |
| 116 | + clearInterval(interval!); | |
| 117 | + interval = null; | |
| 118 | + } | |
| 119 | + }, 300); | |
| 120 | + await toPromise(); | |
| 121 | + return; | |
| 122 | + } | |
| 123 | + markerMap(longitude, latitude, address); | |
| 124 | + } | |
| 125 | + | |
| 126 | + async function markerMap(longitude: number, latitude: number, address: string) { | |
| 109 | 127 | await nextTick(); |
| 110 | - const wrapEl = unref(wrapRef); | |
| 128 | + const wrapEl = unref(mapWrapRef); | |
| 111 | 129 | const BMap = (window as any).BMap; |
| 112 | 130 | if (!wrapEl) return; |
| 113 | 131 | const map = new BMap.Map(wrapEl); |
| ... | ... | @@ -132,6 +150,7 @@ |
| 132 | 150 | map.enableScrollWheelZoom(true); |
| 133 | 151 | map.addOverlay(marker); |
| 134 | 152 | } |
| 153 | + | |
| 135 | 154 | const { createMessage } = useMessage(); |
| 136 | 155 | const { clipboardRef } = useCopyToClipboard(); |
| 137 | 156 | const copyTbDeviceId = () => { |
| ... | ... | @@ -166,7 +185,7 @@ |
| 166 | 185 | const remoteConnectiondGateway = () => {}; |
| 167 | 186 | |
| 168 | 187 | return { |
| 169 | - wrapRef, | |
| 188 | + mapWrapRef, | |
| 170 | 189 | copyTbDeviceId, |
| 171 | 190 | copyDeviceToken, |
| 172 | 191 | initMap, | ... | ... |
| ... | ... | @@ -6,6 +6,7 @@ import { HistoryData } from '/@/api/alarm/position/model'; |
| 6 | 6 | import { getDeviceAttributes } from '/@/api/dataBoard'; |
| 7 | 7 | import { DeviceAttributeRecord } from '/@/api/dataBoard/model'; |
| 8 | 8 | import { dateUtil } from '/@/utils/dateUtil'; |
| 9 | +import { isArray } from '/@/utils/is'; | |
| 9 | 10 | import { QueryWay } from '/@/views/report/config/config.data'; |
| 10 | 11 | import { SchemaFiled } from '/@/views/visual/board/detail/config/historyTrend.config'; |
| 11 | 12 | import { DEFAULT_DATE_FORMAT } from '/@/views/visual/board/detail/config/util'; |
| ... | ... | @@ -24,7 +25,8 @@ export function useHistoryData() { |
| 24 | 25 | const getDeviceAttribute = async (record: DeviceOption) => { |
| 25 | 26 | try { |
| 26 | 27 | const { deviceProfileId } = record; |
| 27 | - deviceAttrs.value = (await getDeviceAttributes({ deviceProfileId })) || []; | |
| 28 | + const list = (await getDeviceAttributes({ deviceProfileId })) || []; | |
| 29 | + deviceAttrs.value = isArray(list) ? list : []; | |
| 28 | 30 | } catch (error) { |
| 29 | 31 | throw error; |
| 30 | 32 | } | ... | ... |
| ... | ... | @@ -33,7 +33,9 @@ |
| 33 | 33 | </Button> |
| 34 | 34 | </Authority> |
| 35 | 35 | <Button type="primary" @click="handleOpenTsl"> 物模型TSL </Button> |
| 36 | - <Button v-if="isShowBtn" type="primary" @click="handleImportModel">导入物模型</Button> | |
| 36 | + <Button v-if="false && isShowBtn" type="primary" @click="handleImportModel" | |
| 37 | + >导入物模型</Button | |
| 38 | + > | |
| 37 | 39 | </div> |
| 38 | 40 | <div class="flex gap-2"> |
| 39 | 41 | <Authority :value="ModelOfMatterPermission.RELEASE"> | ... | ... |
| ... | ... | @@ -25,7 +25,7 @@ |
| 25 | 25 | > |
| 26 | 26 | <TabPane :key="FunctionType.PROPERTIES" tab="属性" /> |
| 27 | 27 | <TabPane :key="FunctionType.SERVICE" :disabled="isTCPGatewaySubDevice" tab="服务" /> |
| 28 | - <TabPane :key="FunctionType.EVENTS" tab="事件" :disabled="isTCPGatewaySubDevice" /> | |
| 28 | + <!-- <TabPane :key="FunctionType.EVENTS" tab="事件" :disabled=" isTCPGatewaySubDevice" /> --> | |
| 29 | 29 | </Tabs> |
| 30 | 30 | <Attribute v-if="activeKey === FunctionType.PROPERTIES" ref="AttrRef" /> |
| 31 | 31 | <Service | ... | ... |
| ... | ... | @@ -11,7 +11,7 @@ |
| 11 | 11 | <Tabs type="card" v-model:active-key="activeKey" @change="handleSwitchTsl"> |
| 12 | 12 | <Tabs.TabPane :key="FunctionType.PROPERTIES" tab="属性" /> |
| 13 | 13 | <Tabs.TabPane :key="FunctionType.SERVICE" tab="服务" /> |
| 14 | - <Tabs.TabPane :key="FunctionType.EVENTS" tab="事件" /> | |
| 14 | + <!-- <Tabs.TabPane :key="FunctionType.EVENTS" tab="事件" /> --> | |
| 15 | 15 | <template #tabBarExtraContent> |
| 16 | 16 | <Button @click="handlePremitter"> |
| 17 | 17 | <template #icon> | ... | ... |
| ... | ... | @@ -27,7 +27,7 @@ |
| 27 | 27 | const _value = (event.target as HTMLInputElement).checked; |
| 28 | 28 | emit('update:value', _value); |
| 29 | 29 | emit('change', _value); |
| 30 | - sendCommand(props.value?.slaveDeviceId || props.value?.deviceId, _value); | |
| 30 | + sendCommand(props.value!, _value); | |
| 31 | 31 | }; |
| 32 | 32 | </script> |
| 33 | 33 | ... | ... |
| ... | ... | @@ -32,8 +32,7 @@ |
| 32 | 32 | |
| 33 | 33 | const { sendCommand } = useSendCommand(); |
| 34 | 34 | const handleChange = (value: boolean) => { |
| 35 | - console.log(props.value); | |
| 36 | - sendCommand(props.value.slaveDeviceId! || props.value.deviceId!, value); | |
| 35 | + sendCommand(props.value, value); | |
| 37 | 36 | }; |
| 38 | 37 | |
| 39 | 38 | watchEffect(() => { | ... | ... |
| ... | ... | @@ -22,7 +22,7 @@ |
| 22 | 22 | const _value = (event.target as HTMLInputElement).checked; |
| 23 | 23 | emit('update:value', _value); |
| 24 | 24 | emit('change', _value); |
| 25 | - sendCommand(props.value?.slaveDeviceId || props.value?.deviceId, _value); | |
| 25 | + sendCommand(props.value!, _value); | |
| 26 | 26 | }; |
| 27 | 27 | |
| 28 | 28 | const getRadio = computed(() => { | ... | ... |
| ... | ... | @@ -13,6 +13,7 @@ export interface ControlComponentValue { |
| 13 | 13 | deviceId?: string; |
| 14 | 14 | fontColor?: string; |
| 15 | 15 | slaveDeviceId?: string; |
| 16 | + deviceProfileId?: string; | |
| 16 | 17 | } |
| 17 | 18 | |
| 18 | 19 | export const ControlComponentDefaultConfig: ControlComponentValue = { |
| ... | ... | @@ -31,6 +32,7 @@ export const transformControlConfig = ( |
| 31 | 32 | ...dataSourceRecord.componentInfo, |
| 32 | 33 | attribute: dataSourceRecord.attribute, |
| 33 | 34 | attributeRename: dataSourceRecord.attributeRename, |
| 35 | + deviceProfileId: dataSourceRecord.deviceProfileId, | |
| 34 | 36 | deviceId: dataSourceRecord.deviceId, |
| 35 | 37 | slaveDeviceId: dataSourceRecord.slaveDeviceId, |
| 36 | 38 | } as ControlComponentValue, | ... | ... |
| 1 | +import { ControlComponentValue } from './control.config'; | |
| 2 | +import { getDeviceProfile } from '/@/api/alarm/position'; | |
| 1 | 3 | import { sendCommandOneway } from '/@/api/dataBoard'; |
| 4 | +import { getModelServices } from '/@/api/device/modelOfMatter'; | |
| 2 | 5 | import { useMessage } from '/@/hooks/web/useMessage'; |
| 6 | +import { isString } from '/@/utils/is'; | |
| 3 | 7 | |
| 4 | 8 | const { createMessage } = useMessage(); |
| 5 | 9 | export function useSendCommand() { |
| 6 | - const sendCommand = async (deviceId: string, value: any) => { | |
| 10 | + const sendCommand = async (record: ControlComponentValue, value: any) => { | |
| 11 | + const { deviceId, deviceProfileId, attribute } = record; | |
| 7 | 12 | if (!deviceId) return; |
| 8 | 13 | try { |
| 14 | + const list = await getDeviceProfile(); | |
| 15 | + const deviceProfile = list.find((item) => item.id === deviceProfileId); | |
| 16 | + if (!deviceProfile) return; | |
| 17 | + let params: string | Recordable = { | |
| 18 | + [attribute!]: Number(value), | |
| 19 | + }; | |
| 20 | + if (deviceProfile.transportType === 'TCP') { | |
| 21 | + const serviceList = await getModelServices({ deviceProfileId: deviceProfileId! }); | |
| 22 | + const record = serviceList.find((item) => item.identifier === attribute); | |
| 23 | + const sendCommand = record?.functionJson.inputData?.at(0)?.serviceCommand || ''; | |
| 24 | + params = isString(sendCommand) ? sendCommand : JSON.stringify(sendCommand); | |
| 25 | + } | |
| 9 | 26 | await sendCommandOneway({ |
| 10 | 27 | deviceId, |
| 11 | 28 | value: { |
| 12 | - params: Number(value), | |
| 29 | + params: params, | |
| 13 | 30 | persistent: true, |
| 14 | 31 | additionalInfo: { |
| 15 | 32 | cmdType: 'API', | ... | ... |
| ... | ... | @@ -5,9 +5,9 @@ |
| 5 | 5 | SettingOutlined, |
| 6 | 6 | SwapOutlined, |
| 7 | 7 | } from '@ant-design/icons-vue'; |
| 8 | - import { Tooltip, Button } from 'ant-design-vue'; | |
| 8 | + import { Tooltip, Button, Alert } from 'ant-design-vue'; | |
| 9 | 9 | import { FormActionType, useForm } from '/@/components/Form'; |
| 10 | - import { basicSchema, DataSourceField } from '../config/basicConfiguration'; | |
| 10 | + import { basicSchema, DataSourceField, isMapComponent } from '../config/basicConfiguration'; | |
| 11 | 11 | import BasicForm from '/@/components/Form/src/BasicForm.vue'; |
| 12 | 12 | import { ref, shallowReactive, unref, nextTick, watch, computed, onMounted } from 'vue'; |
| 13 | 13 | import VisualOptionsModal from './VisualOptionsModal.vue'; |
| ... | ... | @@ -22,6 +22,7 @@ |
| 22 | 22 | import { useSortable } from '/@/hooks/web/useSortable'; |
| 23 | 23 | import { cloneDeep } from 'lodash-es'; |
| 24 | 24 | import { frontComponentMap } from '../../components/help'; |
| 25 | + import { isControlComponent } from '../config/basicConfiguration'; | |
| 25 | 26 | |
| 26 | 27 | type DataSourceFormEL = { [key: string]: Nullable<FormActionType> }; |
| 27 | 28 | |
| ... | ... | @@ -250,6 +251,14 @@ |
| 250 | 251 | inited = true; |
| 251 | 252 | } |
| 252 | 253 | |
| 254 | + const isControlCmp = computed(() => { | |
| 255 | + return isControlComponent(props.frontId as FrontComponent); | |
| 256 | + }); | |
| 257 | + | |
| 258 | + const isMapCmp = computed(() => { | |
| 259 | + return isMapComponent(props.frontId as FrontComponent); | |
| 260 | + }); | |
| 261 | + | |
| 253 | 262 | onMounted(() => handleSort()); |
| 254 | 263 | |
| 255 | 264 | defineExpose({ |
| ... | ... | @@ -264,7 +273,25 @@ |
| 264 | 273 | <div class="w-3/4"> |
| 265 | 274 | <BasicForm @register="basicRegister" class="w-full" /> |
| 266 | 275 | </div> |
| 276 | + <Alert type="info" show-icon v-if="isControlCmp"> | |
| 277 | + <template #description> | |
| 278 | + <div> | |
| 279 | + 控制组件数据源为TCP产品,则其控制命令下发为TCP产品 物模型=>服务,且不具备状态显示功能. | |
| 280 | + </div> | |
| 281 | + <div> | |
| 282 | + 控制组件数据源为非TCP产品,则其控制命令下发为产品 物模型=>属性,且具备状态显示功能. | |
| 283 | + </div> | |
| 284 | + </template> | |
| 285 | + </Alert> | |
| 286 | + <Alert type="info" show-icon v-if="isMapCmp"> | |
| 287 | + <template #description> | |
| 288 | + <div> | |
| 289 | + 地图组件,需绑定两个数据源,且数据源为同一设备。第一数据源为经度,第二数据源为维度,否则地图组件不能正常显示。 | |
| 290 | + </div> | |
| 291 | + </template> | |
| 292 | + </Alert> | |
| 267 | 293 | <h3 class="w-24 flex-shrink-0 text-right pr-2 my-4">选择数据源</h3> |
| 294 | + | |
| 268 | 295 | <section ref="formListEl"> |
| 269 | 296 | <div v-for="item in dataSource" :data-id="item.id" :key="item.id" class="flex bg-light-50"> |
| 270 | 297 | <div class="w-24 text-right flex right justify-end"> 选择设备 </div> | ... | ... |
| ... | ... | @@ -4,6 +4,8 @@ import { FormSchema } from '/@/components/Form'; |
| 4 | 4 | import { copyTransFun } from '/@/utils/fnUtils'; |
| 5 | 5 | import { DeviceAttributeParams, MasterDeviceList } from '/@/api/dataBoard/model'; |
| 6 | 6 | import { FrontComponent } from '../../const/const'; |
| 7 | +import { getDeviceProfile } from '/@/api/alarm/position'; | |
| 8 | +import { getModelServices } from '/@/api/device/modelOfMatter'; | |
| 7 | 9 | |
| 8 | 10 | export enum BasicConfigField { |
| 9 | 11 | NAME = 'name', |
| ... | ... | @@ -18,13 +20,22 @@ const getDeviceAttribute = async (params: DeviceAttributeParams) => { |
| 18 | 20 | return []; |
| 19 | 21 | }; |
| 20 | 22 | |
| 23 | +const getDeviceService = async (deviceProfileId: string) => { | |
| 24 | + try { | |
| 25 | + const data = await getModelServices({ deviceProfileId }); | |
| 26 | + if (data) | |
| 27 | + return data.map((item) => ({ ...item, label: item.functionName, value: item.identifier })); | |
| 28 | + } catch (error) {} | |
| 29 | + return []; | |
| 30 | +}; | |
| 31 | + | |
| 21 | 32 | export enum DataSourceField { |
| 22 | 33 | IS_GATEWAY_DEVICE = 'gatewayDevice', |
| 34 | + TRANSPORT_TYPE = 'transportType', | |
| 23 | 35 | ORIGINATION_ID = 'organizationId', |
| 24 | 36 | DEVICE_ID = 'deviceId', |
| 25 | 37 | SLAVE_DEVICE_ID = 'slaveDeviceId', |
| 26 | 38 | DEVICE_PROFILE_ID = 'deviceProfileId', |
| 27 | - SLAVE_DEVICE_PROFILE_ID = 'slaveDeviceProfileId', | |
| 28 | 39 | ATTRIBUTE = 'attribute', |
| 29 | 40 | ATTRIBUTE_RENAME = 'attributeRename', |
| 30 | 41 | DEVICE_NAME = 'deviceName', |
| ... | ... | @@ -33,16 +44,25 @@ export enum DataSourceField { |
| 33 | 44 | LATITUDE_ATTRIBUTE = 'latitudeAttribute', |
| 34 | 45 | } |
| 35 | 46 | |
| 36 | -const isControlComponent = (frontId: FrontComponent) => { | |
| 47 | +export const isControlComponent = (frontId: FrontComponent) => { | |
| 37 | 48 | const list = [ |
| 38 | 49 | FrontComponent.CONTROL_COMPONENT_SLIDING_SWITCH, |
| 39 | 50 | FrontComponent.CONTROL_COMPONENT_SWITCH_WITH_ICON, |
| 40 | 51 | FrontComponent.CONTROL_COMPONENT_TOGGLE_SWITCH, |
| 41 | 52 | ]; |
| 42 | - if (list.includes(frontId)) { | |
| 43 | - return true; | |
| 44 | - } | |
| 45 | - return false; | |
| 53 | + return list.includes(frontId); | |
| 54 | +}; | |
| 55 | + | |
| 56 | +export const isMapComponent = (frontId: FrontComponent) => { | |
| 57 | + const list = [ | |
| 58 | + FrontComponent.MAP_COMPONENT_TRACK_HISTORY, | |
| 59 | + FrontComponent.MAP_COMPONENT_TRACK_REAL, | |
| 60 | + ]; | |
| 61 | + return list.includes(frontId); | |
| 62 | +}; | |
| 63 | + | |
| 64 | +const isTcpProfile = (transportType: string) => { | |
| 65 | + return transportType === 'TCP'; | |
| 46 | 66 | }; |
| 47 | 67 | |
| 48 | 68 | export const basicSchema: FormSchema[] = [ |
| ... | ... | @@ -81,6 +101,45 @@ export const dataSourceSchema = (frontId?: FrontComponent): FormSchema[] => { |
| 81 | 101 | show: false, |
| 82 | 102 | }, |
| 83 | 103 | { |
| 104 | + field: DataSourceField.TRANSPORT_TYPE, | |
| 105 | + component: 'Input', | |
| 106 | + label: '设备配置类型', | |
| 107 | + show: false, | |
| 108 | + }, | |
| 109 | + { | |
| 110 | + field: DataSourceField.DEVICE_PROFILE_ID, | |
| 111 | + component: 'ApiSelect', | |
| 112 | + label: '产品', | |
| 113 | + colProps: { span: 8 }, | |
| 114 | + rules: [{ required: true, message: '产品为必填项' }], | |
| 115 | + componentProps: ({ formActionType, formModel }) => { | |
| 116 | + const { setFieldsValue } = formActionType; | |
| 117 | + const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID]; | |
| 118 | + return { | |
| 119 | + api: async () => { | |
| 120 | + const list = await getDeviceProfile(); | |
| 121 | + if (deviceProfileId) { | |
| 122 | + const record = list.find((item) => item.id === deviceProfileId); | |
| 123 | + setFieldsValue({ [DataSourceField.TRANSPORT_TYPE]: record?.transportType }); | |
| 124 | + } | |
| 125 | + return list; | |
| 126 | + }, | |
| 127 | + labelField: 'name', | |
| 128 | + valueField: 'id', | |
| 129 | + onChange: (_, option: Record<'transportType', string>) => { | |
| 130 | + console.log(option); | |
| 131 | + setFieldsValue({ | |
| 132 | + [DataSourceField.DEVICE_ID]: null, | |
| 133 | + [DataSourceField.ATTRIBUTE]: null, | |
| 134 | + [DataSourceField.SLAVE_DEVICE_ID]: null, | |
| 135 | + [DataSourceField.IS_GATEWAY_DEVICE]: false, | |
| 136 | + [DataSourceField.TRANSPORT_TYPE]: option.transportType, | |
| 137 | + }); | |
| 138 | + }, | |
| 139 | + }; | |
| 140 | + }, | |
| 141 | + }, | |
| 142 | + { | |
| 84 | 143 | field: DataSourceField.ORIGINATION_ID, |
| 85 | 144 | component: 'ApiTreeSelect', |
| 86 | 145 | label: '组织', |
| ... | ... | @@ -101,8 +160,6 @@ export const dataSourceSchema = (frontId?: FrontComponent): FormSchema[] => { |
| 101 | 160 | [DataSourceField.ATTRIBUTE]: null, |
| 102 | 161 | [DataSourceField.SLAVE_DEVICE_ID]: null, |
| 103 | 162 | [DataSourceField.IS_GATEWAY_DEVICE]: false, |
| 104 | - [DataSourceField.DEVICE_PROFILE_ID]: null, | |
| 105 | - [DataSourceField.SLAVE_DEVICE_PROFILE_ID]: null, | |
| 106 | 163 | }); |
| 107 | 164 | }, |
| 108 | 165 | getPopupContainer: () => document.body, |
| ... | ... | @@ -124,16 +181,13 @@ export const dataSourceSchema = (frontId?: FrontComponent): FormSchema[] => { |
| 124 | 181 | componentProps({ formModel, formActionType }) { |
| 125 | 182 | const { setFieldsValue } = formActionType; |
| 126 | 183 | const organizationId = formModel[DataSourceField.ORIGINATION_ID]; |
| 127 | - const deviceId = formModel[DataSourceField.DEVICE_ID]; | |
| 184 | + const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID]; | |
| 185 | + | |
| 128 | 186 | return { |
| 129 | 187 | api: async () => { |
| 130 | 188 | if (organizationId) { |
| 131 | 189 | try { |
| 132 | - const data = await getAllDeviceByOrg(organizationId); | |
| 133 | - if (deviceId) { | |
| 134 | - const record = data.find((item) => item.id === deviceId); | |
| 135 | - setFieldsValue({ [DataSourceField.DEVICE_PROFILE_ID]: record?.deviceProfileId }); | |
| 136 | - } | |
| 190 | + const data = await getAllDeviceByOrg(organizationId, deviceProfileId); | |
| 137 | 191 | if (data) |
| 138 | 192 | return data.map((item) => ({ |
| 139 | 193 | ...item, |
| ... | ... | @@ -150,9 +204,7 @@ export const dataSourceSchema = (frontId?: FrontComponent): FormSchema[] => { |
| 150 | 204 | setFieldsValue({ |
| 151 | 205 | [DataSourceField.ATTRIBUTE]: null, |
| 152 | 206 | [DataSourceField.IS_GATEWAY_DEVICE]: record?.deviceType === 'GATEWAY', |
| 153 | - [DataSourceField.DEVICE_PROFILE_ID]: record?.deviceProfileId, | |
| 154 | 207 | [DataSourceField.SLAVE_DEVICE_ID]: null, |
| 155 | - [DataSourceField.SLAVE_DEVICE_PROFILE_ID]: null, | |
| 156 | 208 | [DataSourceField.DEVICE_NAME]: record?.label, |
| 157 | 209 | }); |
| 158 | 210 | }, |
| ... | ... | @@ -162,19 +214,18 @@ export const dataSourceSchema = (frontId?: FrontComponent): FormSchema[] => { |
| 162 | 214 | }, |
| 163 | 215 | }, |
| 164 | 216 | { |
| 165 | - field: DataSourceField.SLAVE_DEVICE_PROFILE_ID, | |
| 166 | - component: 'Input', | |
| 167 | - label: '', | |
| 168 | - show: false, | |
| 169 | - }, | |
| 170 | - { | |
| 171 | 217 | field: DataSourceField.SLAVE_DEVICE_ID, |
| 172 | 218 | label: '网关子设备', |
| 173 | 219 | component: 'ApiSelect', |
| 174 | 220 | colProps: { span: 8 }, |
| 175 | 221 | rules: [{ required: true, message: '网关子设备为必填项' }], |
| 222 | + show: false, | |
| 176 | 223 | ifShow({ model }) { |
| 177 | - return model[DataSourceField.IS_GATEWAY_DEVICE]; | |
| 224 | + const transportType = model[DataSourceField.TRANSPORT_TYPE]; | |
| 225 | + return ( | |
| 226 | + !(isControlComponent(frontId as FrontComponent) && isTcpProfile(transportType)) && | |
| 227 | + model[DataSourceField.IS_GATEWAY_DEVICE] | |
| 228 | + ); | |
| 178 | 229 | }, |
| 179 | 230 | dynamicRules({ model }) { |
| 180 | 231 | return [ |
| ... | ... | @@ -186,16 +237,12 @@ export const dataSourceSchema = (frontId?: FrontComponent): FormSchema[] => { |
| 186 | 237 | const organizationId = formModel[DataSourceField.ORIGINATION_ID]; |
| 187 | 238 | const isGatewayDevice = formModel[DataSourceField.IS_GATEWAY_DEVICE]; |
| 188 | 239 | const deviceId = formModel[DataSourceField.DEVICE_ID]; |
| 189 | - const slaveDeviceId = formModel[DataSourceField.SLAVE_DEVICE_ID]; | |
| 240 | + | |
| 190 | 241 | return { |
| 191 | 242 | api: async () => { |
| 192 | 243 | if (organizationId && isGatewayDevice) { |
| 193 | 244 | try { |
| 194 | 245 | const data = await getGatewaySlaveDevice({ organizationId, masterId: deviceId }); |
| 195 | - if (slaveDeviceId) { | |
| 196 | - const record = data.find((item) => item.id === slaveDeviceId); | |
| 197 | - setFieldsValue({ [DataSourceField.DEVICE_PROFILE_ID]: record?.deviceProfileId }); | |
| 198 | - } | |
| 199 | 246 | if (data) |
| 200 | 247 | return data.map((item) => ({ |
| 201 | 248 | ...item, |
| ... | ... | @@ -210,7 +257,6 @@ export const dataSourceSchema = (frontId?: FrontComponent): FormSchema[] => { |
| 210 | 257 | onChange(_value, record: MasterDeviceList) { |
| 211 | 258 | setFieldsValue({ |
| 212 | 259 | [DataSourceField.ATTRIBUTE]: null, |
| 213 | - [DataSourceField.SLAVE_DEVICE_PROFILE_ID]: record.deviceProfileId, | |
| 214 | 260 | [DataSourceField.DEVICE_NAME]: record?.label, |
| 215 | 261 | }); |
| 216 | 262 | }, |
| ... | ... | @@ -226,33 +272,30 @@ export const dataSourceSchema = (frontId?: FrontComponent): FormSchema[] => { |
| 226 | 272 | colProps: { span: 8 }, |
| 227 | 273 | rules: [{ required: true, message: '属性为必填项' }], |
| 228 | 274 | componentProps({ formModel }) { |
| 229 | - const isGatewayDevice = formModel[DataSourceField.IS_GATEWAY_DEVICE]; | |
| 230 | - const deviceId = formModel[DataSourceField.DEVICE_ID]; | |
| 231 | 275 | const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID]; |
| 232 | - const slaveDeviceId = formModel[DataSourceField.SLAVE_DEVICE_ID]; | |
| 233 | - const slaveDeviceProfileId = formModel[DataSourceField.SLAVE_DEVICE_PROFILE_ID]; | |
| 276 | + const transportType = formModel[DataSourceField.TRANSPORT_TYPE]; | |
| 234 | 277 | |
| 235 | 278 | return { |
| 236 | 279 | api: async () => { |
| 237 | - if (deviceId) { | |
| 238 | - try { | |
| 239 | - if (isGatewayDevice && slaveDeviceId && slaveDeviceProfileId) { | |
| 240 | - return await getDeviceAttribute({ | |
| 241 | - deviceProfileId: slaveDeviceProfileId, | |
| 242 | - dataType: isControlComponent(frontId!) ? 'BOOL' : undefined, | |
| 243 | - }); | |
| 244 | - } | |
| 245 | - if (!isGatewayDevice && deviceProfileId) { | |
| 246 | - return await getDeviceAttribute({ | |
| 247 | - deviceProfileId, | |
| 248 | - dataType: isControlComponent(frontId!) ? 'BOOL' : undefined, | |
| 249 | - }); | |
| 250 | - } | |
| 251 | - } catch (error) {} | |
| 252 | - } | |
| 280 | + try { | |
| 281 | + if (isControlComponent(frontId as FrontComponent) && isTcpProfile(transportType)) { | |
| 282 | + return await getDeviceService(deviceProfileId); | |
| 283 | + } | |
| 284 | + | |
| 285 | + if (deviceProfileId) { | |
| 286 | + return await getDeviceAttribute({ | |
| 287 | + deviceProfileId, | |
| 288 | + dataType: isControlComponent(frontId!) ? 'BOOL' : undefined, | |
| 289 | + }); | |
| 290 | + } | |
| 291 | + } catch (error) {} | |
| 292 | + | |
| 253 | 293 | return []; |
| 254 | 294 | }, |
| 255 | - placeholder: '请选择属性', | |
| 295 | + placeholder: | |
| 296 | + isControlComponent(frontId as FrontComponent) && isTcpProfile(transportType) | |
| 297 | + ? '请选择服务' | |
| 298 | + : '请选择属性', | |
| 256 | 299 | getPopupContainer: () => document.body, |
| 257 | 300 | }; |
| 258 | 301 | }, | ... | ... |