659 lines
24 KiB
Vue
659 lines
24 KiB
Vue
<template>
|
|
<div>
|
|
<j-steps :current="stepCurrent">
|
|
<j-step v-for="item in steps" :key="item" :title="item" />
|
|
</j-steps>
|
|
<div class="steps-content">
|
|
<div class="steps-box" v-if="current === 0">
|
|
<div class="alert">
|
|
<AIcon type="InfoCircleOutlined" />
|
|
选择与设备通信的网络组件
|
|
</div>
|
|
<div class="search">
|
|
<j-input-search
|
|
allowClear
|
|
placeholder="请输入"
|
|
style="width: 300px"
|
|
@search="networkSearch"
|
|
/>
|
|
<PermissionButton
|
|
type="primary"
|
|
@click="addNetwork"
|
|
hasPermission="link/Type:add"
|
|
>
|
|
<template #icon><AIcon type="PlusOutlined" /></template>
|
|
新增
|
|
</PermissionButton>
|
|
</div>
|
|
<j-scrollbar height="480">
|
|
<j-row :gutter="[24, 24]" v-if="networkList.length > 0">
|
|
<j-col
|
|
:span="8"
|
|
v-for="item in networkList"
|
|
:key="item.id"
|
|
>
|
|
<AccessCard
|
|
@checkedChange="checkedChange"
|
|
:checked="networkCurrent"
|
|
:data="{
|
|
...item,
|
|
description: item.description
|
|
? item.description
|
|
: descriptionList[provider.id],
|
|
type: 'network',
|
|
}"
|
|
>
|
|
<template #other>
|
|
<div class="other">
|
|
<j-tooltip placement="topLeft">
|
|
<div
|
|
v-if="
|
|
(item.addresses || [])
|
|
.length > 1
|
|
"
|
|
>
|
|
<div
|
|
v-for="i in item.addresses ||
|
|
[]"
|
|
:key="i.address"
|
|
class="item"
|
|
>
|
|
<j-badge
|
|
:status="getColor(i)"
|
|
/>{{ i.address }}
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-for="i in (
|
|
item.addresses || []
|
|
).slice(0, 1)"
|
|
:key="i.address"
|
|
class="item"
|
|
>
|
|
<j-badge
|
|
:status="getColor(i)"
|
|
:text="i.address"
|
|
/>
|
|
<span
|
|
v-if="
|
|
(item.addresses || [])
|
|
.length > 1
|
|
"
|
|
>...</span
|
|
>
|
|
</div>
|
|
</j-tooltip>
|
|
</div>
|
|
</template>
|
|
</AccessCard>
|
|
</j-col>
|
|
</j-row>
|
|
<j-empty v-else description="暂无数据" />
|
|
</j-scrollbar>
|
|
</div>
|
|
<div class="steps-box" v-else-if="current === 1">
|
|
<div class="alert">
|
|
<AIcon type="InfoCircleOutlined" />
|
|
使用选择的消息协议,对网络组件通信数据进行编解码、认证等操作
|
|
</div>
|
|
<div class="search">
|
|
<j-input-search
|
|
allowClear
|
|
placeholder="请输入"
|
|
style="width: 300px"
|
|
@search="procotolSearch"
|
|
/>
|
|
<PermissionButton
|
|
type="primary"
|
|
@click="addProcotol"
|
|
hasPermission="link/Protocol:add"
|
|
>
|
|
<template #icon><AIcon type="PlusOutlined" /></template>
|
|
新增
|
|
</PermissionButton>
|
|
</div>
|
|
<j-scrollbar height="480">
|
|
<j-row :gutter="[24, 24]" v-if="procotolList.length > 0">
|
|
<j-col
|
|
:span="8"
|
|
v-for="item in procotolList"
|
|
:key="item?.id"
|
|
>
|
|
<AccessCard
|
|
@checkedChange="procotolChange"
|
|
:checked="procotolCurrent"
|
|
:data="{ ...item, type: 'protocol' }"
|
|
>
|
|
</AccessCard>
|
|
</j-col>
|
|
</j-row>
|
|
<j-empty v-else description="暂无数据" />
|
|
</j-scrollbar>
|
|
</div>
|
|
<div class="steps-box" v-else>
|
|
<div
|
|
class="card-last"
|
|
:style="`max-height:${
|
|
clientHeight > 900 ? 750 : clientHeight * 0.7
|
|
}px`"
|
|
>
|
|
<j-row :gutter="[24, 24]">
|
|
<j-col :span="12">
|
|
<title-component data="基本信息" />
|
|
<j-form
|
|
ref="formRef"
|
|
:model="formData"
|
|
layout="vertical"
|
|
>
|
|
<j-form-item
|
|
label="名称"
|
|
v-bind="validateInfos.name"
|
|
>
|
|
<j-input
|
|
v-model:value="formData.name"
|
|
allowClear
|
|
placeholder="请输入名称"
|
|
/>
|
|
</j-form-item>
|
|
<j-form-item
|
|
label="说明"
|
|
v-bind="validateInfos.description"
|
|
>
|
|
<j-textarea
|
|
placeholder="请输入说明"
|
|
:rows="4"
|
|
v-model:value="formData.description"
|
|
show-count
|
|
:maxlength="200"
|
|
/>
|
|
</j-form-item>
|
|
</j-form>
|
|
</j-col>
|
|
<j-col :span="12">
|
|
<j-scrollbar height="580">
|
|
<div class="doc">
|
|
<h1>接入方式</h1>
|
|
<p>
|
|
{{ provider.name }}
|
|
</p>
|
|
<p>
|
|
{{ provider.description }}
|
|
</p>
|
|
<h1>消息协议</h1>
|
|
<p>
|
|
{{
|
|
procotolList.find(
|
|
(i: any) =>
|
|
i.id === procotolCurrent,
|
|
).name
|
|
}}
|
|
</p>
|
|
<p v-if="config.document">
|
|
<Markdown :source="config.document" />
|
|
</p>
|
|
<div v-if="getNetworkCurrent()">
|
|
<h1>网络组件</h1>
|
|
<p
|
|
v-for="i in getNetworkCurrentData()"
|
|
:key="i.address"
|
|
>
|
|
<j-badge
|
|
:status="getColor(i)"
|
|
:text="i.address"
|
|
/>
|
|
</p>
|
|
</div>
|
|
<div
|
|
v-if="
|
|
config.routes &&
|
|
config.routes.length > 0
|
|
"
|
|
>
|
|
<h1>
|
|
{{
|
|
data.provider ===
|
|
'mqtt-server-gateway' ||
|
|
data.provider ===
|
|
'mqtt-client-gateway'
|
|
? 'topic'
|
|
: 'URL信息'
|
|
}}
|
|
</h1>
|
|
<j-scrollbar height="400">
|
|
<j-table
|
|
:pagination="false"
|
|
:rowKey="generateUUID()"
|
|
:data-source="
|
|
config.routes || []
|
|
"
|
|
bordered
|
|
:columns="
|
|
config.id === 'MQTT'
|
|
? columnsMQTT
|
|
: columnsHTTP
|
|
"
|
|
:scroll="{ y: 400 }"
|
|
>
|
|
<template
|
|
#bodyCell="{
|
|
column,
|
|
text,
|
|
record,
|
|
}"
|
|
>
|
|
<template
|
|
v-if="
|
|
column.dataIndex ===
|
|
'stream'
|
|
"
|
|
>
|
|
{{ getStream(record) }}
|
|
</template>
|
|
</template>
|
|
</j-table>
|
|
</j-scrollbar>
|
|
</div>
|
|
</div>
|
|
</j-scrollbar>
|
|
</j-col>
|
|
</j-row>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="steps-action">
|
|
<j-button
|
|
v-if="[0, 1].includes(current)"
|
|
type="primary"
|
|
style="margin-right: 8px"
|
|
@click="next"
|
|
>
|
|
下一步
|
|
</j-button>
|
|
<PermissionButton
|
|
v-if="current === 2 && view === 'false'"
|
|
type="primary"
|
|
style="margin-right: 8px"
|
|
@click="saveData"
|
|
:hasPermission="`link/AccessConfig:${
|
|
id === ':id' ? 'add' : 'update'
|
|
}`"
|
|
>
|
|
保存
|
|
</PermissionButton>
|
|
<j-button
|
|
v-if="type === 'child-device' ? current > 1 : current > 0"
|
|
@click="prev"
|
|
>
|
|
上一步
|
|
</j-button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup name="AccessNetwork">
|
|
import {
|
|
getNetworkList,
|
|
getProtocolList,
|
|
getConfigView,
|
|
save,
|
|
update,
|
|
getChildConfigView,
|
|
} from '@/api/link/accessConfig';
|
|
import {
|
|
descriptionList,
|
|
NetworkTypeMapping,
|
|
ProtocolMapping,
|
|
ColumnsMQTT,
|
|
ColumnsHTTP,
|
|
} from '../../data';
|
|
import AccessCard from '../AccessCard/index.vue';
|
|
import { Form } from 'ant-design-vue';
|
|
import type { FormInstance, TableColumnType } from 'ant-design-vue';
|
|
import { useMenuStore } from 'store/menu';
|
|
import { onlyMessage } from '@/utils/comm';
|
|
|
|
const menuStory = useMenuStore();
|
|
function generateUUID() {
|
|
var d = new Date().getTime();
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
|
|
/[xy]/g,
|
|
function (c) {
|
|
var r = (d + Math.random() * 16) % 16 | 0;
|
|
d = Math.floor(d / 16);
|
|
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
|
|
},
|
|
);
|
|
}
|
|
|
|
const props = defineProps({
|
|
provider: {
|
|
type: Object,
|
|
default: () => {},
|
|
},
|
|
data: {
|
|
type: Object,
|
|
default: () => {},
|
|
},
|
|
});
|
|
|
|
const clientHeight = document.body.clientHeight;
|
|
const type = props.provider.channel;
|
|
const route = useRoute();
|
|
const view = route.query.view as string;
|
|
const id = route.params.id as string;
|
|
|
|
const formRef = ref<FormInstance>();
|
|
const useForm = Form.useForm;
|
|
|
|
const current = ref(0);
|
|
const stepCurrent = ref(0);
|
|
const steps = ref(['网络组件', '消息协议', '完成']);
|
|
const networkList: any = ref([]);
|
|
const allNetworkList: any = ref([]);
|
|
const procotolList: any = ref([]);
|
|
const allProcotolList = ref([]);
|
|
const networkCurrent: any = ref('');
|
|
const procotolCurrent: any = ref('');
|
|
const config: any = ref({});
|
|
const columnsMQTT = ref(<TableColumnType>[]);
|
|
const columnsHTTP = ref(<TableColumnType>[]);
|
|
const formData = ref({
|
|
name: '',
|
|
description: '',
|
|
});
|
|
|
|
const { resetFields, validate, validateInfos } = useForm(
|
|
formData,
|
|
reactive({
|
|
name: [
|
|
{ required: true, message: '请输入名称', trigger: 'blur' },
|
|
{ max: 64, message: '最多可输入64个字符' },
|
|
],
|
|
description: [{ max: 200, message: '最多可输入200个字符' }],
|
|
}),
|
|
);
|
|
|
|
const queryNetworkList = async (id: string, include: string, data = {}) => {
|
|
const resp = await getNetworkList(
|
|
NetworkTypeMapping.get(id),
|
|
include,
|
|
data,
|
|
);
|
|
if (resp.status === 200) {
|
|
networkList.value = resp.result;
|
|
allNetworkList.value = resp.result;
|
|
}
|
|
};
|
|
|
|
const queryProcotolList = async (id: string, params = {}) => {
|
|
const resp: any = await getProtocolList(ProtocolMapping.get(id), {
|
|
...params,
|
|
'sorts[0].name': 'createTime',
|
|
'sorts[0].order': 'desc',
|
|
});
|
|
if (resp.status === 200) {
|
|
procotolList.value = resp.result;
|
|
allProcotolList.value = resp.result;
|
|
}
|
|
};
|
|
|
|
const addNetwork = () => {
|
|
const url = menuStory.menus['link/Type/Detail']?.path;
|
|
const tab: any = window.open(
|
|
`${window.location.origin + window.location.pathname}#${url}?type=${
|
|
NetworkTypeMapping.get(props.provider?.id) || ''
|
|
}`,
|
|
);
|
|
tab.onTabSaveSuccess = (value: any) => {
|
|
if (value.success) {
|
|
networkCurrent.value = value.result.id;
|
|
queryNetworkList(props.provider?.id, networkCurrent.value || '');
|
|
}
|
|
};
|
|
};
|
|
|
|
const addProcotol = () => {
|
|
const url = menuStory.menus['link/Protocol']?.path;
|
|
const tab: any = window.open(
|
|
`${window.location.origin + window.location.pathname}#${url}?save=true`,
|
|
);
|
|
tab.onTabSaveSuccess = (value: any) => {
|
|
if (value.success) {
|
|
procotolCurrent.value = value.result?.id;
|
|
queryProcotolList(props.provider?.id);
|
|
}
|
|
};
|
|
};
|
|
|
|
const getNetworkCurrent = () =>
|
|
networkList.value.find((i: any) => i.id === networkCurrent) &&
|
|
(
|
|
networkList.value.find((i: any) => i.id === networkCurrent).addresses ||
|
|
[]
|
|
).length > 0;
|
|
const getNetworkCurrentData = () =>
|
|
getNetworkCurrent()
|
|
? networkList.value.find((i: any) => i.id === networkCurrent).addresses
|
|
: [];
|
|
|
|
const getColor = (i: any) => (i.health === -1 ? 'error' : 'processing');
|
|
|
|
const getStream = (record: any) => {
|
|
let stream = '';
|
|
if (record.upstream && record.downstream) stream = '上行、下行';
|
|
else if (record.upstream) stream = '上行';
|
|
else if (record.downstream) stream = '下行';
|
|
return stream;
|
|
};
|
|
|
|
const checkedChange = (id: string) => {
|
|
networkCurrent.value = id;
|
|
};
|
|
|
|
const networkSearch = (value: string) => {
|
|
networkList.value = value
|
|
? allNetworkList.value.filter(
|
|
(i: any) =>
|
|
i.name &&
|
|
i.name
|
|
.toLocaleLowerCase()
|
|
.includes(value.toLocaleLowerCase()),
|
|
)
|
|
: allNetworkList.value;
|
|
};
|
|
const procotolChange = (id: string) => {
|
|
if (!props.data.id) {
|
|
procotolCurrent.value = id;
|
|
}
|
|
};
|
|
|
|
const procotolSearch = (value: string) => {
|
|
procotolList.value = value
|
|
? allProcotolList.value.filter(
|
|
(i: any) =>
|
|
i.name &&
|
|
i.name
|
|
.toLocaleLowerCase()
|
|
.includes(value.toLocaleLowerCase()),
|
|
)
|
|
: allProcotolList.value;
|
|
};
|
|
|
|
const saveData = () => {
|
|
validate()
|
|
.then(async (values) => {
|
|
const params = {
|
|
...props.data,
|
|
...values,
|
|
protocol: procotolCurrent.value,
|
|
channel: 'network', // 网络组件
|
|
channelId: networkCurrent.value,
|
|
provider: props.provider.id,
|
|
transport:
|
|
props.provider?.id === 'child-device'
|
|
? 'Gateway'
|
|
: ProtocolMapping.get(props.provider.id),
|
|
};
|
|
const resp =
|
|
id === ':id'
|
|
? await save(params)
|
|
: await update({ ...params, id });
|
|
if (resp.status === 200) {
|
|
onlyMessage('操作成功', 'success');
|
|
history.back();
|
|
}
|
|
})
|
|
.catch((err) => {});
|
|
};
|
|
|
|
const next = async () => {
|
|
if (current.value === 0) {
|
|
if (!networkCurrent.value) {
|
|
onlyMessage('请选择网络组件!', 'error');
|
|
} else {
|
|
queryProcotolList(props.provider.id);
|
|
current.value = current.value + 1;
|
|
}
|
|
} else if (current.value === 1) {
|
|
if (!procotolCurrent.value) {
|
|
onlyMessage('请选择消息协议!', 'error');
|
|
} else {
|
|
const resp =
|
|
type !== 'child-device'
|
|
? await getConfigView(
|
|
procotolCurrent.value,
|
|
ProtocolMapping.get(props.provider.id),
|
|
)
|
|
: await getChildConfigView(procotolCurrent.value);
|
|
if (resp.status === 200) {
|
|
config.value = resp.result;
|
|
current.value = current.value + 1;
|
|
const Group = {
|
|
title: '分组',
|
|
dataIndex: 'group',
|
|
key: 'group',
|
|
ellipsis: true,
|
|
align: 'center',
|
|
width: 100,
|
|
customCell: (record: any, rowIndex: number) => {
|
|
const obj = {
|
|
children: record,
|
|
rowSpan: 0,
|
|
};
|
|
const list = config.value?.routes || [];
|
|
const arr = list.filter(
|
|
(res: any) => res.group === record.group,
|
|
);
|
|
|
|
const isRowIndex =
|
|
rowIndex === 0 ||
|
|
list[rowIndex - 1].group !== record.group;
|
|
isRowIndex && (obj.rowSpan = arr.length);
|
|
return obj;
|
|
},
|
|
};
|
|
columnsMQTT.value = [Group, ...ColumnsMQTT] as TableColumnType;
|
|
columnsHTTP.value = [Group, ...ColumnsHTTP] as TableColumnType;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
const prev = () => {
|
|
current.value = current.value - 1;
|
|
};
|
|
|
|
onMounted(() => {
|
|
if (props.data && props.data.id) {
|
|
if (props.data.provider !== 'child-device') {
|
|
procotolCurrent.value = props.data.protocol;
|
|
current.value = 0;
|
|
networkCurrent.value = props.data.channelId;
|
|
queryNetworkList(props.provider.id, networkCurrent.value);
|
|
procotolCurrent.value = props.data.protocol;
|
|
steps.value = ['网络组件', '消息协议', '完成'];
|
|
} else {
|
|
steps.value = ['消息协议', '完成'];
|
|
current.value = 1;
|
|
queryProcotolList(props.provider.id);
|
|
}
|
|
} else {
|
|
if (props.provider?.id) {
|
|
if (type !== 'child-device') {
|
|
queryNetworkList(props.provider.id, '');
|
|
steps.value = ['网络组件', '消息协议', '完成'];
|
|
current.value = 0;
|
|
} else {
|
|
steps.value = ['消息协议', '完成'];
|
|
current.value = 1;
|
|
queryProcotolList(props.provider.id);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
onMounted(() => {
|
|
if (id !== ':id') {
|
|
procotolCurrent.value = props.data.protocol;
|
|
formData.value = {
|
|
name: props.data.name,
|
|
description: props.data.description,
|
|
};
|
|
}
|
|
});
|
|
|
|
watch(
|
|
current,
|
|
(v) => {
|
|
stepCurrent.value = type === 'child-device' ? v - 1 : v;
|
|
},
|
|
{
|
|
deep: true,
|
|
immediate: true,
|
|
},
|
|
);
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
.steps-content {
|
|
margin-top: 20px;
|
|
}
|
|
.steps-box {
|
|
min-height: 400px;
|
|
.card-last {
|
|
padding-right: 5px;
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
}
|
|
}
|
|
.steps-action {
|
|
width: 100%;
|
|
margin-top: 24px;
|
|
}
|
|
.alert {
|
|
height: 40px;
|
|
padding-left: 10px;
|
|
color: rgba(0, 0, 0, 0.55);
|
|
line-height: 40px;
|
|
background-color: #f6f6f6;
|
|
}
|
|
.search {
|
|
display: flex;
|
|
margin: 15px 0;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.other {
|
|
width: 100%;
|
|
height: 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
.item {
|
|
width: 100%;
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
text-overflow: ellipsis;
|
|
}
|
|
}
|
|
</style>
|