feat: 子设备新增并绑定
This commit is contained in:
parent
c932dc015f
commit
c9b56626d5
|
@ -85,7 +85,7 @@ export const batchDeleteDevice = (data: string[]) => server.put(`/device-instanc
|
|||
*/
|
||||
export const deviceTemplateDownload = (productId: string, type: string) => `${BASE_API_PATH}/device-instance/${productId}/template.${type}`
|
||||
|
||||
export const templateDownload = (productId: string, type: string) => server.get(`/device-instance/${productId}/template.${type}`,{},{responseType: 'blob'})
|
||||
export const templateDownload = (productId: string, type: string) => server.get(`/device-instance/${productId}/template.${type}`, {}, { responseType: 'blob' })
|
||||
/**
|
||||
* 设备导入
|
||||
* @param productId 产品id
|
||||
|
@ -245,6 +245,22 @@ export const unbindBatchDevice = (deviceId: string, data: Record<string, any>) =
|
|||
*/
|
||||
export const bindDevice = (deviceId: string, data: Record<string, any>) => server.post(`/device/gateway/${deviceId}/bind`, data)
|
||||
|
||||
/**
|
||||
* 获取产品列表
|
||||
* @param data
|
||||
*/
|
||||
export const getProductListNoPage = (data: any) => server.post('/device/product/_query/no-paging?paging=false', data)
|
||||
|
||||
/**
|
||||
* 修改设备
|
||||
*/
|
||||
export const editDevice = (parmas: any) => server.patch('/device-instance', parmas)
|
||||
|
||||
/**
|
||||
* 新增设备
|
||||
*/
|
||||
export const addDevice = (params: any) => server.post("/device-instance", params)
|
||||
|
||||
/**
|
||||
* 设备接入网关状态
|
||||
* @param id 设备接入网关id
|
||||
|
@ -504,14 +520,14 @@ export const productCode = (productId: string) => server.get(`/device/transparen
|
|||
* @param productId
|
||||
* @returns
|
||||
*/
|
||||
export const saveProductCode = (productId: string,data: Record<string, unknown>) => server.post(`/device/transparent-codec/${productId}`,data)
|
||||
export const saveProductCode = (productId: string, data: Record<string, unknown>) => server.post(`/device/transparent-codec/${productId}`, data)
|
||||
/**
|
||||
* 获取设备解析规则
|
||||
* @param productId
|
||||
* @param deviceId
|
||||
* @returns
|
||||
*/
|
||||
export const deviceCode = (productId: string,deviceId:string) => server.get(`device/transparent-codec/${productId}/${deviceId}`)
|
||||
export const deviceCode = (productId: string, deviceId: string) => server.get(`device/transparent-codec/${productId}/${deviceId}`)
|
||||
/**
|
||||
* 保存设备解析规则
|
||||
* @param productId
|
||||
|
@ -520,13 +536,13 @@ export const deviceCode = (productId: string,deviceId:string) => server.get(`dev
|
|||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const saveDeviceCode = (productId: string,deviceId:string,data: Record<string, unknown>) => server.post(`/device/transparent-codec/${productId}/${deviceId}`,data)
|
||||
export const saveDeviceCode = (productId: string, deviceId: string, data: Record<string, unknown>) => server.post(`/device/transparent-codec/${productId}/${deviceId}`, data)
|
||||
/**
|
||||
* 编码测试
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const testCode = (data: Record<string, unknown>) => server.post(`/device/transparent-codec/decode-test`,data)
|
||||
export const testCode = (data: Record<string, unknown>) => server.post(`/device/transparent-codec/decode-test`, data)
|
||||
/**
|
||||
* 删除设备解析规则
|
||||
* @param productId
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
@search="handleSearch"
|
||||
type="simple"
|
||||
/>
|
||||
<JTable
|
||||
<JProTable
|
||||
ref="bindDeviceRef"
|
||||
:columns="columns"
|
||||
:request="query"
|
||||
|
@ -78,7 +78,7 @@
|
|||
:status="statusMap.get(slotProps.state.value)"
|
||||
/>
|
||||
</template>
|
||||
</JTable>
|
||||
</JProTable>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
<template>
|
||||
<a-select allowClear v-model:value="_value" @change="onChange" placeholder="请选择" style="width: 100%">
|
||||
<a-select-option
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
:label="item.name"
|
||||
:filter-option="filterOption"
|
||||
>{{ item.name }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
edgeCollector,
|
||||
edgePoint,
|
||||
} from '@/api/device/instance';
|
||||
|
||||
const _props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'POINT',
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
edgeId: {
|
||||
type: String,
|
||||
default: '',
|
||||
}
|
||||
});
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
type Emits = {
|
||||
(e: 'update:modelValue', data: string | undefined): void;
|
||||
};
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const list = ref<any[]>([]);
|
||||
const _value = ref<string | undefined>(undefined);
|
||||
|
||||
watchEffect(() => {
|
||||
_value.value = _props.modelValue;
|
||||
});
|
||||
|
||||
const onChange = (_val: string) => {
|
||||
emit('update:modelValue', _val);
|
||||
};
|
||||
|
||||
const getCollector = async (_val: string) => {
|
||||
if (!_val) {
|
||||
return [];
|
||||
} else {
|
||||
const resp = await edgeCollector(_props.edgeId, {
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'channelId',
|
||||
value: _val,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
list.value = (resp.result as any[])?.[0] || []
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getPoint = async (_val: string) => {
|
||||
if (!_val) {
|
||||
return [];
|
||||
} else {
|
||||
const resp = await edgePoint(_props.edgeId, {
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'collectorId',
|
||||
value: _val,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
list.value = (resp.result as any[])?.[0] || []
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
if (_props.id) {
|
||||
if (_props.type === 'POINT') {
|
||||
getPoint(_props.id);
|
||||
} else {
|
||||
getCollector(_props.id);
|
||||
}
|
||||
} else {
|
||||
list.value = [];
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -0,0 +1,212 @@
|
|||
<template>
|
||||
<a-modal
|
||||
width="900px"
|
||||
title="批量映射"
|
||||
visible
|
||||
@ok="handleClick"
|
||||
@cancel="handleClose"
|
||||
>
|
||||
<div class="map-tree">
|
||||
<div class="map-tree-top">
|
||||
采集器的点位名称与属性名称一致时将自动映射绑定;有多个采集器点位名称与属性名称一致时以第1个采集器的点位数据进行绑定
|
||||
</div>
|
||||
<a-spin :spinning="loading">
|
||||
<div class="map-tree-content">
|
||||
<a-card class="map-tree-content-card" title="源数据">
|
||||
<a-tree
|
||||
checkable
|
||||
:height="300"
|
||||
:tree-data="dataSource"
|
||||
:checkedKeys="checkedKeys"
|
||||
@check="onCheck"
|
||||
/>
|
||||
</a-card>
|
||||
<div style="width: 100px">
|
||||
<a-button
|
||||
:disabled="rightList.length >= leftList.length"
|
||||
@click="onRight"
|
||||
>加入右侧</a-button
|
||||
>
|
||||
</div>
|
||||
<a-card class="map-tree-content-card" title="采集器">
|
||||
<a-list
|
||||
size="small"
|
||||
:data-source="rightList"
|
||||
class="map-tree-content-card-list"
|
||||
>
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
{{ item.title }}
|
||||
<template #actions>
|
||||
<a-popconfirm
|
||||
title="确定删除?"
|
||||
@confirm="_delete(item.key)"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { treeEdgeMap, saveEdgeMap, addDevice } from '@/api/device/instance';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
const _props = defineProps({
|
||||
metaData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
deviceId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
edgeId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
deviceData: {
|
||||
type: Object,
|
||||
},
|
||||
});
|
||||
const _emits = defineEmits(['close', 'save']);
|
||||
|
||||
const checkedKeys = ref<string[]>([]);
|
||||
|
||||
const leftList = ref<any[]>([]);
|
||||
const rightList = ref<any[]>([]);
|
||||
|
||||
const dataSource = ref<any[]>([]);
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
const handleData = (data: any[], type: string) => {
|
||||
data.forEach((item) => {
|
||||
item.key = item.id;
|
||||
item.title = item.name;
|
||||
item.checkable = type === 'collectors';
|
||||
if (
|
||||
item.collectors &&
|
||||
Array.isArray(item.collectors) &&
|
||||
item.collectors.length
|
||||
) {
|
||||
item.children = handleData(item.collectors, 'collectors');
|
||||
}
|
||||
if (item.points && Array.isArray(item.points) && item.points.length) {
|
||||
item.children = handleData(item.points, 'points');
|
||||
}
|
||||
});
|
||||
return data as any[];
|
||||
};
|
||||
|
||||
const handleSearch = async () => {
|
||||
loading.value = true;
|
||||
const resp = await treeEdgeMap(_props.edgeId);
|
||||
loading.value = false;
|
||||
if (resp.status === 200) {
|
||||
dataSource.value = handleData((resp.result as any[])?.[0], 'channel');
|
||||
}
|
||||
};
|
||||
|
||||
const onCheck = (keys: string[], e: any) => {
|
||||
checkedKeys.value = [...keys];
|
||||
leftList.value = e?.checkedNodes || [];
|
||||
};
|
||||
|
||||
const onRight = () => {
|
||||
rightList.value = leftList.value;
|
||||
};
|
||||
|
||||
const _delete = (_key: string) => {
|
||||
const _index = rightList.value.findIndex((i) => i.key === _key);
|
||||
rightList.value.splice(_index, 1);
|
||||
checkedKeys.value = rightList.value.map((i) => i.key);
|
||||
leftList.value = rightList.value;
|
||||
};
|
||||
|
||||
const handleClick = async () => {
|
||||
if (!rightList.value.length) {
|
||||
message.warning('请选择采集器');
|
||||
} else {
|
||||
const params: any[] = [];
|
||||
rightList.value.map((item: any) => {
|
||||
const array = (item.children || []).map((element: any) => ({
|
||||
channelId: item.parentId,
|
||||
collectorId: element.collectorId,
|
||||
pointId: element.id,
|
||||
metadataType: 'property',
|
||||
metadataId: (_props.metaData as any[]).find(
|
||||
(i: any) => i.name === element.name,
|
||||
)?.metadataId,
|
||||
provider: dataSource.value.find(
|
||||
(it: any) => it.id === item.parentId,
|
||||
).provider,
|
||||
}));
|
||||
params.push(...array);
|
||||
});
|
||||
const filterParms = params.filter((item) => !!item.metadataId);
|
||||
if (_props.deviceId) {
|
||||
if (filterParms && filterParms.length !== 0) {
|
||||
const res = await saveEdgeMap(_props.edgeId, {
|
||||
deviceId: _props.deviceId,
|
||||
provider: filterParms[0]?.provider,
|
||||
requestList: filterParms,
|
||||
});
|
||||
if (res.status === 200) {
|
||||
message.success('操作成功');
|
||||
_emits('save');
|
||||
}
|
||||
} else {
|
||||
message.error('暂无对应属性的映射');
|
||||
}
|
||||
} else {
|
||||
if (filterParms && filterParms.length !== 0) {
|
||||
const res = await addDevice(_props.deviceData);
|
||||
if (res.status === 200) {
|
||||
const resq = await saveEdgeMap(_props.edgeId, {
|
||||
deviceId: res.result?.id,
|
||||
provider: filterParms[0]?.provider,
|
||||
requestList: filterParms,
|
||||
});
|
||||
if (res.status === 200) {
|
||||
message.success('操作成功');
|
||||
_emits('save');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleClose = () => {
|
||||
_emits('close');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (_props.edgeId) {
|
||||
handleSearch();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.map-tree-content {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.map-tree-content-card {
|
||||
width: 350px;
|
||||
height: 400px;
|
||||
|
||||
.map-tree-content-card-list {
|
||||
overflow-y: auto;
|
||||
height: 300px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,337 @@
|
|||
<template>
|
||||
<a-spin :spinning="loading" v-if="_metadata">
|
||||
<a-card :bordered="false">
|
||||
<template #title>
|
||||
<TitleComponent data="点位映射"></TitleComponent>
|
||||
</template>
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button @click="showModal">批量映射</a-button>
|
||||
<a-button type="primary" @click="onSave">保存</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<a-form ref="formRef" :model="modelRef">
|
||||
<a-table :dataSource="modelRef.dataSource" :columns="columns">
|
||||
<template #headerCell="{ column }">
|
||||
<template v-if="column.key === 'collectorId'">
|
||||
采集器
|
||||
<a-tooltip title="边缘网关代理的真实物理设备">
|
||||
<AIcon type="QuestionCircleOutlined" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.dataIndex === 'channelId'">
|
||||
<a-form-item
|
||||
:name="['dataSource', index, 'channelId']"
|
||||
>
|
||||
<a-select
|
||||
style="width: 100%"
|
||||
v-model:value="record[column.dataIndex]"
|
||||
placeholder="请选择"
|
||||
allowClear
|
||||
:filter-option="filterOption"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in channelList"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
>{{ item.label }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'collectorId'">
|
||||
<a-form-item
|
||||
:name="['dataSource', index, 'collectorId']"
|
||||
:rules="[
|
||||
{
|
||||
required: !!record.channelId,
|
||||
message: '请选择采集器',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<MSelect
|
||||
v-model="record[column.dataIndex]"
|
||||
:id="record.channelId"
|
||||
type="COLLECTOR"
|
||||
:edgeId="instanceStore.current.id"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'pointId'">
|
||||
<a-form-item
|
||||
:name="['dataSource', index, 'pointId']"
|
||||
:rules="[
|
||||
{
|
||||
required: !!record.channelId,
|
||||
message: '请选择点位',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<MSelect
|
||||
v-model="record[column.dataIndex]"
|
||||
:id="record.collectorId"
|
||||
type="POINT"
|
||||
:edgeId="instanceStore.current.id"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'id'">
|
||||
<a-badge
|
||||
v-if="record[column.dataIndex]"
|
||||
status="success"
|
||||
text="已绑定"
|
||||
/>
|
||||
<a-badge v-else status="error" text="未绑定" />
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-tooltip title="解绑">
|
||||
<a-popconfirm
|
||||
title="确认解绑"
|
||||
:disabled="!record.id"
|
||||
@confirm="unbind(record.id)"
|
||||
>
|
||||
<a-button type="link" :disabled="!record.id"
|
||||
><AIcon type="icon-jiebang"
|
||||
/></a-button>
|
||||
</a-popconfirm>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-form>
|
||||
</a-card>
|
||||
<PatchMapping
|
||||
:deviceId="instanceStore.current.parentId"
|
||||
v-if="visible"
|
||||
@close="visible = false"
|
||||
@save="onPatchBind"
|
||||
:metaData="modelRef.dataSource"
|
||||
:edgeId="instanceStore.current.id"
|
||||
:deviceData="deviceData"
|
||||
/>
|
||||
</a-spin>
|
||||
<a-card v-else>
|
||||
<JEmpty description="暂无数据,请配置物模型" style="margin: 10% 0" />
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import {
|
||||
getEdgeMap,
|
||||
saveEdgeMap,
|
||||
removeEdgeMap,
|
||||
edgeChannel,
|
||||
addDevice,
|
||||
editDevice,
|
||||
} from '@/api/device/instance';
|
||||
import MSelect from './MSelect.vue';
|
||||
import PatchMapping from './PatchMapping.vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { inject } from 'vue';
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'metadataName',
|
||||
key: 'metadataName',
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
title: '通道',
|
||||
dataIndex: 'channelId',
|
||||
key: 'channelId',
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
title: '采集器',
|
||||
dataIndex: 'collectorId',
|
||||
key: 'collectorId',
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
title: '点位',
|
||||
key: 'pointId',
|
||||
dataIndex: 'pointId',
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'id',
|
||||
dataIndex: 'id',
|
||||
width: '10%',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: '10%',
|
||||
},
|
||||
];
|
||||
const validate = inject('validate');
|
||||
const form = ref();
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
const { productList } = defineProps(['productList']);
|
||||
const _emit = defineEmits(['close']);
|
||||
const instanceStore = useInstanceStore();
|
||||
let _metadata = ref();
|
||||
const loading = ref<boolean>(false);
|
||||
const channelList = ref([]);
|
||||
|
||||
const modelRef = reactive({
|
||||
dataSource: [],
|
||||
});
|
||||
const deviceData = ref();
|
||||
const formRef = ref();
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
const getChannel = async () => {
|
||||
if (instanceStore.current?.id) {
|
||||
const resp: any = await edgeChannel(instanceStore.current.id);
|
||||
if (resp.status === 200) {
|
||||
channelList.value = resp.result?.[0]?.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
provider: item.provider,
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = async () => {
|
||||
loading.value = true;
|
||||
modelRef.dataSource = _metadata;
|
||||
getChannel();
|
||||
if (_metadata && _metadata.length) {
|
||||
const resp: any = await getEdgeMap(instanceStore.current?.orgId || '', {
|
||||
deviceId: instanceStore.current.id,
|
||||
query: {},
|
||||
}).catch(() => {
|
||||
modelRef.dataSource = _metadata;
|
||||
loading.value = false;
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
const array = resp.result?.[0].reduce((x: any, y: any) => {
|
||||
const metadataId = _metadata.find(
|
||||
(item: any) => item.metadataId === y.metadataId,
|
||||
);
|
||||
if (metadataId) {
|
||||
Object.assign(metadataId, y);
|
||||
} else {
|
||||
x.push(y);
|
||||
}
|
||||
return x;
|
||||
}, _metadata);
|
||||
modelRef.dataSource = array;
|
||||
}
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const unbind = async (id: string) => {
|
||||
if (id) {
|
||||
const resp = await removeEdgeMap(
|
||||
instanceStore.current?.parentId || '',
|
||||
{
|
||||
deviceId: instanceStore.current.id,
|
||||
idList: [id],
|
||||
},
|
||||
);
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
handleSearch();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onPatchBind = () => {
|
||||
visible.value = false;
|
||||
_emit('close');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
handleSearch();
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (instanceStore.current?.metadata) {
|
||||
_metadata.value = instanceStore.current?.metadata;
|
||||
} else {
|
||||
_metadata.value = {};
|
||||
}
|
||||
});
|
||||
const onSave = async () => {
|
||||
form.value = await validate();
|
||||
if (form.value) {
|
||||
formRef.value.validateFields().then(async () => {
|
||||
if (modelRef.dataSource.length === 0) {
|
||||
message.error('请配置物模型');
|
||||
} else {
|
||||
channelList.value.forEach((item: any) => {
|
||||
modelRef.dataSource.forEach((i: any) => {
|
||||
if (item.value === i.channelId) {
|
||||
i.provider = item.provider;
|
||||
}
|
||||
});
|
||||
});
|
||||
const formData = {
|
||||
...form.value,
|
||||
productName: productList.find(
|
||||
(item: any) => item.id === form.value?.productId,
|
||||
).name,
|
||||
parentId: instanceStore.current.id,
|
||||
id: instanceStore.current.parentId
|
||||
? instanceStore.current.parentId
|
||||
: undefined,
|
||||
};
|
||||
const resq = instanceStore.current.parentId
|
||||
? await editDevice(formData)
|
||||
: await addDevice(formData);
|
||||
if (resq.status === 200) {
|
||||
const array = modelRef.dataSource.filter(
|
||||
(item: any) => item.channelId,
|
||||
);
|
||||
const submitData = {
|
||||
deviceId: instanceStore.current.parentId
|
||||
? instanceStore.current.parentId
|
||||
: resq.result?.id,
|
||||
provider: array?.[0]?.provider,
|
||||
requestList: array,
|
||||
};
|
||||
save(submitData);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
const save = async (item: any) => {
|
||||
const res = await saveEdgeMap(instanceStore.current.id, item);
|
||||
if (res.status === 200) {
|
||||
message.success('保存成功');
|
||||
_emit('close');
|
||||
}
|
||||
};
|
||||
const showModal = async () => {
|
||||
form.value = await validate();
|
||||
if (form.value) {
|
||||
const formData = {
|
||||
...form.value,
|
||||
productName: productList.find(
|
||||
(item: any) => item.id === form.value?.productId,
|
||||
).name,
|
||||
parentId: instanceStore.current.id,
|
||||
};
|
||||
deviceData.value = formData;
|
||||
}
|
||||
visible.value = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-form-item) {
|
||||
margin: 0 !important;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,106 @@
|
|||
<template>
|
||||
<div>
|
||||
<TitleComponent data="基本信息">
|
||||
<template #extra>
|
||||
<j-button @click="comeBack">返回</j-button>
|
||||
</template>
|
||||
</TitleComponent>
|
||||
<j-form layout="vertical" :model="form" ref="formRef">
|
||||
<j-row :gutter="24">
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
label="设备名称"
|
||||
name="name"
|
||||
:rules="{ required: true, message: '请输入设备名称' }"
|
||||
>
|
||||
<j-input v-model:value="form.name"></j-input>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
label="产品名称"
|
||||
name="productId"
|
||||
:rules="{ required: true, message: '请选择产品名称' }"
|
||||
>
|
||||
<j-select
|
||||
:disabled="props.childData?.id"
|
||||
@change="selectChange"
|
||||
v-model:value="form.productId"
|
||||
>
|
||||
<j-select-option
|
||||
v-for="i in productList"
|
||||
:key="i.id"
|
||||
:value="i.id"
|
||||
>{{ i.name }}</j-select-option
|
||||
>
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-row :gutter="24" v-if="visible">
|
||||
<j-col :span="24"
|
||||
><EdgeMap :productList="productList" @close="comeBack"
|
||||
/></j-col>
|
||||
</j-row>
|
||||
</j-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getProductListNoPage } from '@/api/device/instance';
|
||||
import EdgeMap from '../EdgeMap/index.vue';
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { provide } from 'vue';
|
||||
const instanceStore = useInstanceStore();
|
||||
const { current } = storeToRefs(instanceStore);
|
||||
const props = defineProps(['childData']);
|
||||
const form = reactive({
|
||||
name: '',
|
||||
productId: '',
|
||||
});
|
||||
const formRef = ref();
|
||||
const emit = defineEmits(['closeChildSave']);
|
||||
const productList = ref();
|
||||
const visible = ref(false);
|
||||
const getProductList = async () => {
|
||||
const res = await getProductListNoPage({
|
||||
terms: [{ column: 'accessProvider', value: 'edge-child-device' }],
|
||||
});
|
||||
if (res.status === 200) {
|
||||
productList.value = res.result;
|
||||
}
|
||||
};
|
||||
getProductList();
|
||||
const selectChange = (e: any) => {
|
||||
if (e) {
|
||||
visible.value = true;
|
||||
}
|
||||
const item = productList.value.filter((i: any) => i.id === e)[0];
|
||||
const array = JSON.parse(item.metadata || [])?.properties?.map(
|
||||
(i: any) => ({
|
||||
metadataType: 'property',
|
||||
metadataName: `${i.name}(${i.id})`,
|
||||
metadataId: i.id,
|
||||
name: i.name,
|
||||
}),
|
||||
);
|
||||
current.value.metadata = array;
|
||||
};
|
||||
watchEffect(() => {
|
||||
if (props.childData?.id) {
|
||||
visible.value = true;
|
||||
}
|
||||
});
|
||||
watchEffect(() => {});
|
||||
|
||||
const validate = async () => {
|
||||
return formRef.value.validateFields();
|
||||
};
|
||||
provide('validate',validate);
|
||||
const comeBack = () =>{
|
||||
emit('closeChildSave');
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -1,91 +1,113 @@
|
|||
<template>
|
||||
<a-card>
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="child-device"
|
||||
@search="handleSearch"
|
||||
class="child-device-search"
|
||||
<SaveChild
|
||||
v-if="childVisible"
|
||||
@close-child-save="closeChildSave"
|
||||
:childData="current"
|
||||
/>
|
||||
<JTable
|
||||
ref="childDeviceRef"
|
||||
:columns="columns"
|
||||
:request="query"
|
||||
:defaultParams="{
|
||||
terms: [
|
||||
{
|
||||
column: 'parentId',
|
||||
value: detail?.id || '',
|
||||
termType: 'eq',
|
||||
},
|
||||
],
|
||||
}"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: _selectedRowKeys,
|
||||
onChange: onSelectChange,
|
||||
}"
|
||||
@cancelSelect="cancelSelect"
|
||||
:params="params"
|
||||
:model="'TABLE'"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-space>
|
||||
<a-button type="primary"> 新增并绑定 </a-button>
|
||||
<a-button type="primary" @click="visible = true">
|
||||
绑定
|
||||
</a-button>
|
||||
<a-popconfirm title="确认解绑吗?" @confirm="handleUnBind">
|
||||
<a-button type="primary"> 批量解绑 </a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #registryTime="slotProps">
|
||||
{{
|
||||
slotProps.registryTime
|
||||
? moment(slotProps.registryTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)
|
||||
: ''
|
||||
}}
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<a-badge
|
||||
:text="slotProps.state.text"
|
||||
:status="statusMap.get(slotProps.state.value)"
|
||||
/>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
v-for="i in getActions(slotProps)"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-popconfirm v-if="i.popConfirm" v-bind="i.popConfirm">
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
v-else
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
<div v-else>
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="child-device"
|
||||
@search="handleSearch"
|
||||
class="child-device-search"
|
||||
/>
|
||||
<JProTable
|
||||
ref="childDeviceRef"
|
||||
:columns="columns"
|
||||
:request="query"
|
||||
:defaultParams="{
|
||||
terms: [
|
||||
{
|
||||
column: 'parentId',
|
||||
value: detail?.id || '',
|
||||
termType: 'eq',
|
||||
},
|
||||
],
|
||||
}"
|
||||
:rowSelection="{
|
||||
selectedRowKeys: _selectedRowKeys,
|
||||
onChange: onSelectChange,
|
||||
}"
|
||||
@cancelSelect="cancelSelect"
|
||||
:params="params"
|
||||
:model="'TABLE'"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<j-space>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
v-if="
|
||||
detail?.accessProvider ===
|
||||
'official-edge-gateway'
|
||||
"
|
||||
hasPermission="device/Instance:update"
|
||||
@click="
|
||||
current = {};
|
||||
childVisible = true;
|
||||
"
|
||||
>新增并绑定</PermissionButton
|
||||
>
|
||||
<a-button
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
@click="visible = true"
|
||||
hasPermission="device/Instance:update"
|
||||
>
|
||||
绑定</PermissionButton
|
||||
>
|
||||
<PermissionButton
|
||||
type="primary"
|
||||
hasPermission="device/Instance:update"
|
||||
:popConfirm="{
|
||||
title: '确定解绑吗?',
|
||||
onConfirm: handleUnBind,
|
||||
}"
|
||||
>批量解除</PermissionButton
|
||||
>
|
||||
</j-space>
|
||||
</template>
|
||||
<template #registryTime="slotProps">
|
||||
{{
|
||||
slotProps.registryTime
|
||||
? moment(slotProps.registryTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)
|
||||
: ''
|
||||
}}
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<a-badge
|
||||
:text="slotProps.state.text"
|
||||
:status="statusMap.get(slotProps.state.value)"
|
||||
/>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<j-space :size="16">
|
||||
<template
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
>
|
||||
<PermissionButton
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
:popConfirm="i.popConfirm"
|
||||
:tooltip="{
|
||||
...i.tooltip,
|
||||
}"
|
||||
@click="i.onClick"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
<BindChildDevice v-if="visible" @change="closeBindDevice" />
|
||||
style="padding: 0px"
|
||||
:hasPermission="'device/Instance:' + i.key"
|
||||
>
|
||||
<template #icon
|
||||
><AIcon :type="i.icon"
|
||||
/></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</j-space>
|
||||
</template>
|
||||
</JProTable>
|
||||
<BindChildDevice v-if="visible" @change="closeBindDevice" />
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
|
@ -97,11 +119,17 @@ import { useInstanceStore } from '@/store/instance';
|
|||
import { storeToRefs } from 'pinia';
|
||||
import { message } from 'ant-design-vue';
|
||||
import BindChildDevice from './BindChildDevice/index.vue';
|
||||
import { usePermissionStore } from '@/store/permission';
|
||||
import SaveChild from './SaveChild/index.vue';
|
||||
|
||||
const instanceStore = useInstanceStore();
|
||||
const { detail } = storeToRefs(instanceStore);
|
||||
const router = useRouter();
|
||||
|
||||
const childVisible = ref(false);
|
||||
const permissionStore = usePermissionStore();
|
||||
// watchEffect(() => {
|
||||
// console.log(detail.value);
|
||||
// });
|
||||
const statusMap = new Map();
|
||||
statusMap.set('online', 'success');
|
||||
statusMap.set('offline', 'error');
|
||||
|
@ -111,6 +139,7 @@ const childDeviceRef = ref<Record<string, any>>({});
|
|||
const params = ref<Record<string, any>>({});
|
||||
const _selectedRowKeys = ref<string[]>([]);
|
||||
const visible = ref<boolean>(false);
|
||||
const current = ref({});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
|
@ -192,7 +221,7 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
|
|||
},
|
||||
},
|
||||
{
|
||||
key: 'unbind',
|
||||
key: 'action',
|
||||
text: '解绑',
|
||||
tooltip: {
|
||||
title: '解绑',
|
||||
|
@ -215,6 +244,18 @@ const getActions = (data: Partial<Record<string, any>>): ActionsType[] => {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'update',
|
||||
text: '编辑',
|
||||
tooltip: {
|
||||
title: '编辑',
|
||||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
current.value = data;
|
||||
childVisible.value = true;
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
|
@ -252,6 +293,10 @@ const closeBindDevice = (val: boolean) => {
|
|||
childDeviceRef.value?.reload();
|
||||
}
|
||||
};
|
||||
|
||||
const closeChildSave = () => {
|
||||
childVisible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
|
|
@ -306,23 +306,6 @@ const columns = [
|
|||
const _selectedRowKeys = ref<string[]>([]);
|
||||
const currentForm = ref({});
|
||||
|
||||
const onSelectChange = (keys: string[]) => {
|
||||
_selectedRowKeys.value = [...keys];
|
||||
};
|
||||
|
||||
const cancelSelect = () => {
|
||||
_selectedRowKeys.value = [];
|
||||
};
|
||||
|
||||
// const handleClick = (dt: any) => {
|
||||
// if (_selectedRowKeys.value.includes(dt.id)) {
|
||||
// const _index = _selectedRowKeys.value.findIndex((i) => i === dt.id);
|
||||
// _selectedRowKeys.value.splice(_index, 1);
|
||||
// } else {
|
||||
// _selectedRowKeys.value = [..._selectedRowKeys.value, dt.id];
|
||||
// }
|
||||
// };
|
||||
|
||||
const getActions = (
|
||||
data: Partial<Record<string, any>>,
|
||||
type: 'card' | 'table',
|
||||
|
|
|
@ -116,6 +116,9 @@
|
|||
@click="i.onClick"
|
||||
type="link"
|
||||
style="padding: 0px"
|
||||
:hasPermission="
|
||||
'rule-engine/Instance:' + i.key
|
||||
"
|
||||
>
|
||||
<template #icon
|
||||
><AIcon :type="i.icon"
|
||||
|
|
Loading…
Reference in New Issue