feat: 设备诊断
This commit is contained in:
parent
5d21fe98c2
commit
6cb673fbe2
|
@ -242,3 +242,70 @@ export const unbindBatchDevice = (deviceId: string, data: Record<string, any>) =
|
|||
* @returns
|
||||
*/
|
||||
export const bindDevice = (deviceId: string, data: Record<string, any>) => server.post(`/device/gateway/${deviceId}/bind`, data)
|
||||
|
||||
/**
|
||||
* 设备接入网关状态
|
||||
* @param id 设备接入网关id
|
||||
* @returns
|
||||
*/
|
||||
export const queryGatewayState = (id: string) => server.get(`/gateway/device/${id}/detail`)
|
||||
|
||||
/**
|
||||
* 网络组件状态
|
||||
* @param id 网络组件id
|
||||
* @returns
|
||||
*/
|
||||
export const queryNetworkState = (id: string) => server.get(`/network/config/${id}`)
|
||||
|
||||
/**
|
||||
* 产品状态
|
||||
* @param id 产品id
|
||||
* @returns
|
||||
*/
|
||||
export const queryProductState = (id: string) => server.get(`/device/product/${id}`)
|
||||
|
||||
/**
|
||||
* 产品配置
|
||||
* @param id 产品id
|
||||
* @returns
|
||||
*/
|
||||
export const queryProductConfig = (id: string) => server.get(`/device/product/${id}/config-metadata`)
|
||||
|
||||
/**
|
||||
* 设备配置
|
||||
* @param id 设备id
|
||||
* @returns
|
||||
*/
|
||||
export const queryDeviceConfig = (id: string) => server.get(`/device-instance/${id}/config-metadata`)
|
||||
|
||||
/**
|
||||
* 查询协议
|
||||
* @param type
|
||||
* @param transport
|
||||
* @returns
|
||||
*/
|
||||
export const queryProtocolDetail = (type: string, transport: string) => server.get(`/protocol/${type}/transport/${transport}`)
|
||||
|
||||
/**
|
||||
* 网络组件启用
|
||||
* @param id 网络组件ID
|
||||
* @returns
|
||||
*/
|
||||
export const startNetwork = (id: string) => server.post(`/network/config/${id}/_start`)
|
||||
|
||||
/**
|
||||
* 启用网关
|
||||
* @param id 网关id
|
||||
* @returns
|
||||
*/
|
||||
export const startGateway = (id: string) => server.post(`/gateway/device/${id}/_startup`)
|
||||
|
||||
/**
|
||||
* 网关详情
|
||||
* @param id 网关id
|
||||
* @returns
|
||||
*/
|
||||
export const getGatewayDetail = (id: string) => server.get(`/gateway/device/${id}`)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { UnorderedListOutlined, AppstoreOutlined } from '@ant-design/icons-vue'
|
||||
import styles from './index.module.less'
|
||||
import { Pagination, Table, Empty, Spin, Alert } from 'ant-design-vue'
|
||||
import type { TableProps, ColumnProps } from 'ant-design-vue/es/table'
|
||||
import type { TableProps } from 'ant-design-vue/es/table'
|
||||
import type { TooltipProps } from 'ant-design-vue/es/tooltip'
|
||||
import type { PopconfirmProps } from 'ant-design-vue/es/popconfirm'
|
||||
import { CSSProperties, PropType } from 'vue';
|
||||
import type { JColumnsProps } from './types'
|
||||
|
||||
enum ModelEnum {
|
||||
TABLE = 'TABLE',
|
||||
|
@ -40,14 +41,10 @@ export interface ActionsType {
|
|||
children?: ActionsType[];
|
||||
}
|
||||
|
||||
export interface JColumnProps extends ColumnProps {
|
||||
scopedSlots?: boolean; // 是否为插槽 true: 是 false: 否
|
||||
}
|
||||
|
||||
export interface JTableProps extends TableProps {
|
||||
request?: (params?: Record<string, any>) => Promise<Partial<RequestData>>;
|
||||
cardBodyClass?: string;
|
||||
columns: JColumnProps[];
|
||||
columns: JColumnsProps[];
|
||||
params?: Record<string, any>;
|
||||
model?: keyof typeof ModelEnum | undefined; // 显示table还是card
|
||||
// actions?: ActionsType[];
|
||||
|
@ -156,9 +153,10 @@ const JTable = defineComponent<JTableProps>({
|
|||
const pageIndex = ref<number>(0)
|
||||
const pageSize = ref<number>(6)
|
||||
const total = ref<number>(0)
|
||||
const _columns = ref<JColumnProps[]>(props?.columns || [])
|
||||
const loading = ref<boolean>(true)
|
||||
|
||||
const _columns = computed(() => props.columns.filter(i => !(i?.hideInTable)))
|
||||
|
||||
/**
|
||||
* 监听宽度,计算显示卡片个数
|
||||
*/
|
||||
|
|
|
@ -3,5 +3,6 @@ import { ColumnType } from 'ant-design-vue/es/table'
|
|||
|
||||
export interface JColumnsProps extends ColumnType{
|
||||
scopedSlots?: boolean;
|
||||
search: SearchProps
|
||||
search: SearchProps;
|
||||
hideInTable?: boolean;
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
<template>
|
||||
<div class="dialog-item" :key="data.key" :class="{'dialog-active' : !data?.upstream}">
|
||||
<div class="dialog-card">
|
||||
<div class="dialog-list" v-for="item in data.list" :key="item.key">
|
||||
<div class="dialog-icon">
|
||||
<AIcon :type="visible.includes(item.key) ? 'DownOutlined' : 'RightOutlined'" />
|
||||
</div>
|
||||
<div class="dialog-box">
|
||||
<div class="dialog-header">
|
||||
<div class="dialog-title">
|
||||
<a-badge :color="statusColor.get(item.error ? 'error' : 'success')" style="margin-right: 5px" />
|
||||
{{operationMap.get(item.operation) || item?.operation}}
|
||||
</div>
|
||||
<div class="dialog-item">{{moment(item.endTime).format('YYYY-MM-DD HH:mm:ss')}}</div>
|
||||
</div>
|
||||
<div class="dialog-editor" v-if="visible.includes(item.key)">
|
||||
<a-textarea :bordered="false" :value="item?.detail" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const operationMap = new Map();
|
||||
import moment from 'moment'
|
||||
operationMap.set('connection', '连接');
|
||||
operationMap.set('auth', '权限验证');
|
||||
operationMap.set('decode', '解码');
|
||||
operationMap.set('encode', '编码');
|
||||
operationMap.set('request', '请求');
|
||||
operationMap.set('response', '响应');
|
||||
operationMap.set('downstream', '下行消息');
|
||||
operationMap.set('upstream', '上行消息');
|
||||
|
||||
const statusColor = new Map();
|
||||
statusColor.set('error', '#E50012');
|
||||
statusColor.set('success', '#24B276');
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
const visible = ref<string[]>([])
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import 'ant-design-vue/es/style/themes/default.less';
|
||||
|
||||
:root {
|
||||
--dialog-primary-color: @primary-color;
|
||||
}
|
||||
|
||||
.dialog-item {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
padding-bottom: 12px;
|
||||
|
||||
.dialog-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 60%;
|
||||
padding: 24px;
|
||||
background-color: #fff;
|
||||
|
||||
.dialog-list {
|
||||
display: flex;
|
||||
|
||||
.dialog-icon {
|
||||
margin-right: 10px;
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.dialog-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
.dialog-header {
|
||||
.dialog-title {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dialog-time {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-editor {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
|
||||
textarea::-webkit-scrollbar {
|
||||
width: 5px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-active {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
.dialog-card {
|
||||
background-color: @primary-color;
|
||||
|
||||
.dialog-list {
|
||||
.dialog-icon {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dialog-box {
|
||||
.dialog-header {
|
||||
.dialog-title,
|
||||
.dialog-time {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-editor {
|
||||
textarea {
|
||||
color: #fff !important;
|
||||
background-color: @primary-color !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,93 @@
|
|||
<template>
|
||||
<a-table
|
||||
rowKey="id"
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
bordered
|
||||
:pagination="false"
|
||||
>
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
<div style="width: 280px">
|
||||
<template v-if="['valueType', 'name'].includes(column.dataIndex)">
|
||||
<span>{{ text }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ValueItem
|
||||
v-model:modelValue="record.value"
|
||||
:itemType="record.type"
|
||||
:options="
|
||||
record.type === 'enum'
|
||||
? (record?.dataType?.elements || []).map(
|
||||
(item) => {
|
||||
return {
|
||||
label: item.text,
|
||||
value: item.value,
|
||||
};
|
||||
},
|
||||
)
|
||||
: record.type === 'boolean'
|
||||
? [
|
||||
{ label: '是', value: true },
|
||||
{ label: '否', value: false },
|
||||
]
|
||||
: undefined
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PropType } from "vue-demi";
|
||||
|
||||
|
||||
type Emits = {
|
||||
(e: 'update:modelValue', data: Record<string, any>[]): void;
|
||||
};
|
||||
const _emit = defineEmits<Emits>();
|
||||
|
||||
const _props = defineProps({
|
||||
modelValue: {
|
||||
type: Array as PropType<Record<string, any>[]>,
|
||||
default: '',
|
||||
}
|
||||
});
|
||||
const columns = [
|
||||
{
|
||||
title: '参数名称',
|
||||
dataIndex: 'name',
|
||||
with: '33%',
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'valueType',
|
||||
with: '33%',
|
||||
},
|
||||
{
|
||||
title: '值',
|
||||
dataIndex: 'value',
|
||||
with: '34%',
|
||||
},
|
||||
];
|
||||
|
||||
// const dataSource = ref<Record<any, any>[]>(_props.modelValue || []);
|
||||
|
||||
const dataSource = computed({
|
||||
get: () => {
|
||||
return _props.modelValue || {
|
||||
messageType: undefined,
|
||||
message: {
|
||||
properties: undefined,
|
||||
functionId: undefined,
|
||||
inputs: []
|
||||
}
|
||||
}
|
||||
},
|
||||
set: (val: any) => {
|
||||
_emit('update:modelValue', val);
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
|
@ -0,0 +1,127 @@
|
|||
<template>
|
||||
<div class="function">
|
||||
<a-form
|
||||
:layout="'vertical'"
|
||||
ref="formRef"
|
||||
:model="modelRef"
|
||||
>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="6">
|
||||
<a-form-item name="messageType" :rules="{
|
||||
required: true,
|
||||
message: '请选择',
|
||||
}">
|
||||
<a-select placeholder="请选择" v-model:value="modelRef.messageType" show-search :filter-option="filterOption">
|
||||
<a-select-option value="READ_PROPERTY">读取属性</a-select-option>
|
||||
<a-select-option value="WRITE_PROPERTY">修改属性</a-select-option>
|
||||
<a-select-option value="INVOKE_FUNCTION">调用功能</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6" v-if="['READ_PROPERTY','WRITE_PROPERTY'].includes(modelRef.messageType)">
|
||||
<a-form-item :name="['message', 'properties']" :rules="{
|
||||
required: true,
|
||||
message: '请选择属性',
|
||||
}">
|
||||
<a-select placeholder="请选择属性" v-model:value="modelRef.message.properties" show-search :filter-option="filterOption">
|
||||
<a-select-option v-for="i in (metadata?.properties) || []" :key="i.id" :value="i.id" :label="i.name">{{i.name}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6" v-if="modelRef.messageType === 'WRITE_PROPERTY'">
|
||||
<a-form-item :name="['message', 'value']" :rules="{
|
||||
required: true,
|
||||
message: '请输入值',
|
||||
}">
|
||||
<a-input />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6" v-if="modelRef.messageType === 'INVOKE_FUNCTION'">
|
||||
<a-form-item :name="['message', 'functionId']" :rules="{
|
||||
required: true,
|
||||
message: '请选择功能',
|
||||
}">
|
||||
<a-select placeholder="请选择功能" v-model:value="modelRef.message.functionId" show-search :filter-option="filterOption" @change="funcChange">
|
||||
<a-select-option v-for="i in (metadata?.functions) || []" :key="i.id" :value="i.id" :label="i.name">{{i.name}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-button type="primary" @click="saveBtn">发送</a-button>
|
||||
</a-col>
|
||||
<a-col :span="24" v-if="modelRef.messageType === 'INVOKE_FUNCTION' && modelRef.message.functionId">
|
||||
<a-form-item :name="['message', 'inputs']" label="参数列表" :rules="{
|
||||
required: true,
|
||||
message: '请输入参数列表',
|
||||
}">
|
||||
<EditTable v-model="modelRef.message.inputs"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import EditTable from './EditTable.vue'
|
||||
|
||||
const instanceStore = useInstanceStore()
|
||||
|
||||
const formRef = ref();
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
type Emits = {
|
||||
(e: 'update:modelValue', data: any): void;
|
||||
};
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const modelRef = reactive({
|
||||
messageType: undefined,
|
||||
message: {
|
||||
properties: undefined,
|
||||
functionId: undefined,
|
||||
inputs: []
|
||||
}
|
||||
})
|
||||
|
||||
const metadata = computed(() => {
|
||||
return JSON.parse(instanceStore.current?.metadata || '{}')
|
||||
})
|
||||
|
||||
const funcChange = (val: string) => {
|
||||
if(val){
|
||||
const arr = metadata.value?.functions.find((item: any) => item.id === val)?.inputs || []
|
||||
const list = arr.map((item: any) => {
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
value: undefined,
|
||||
valueType: item?.valueType?.type,
|
||||
}
|
||||
})
|
||||
modelRef.message.inputs = list
|
||||
}
|
||||
}
|
||||
|
||||
const saveBtn = () => {
|
||||
formRef.value.validate()
|
||||
.then(() => {
|
||||
console.log(toRaw(modelRef))
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ saveBtn })
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.function {
|
||||
padding: 15px;
|
||||
background-color: #e7eaec;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,3 @@
|
|||
<template>
|
||||
log
|
||||
</template>
|
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="16">
|
||||
<a-row :gutter="24" style="margin-bottom: 20px;">
|
||||
<a-col :span="12" v-for="item in messageArr" :key="item">
|
||||
<div :style="messageStyleMap.get(item.status)" class="message-status">
|
||||
<a-badge :status="messageStatusMap.get(item.status)" style="margin-right: 5px;" />
|
||||
<span>{{item.text}}</span>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div>
|
||||
<TitleComponent data="调试" />
|
||||
<div class="content">
|
||||
<div class="dialog" id="dialog">
|
||||
<template v-for="item in dialogList" :key="item.key">
|
||||
<Dialog :data="item" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div><Function /></div>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<div class="right-log">
|
||||
<TitleComponent data="日志" />
|
||||
<div :style="{ marginTop: 10 }">
|
||||
<template v-if="logList.length">
|
||||
<Log v-for="item in logList" :data="item" :key="item.key" />
|
||||
</template>
|
||||
<a-empty v-else />
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { MessageType } from './util'
|
||||
import { messageStatusMap, messageStyleMap } from './util'
|
||||
import Dialog from './Dialog/index.vue'
|
||||
import Function from './Function/index.vue'
|
||||
import Log from './Log/index.vue'
|
||||
|
||||
const message = reactive<MessageType>({
|
||||
up: {
|
||||
text: '上行消息诊断中',
|
||||
status: 'loading',
|
||||
},
|
||||
down: {
|
||||
text: '下行消息诊断中',
|
||||
status: 'loading',
|
||||
},
|
||||
})
|
||||
|
||||
const dialogList = ref<Record<string, any>>([])
|
||||
const logList = ref<Record<string, any>>([])
|
||||
|
||||
const messageArr = computed(() => {
|
||||
const arr = Object.keys(message) || []
|
||||
return arr.map(i => { return {...message[i], key: i}})
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.message-status {
|
||||
padding: 8px 24px;
|
||||
}
|
||||
.content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
width: 100%;
|
||||
min-height: 300px;
|
||||
max-height: 500px;
|
||||
padding: 24px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
background-color: #f2f5f7;
|
||||
}
|
||||
.right-log {
|
||||
padding-left: 20px;
|
||||
border-left: 1px solid rgba(0, 0, 0, .09);
|
||||
overflow: hidden;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
min-height: 400px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,29 @@
|
|||
export type MessageType = {
|
||||
up: {
|
||||
text: string;
|
||||
status: 'loading' | 'success' | 'error';
|
||||
};
|
||||
down: {
|
||||
text: string;
|
||||
status: 'loading' | 'success' | 'error';
|
||||
};
|
||||
}
|
||||
|
||||
export const messageStyleMap = new Map();
|
||||
messageStyleMap.set('loading', {
|
||||
background: 'linear-gradient(0deg, rgba(30, 165, 241, 0.03), rgba(30, 165, 241, 0.03)), #FFFFFF',
|
||||
boxShadow: '-2px 0px 0px #1EA5F1',
|
||||
});
|
||||
messageStyleMap.set('error', {
|
||||
background: 'linear-gradient(0deg, rgba(255, 77, 79, 0.03), rgba(255, 77, 79, 0.03)), #FFFFFF',
|
||||
boxShadow: '-2px 0px 0px #FF4D4F',
|
||||
});
|
||||
messageStyleMap.set('success', {
|
||||
background: 'linear-gradient(0deg, rgba(50, 212, 164, 0.03), rgba(50, 212, 164, 0.03)), #FFFFFF',
|
||||
boxShadow: '-2px 0px 0px #32D4A4',
|
||||
});
|
||||
|
||||
export const messageStatusMap = new Map();
|
||||
messageStatusMap.set('loading', 'processing');
|
||||
messageStatusMap.set('error', 'error');
|
||||
messageStatusMap.set('success', 'success');
|
|
@ -0,0 +1,101 @@
|
|||
import { Badge, Descriptions, Modal, Tooltip } from "ant-design-vue"
|
||||
import TitleComponent from '@/components/TitleComponent/index.vue'
|
||||
import styles from './index.module.less'
|
||||
import AIcon from "@/components/AIcon";
|
||||
import _ from "lodash";
|
||||
|
||||
const DiagnosticAdvice = defineComponent({
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => { }
|
||||
}
|
||||
},
|
||||
emits: ['close'],
|
||||
setup(props, { emit }) {
|
||||
const { data } = props
|
||||
return () => <Modal visible title="设备诊断" width={1000} onOk={() => {
|
||||
emit('close')
|
||||
}}
|
||||
onCancel={() => {
|
||||
emit('close')
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<TitleComponent data="诊断建议" />
|
||||
<div class={styles.advice}>
|
||||
<div class={styles.alert}>
|
||||
<span style={{ marginRight: 10 }}><AIcon type="InfoCircleOutlined" /></span>
|
||||
所有诊断均无异常但设备仍未上线,请检查以下内容
|
||||
</div>
|
||||
<div style={{ marginLeft: 10 }}>
|
||||
{
|
||||
(data?.list || []).map((item: any, index: number) => (
|
||||
<div class={styles.infoItem} key={index} style={{ margin: '10px 0' }}>
|
||||
{item}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginTop: 15 }}>
|
||||
<TitleComponent data="连接信息" />
|
||||
<Descriptions column={2}>
|
||||
<Descriptions.Item span={1} label="设备ID">
|
||||
{data?.info?.id || ''}
|
||||
</Descriptions.Item>
|
||||
{data?.info?.address?.length > 0 && (
|
||||
<Descriptions.Item span={1} label="连接地址">
|
||||
<Tooltip
|
||||
placement="topLeft"
|
||||
title={
|
||||
<div class="serverItem">
|
||||
{(data?.info?.address || []).map((i: any) => (
|
||||
<div key={i.address}>
|
||||
<Badge color={i.health === -1 ? 'red' : 'green'} />
|
||||
{i.address}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div class="serverItem">
|
||||
{(data?.info?.address || []).slice(0, 1).map((i: any) => (
|
||||
<div key={i.address}>
|
||||
<Badge color={i.health === -1 ? 'red' : 'green'} />
|
||||
{i.address}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
|
||||
{(_.flatten(_.map(data?.info?.config, 'properties')) || []).map((item: any, index: number) => (
|
||||
<Descriptions.Item
|
||||
key={index}
|
||||
span={1}
|
||||
label={
|
||||
item?.description ? (
|
||||
<div>
|
||||
<span style={{ marginRight: '10px' }}>{item.name}</span>
|
||||
<Tooltip title={item.description}>
|
||||
<AIcon type="QuestionCircleOutlined" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : (
|
||||
item.name
|
||||
)
|
||||
}
|
||||
>
|
||||
{data?.info?.configValue[item?.property] || ''}
|
||||
</Descriptions.Item>
|
||||
))}
|
||||
</Descriptions>
|
||||
</div>
|
||||
</Modal>
|
||||
}
|
||||
})
|
||||
|
||||
export default DiagnosticAdvice
|
|
@ -0,0 +1,217 @@
|
|||
import AIcon from "@/components/AIcon";
|
||||
import { Button, Descriptions, Modal } from "ant-design-vue"
|
||||
import styles from './index.module.less'
|
||||
|
||||
const ManualInspection = defineComponent({
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => { }
|
||||
}
|
||||
},
|
||||
emits: ['close', 'save'],
|
||||
setup(props, { emit }) {
|
||||
|
||||
const { data } = props
|
||||
|
||||
const dataRender = () => {
|
||||
if (data.type === 'device' || data.type === 'product') {
|
||||
return (
|
||||
<>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div class={styles.alert}>
|
||||
<span style={{ marginRight: 10 }}><AIcon type="InfoCircleOutlined" /></span>
|
||||
请检查配置项是否填写正确,若您确定该项无需诊断可
|
||||
<Button type="link" style="padding: 0"
|
||||
onClick={() => {
|
||||
emit('save', data)
|
||||
}}
|
||||
>
|
||||
忽略
|
||||
</Button>
|
||||
</div>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Descriptions title={data?.data?.name} layout="vertical" bordered>
|
||||
{(data?.data?.properties || []).map((item: any) => (
|
||||
<Descriptions.Item
|
||||
key={item.property}
|
||||
label={`${item.name}${item?.description ? `(${item.description})` : ''}`}
|
||||
>
|
||||
{data?.configuration[item.property] || ''}
|
||||
</Descriptions.Item>
|
||||
))}
|
||||
</Descriptions>
|
||||
</div>
|
||||
</div>
|
||||
{data?.data?.description ? (
|
||||
<div
|
||||
style={{ width: '50%', border: '1px solid #f0f0f0', padding: 10, borderLeft: 'none' }}
|
||||
>
|
||||
<h4>诊断项说明</h4>
|
||||
<p>{data?.data?.description}</p>
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else if (data.type === 'cloud') {
|
||||
return (
|
||||
<>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div class={styles.alert}>
|
||||
<span style={{ marginRight: 10 }}><AIcon type="InfoCircleOutlined" /></span>
|
||||
请检查配置项是否填写正确,若您确定该项无需诊断可
|
||||
<Button type="link" style="padding: 0"
|
||||
onClick={() => {
|
||||
emit('save', data)
|
||||
}}
|
||||
>
|
||||
忽略
|
||||
</Button>
|
||||
</div>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Descriptions title={data?.data?.name} layout="vertical" bordered>
|
||||
{data.configuration?.provider === 'OneNet' ? (
|
||||
<>
|
||||
<Descriptions.Item label={'接口地址'}>
|
||||
{data?.configuration?.configuration?.apiAddress || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'apiKey'}>
|
||||
{data?.configuration?.configuration?.apiKey || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'通知Token'}>
|
||||
{data?.configuration?.configuration?.validateToken || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'aesKey'}>
|
||||
{data?.configuration?.configuration?.aesKey || ''}
|
||||
</Descriptions.Item>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Descriptions.Item label={'接口地址'}>
|
||||
{data?.configuration?.configuration?.apiAddress || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'appKey'}>
|
||||
{data?.configuration?.configuration?.appKey || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'appSecret'}>
|
||||
{data?.configuration?.configuration?.appSecret || ''}
|
||||
</Descriptions.Item>
|
||||
</>
|
||||
)}
|
||||
</Descriptions>
|
||||
</div>
|
||||
</div>
|
||||
{data?.configuration?.configuration?.description ? (
|
||||
<div
|
||||
style={{ width: '50%', border: '1px solid #f0f0f0', padding: 10, borderLeft: 'none' }}
|
||||
>
|
||||
<h4>诊断项说明</h4>
|
||||
<p>{data?.configuration?.configuration?.description}</p>
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else if (data.type === 'media') {
|
||||
return (
|
||||
<>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div class={styles.alert}>
|
||||
<span style={{ marginRight: 10 }}><AIcon type="InfoCircleOutlined" /></span>
|
||||
请检查配置项是否填写正确,若您确定该项无需诊断可
|
||||
<Button type="link" style="padding: 0"
|
||||
onClick={() => {
|
||||
emit('save', data)
|
||||
}}
|
||||
>
|
||||
忽略
|
||||
</Button>
|
||||
</div>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Descriptions title={data?.data?.name} layout="vertical" bordered>
|
||||
{data?.configuration?.configuration?.shareCluster ? (
|
||||
<>
|
||||
<Descriptions.Item label={'SIP 域'}>
|
||||
{data?.configuration?.configuration?.domain || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'SIP ID'}>
|
||||
{data?.configuration?.configuration?.sipId || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'集群'}>
|
||||
{data?.configuration?.configuration?.shareCluster ? '共享配置' : '独立配置'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'SIP 地址'}>
|
||||
{`${data?.configuration?.configuration?.hostPort?.host}:${data?.configuration?.configuration?.hostPort?.port}`}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'公网 Host'}>
|
||||
{`${data?.configuration?.configuration?.hostPort?.publicHost}:${data?.configuration?.configuration?.hostPort?.publicPort}`}
|
||||
</Descriptions.Item>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Descriptions.Item label={'SIP 域'}>
|
||||
{data?.configuration?.configuration?.domain || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'SIP ID'}>
|
||||
{data?.configuration?.configuration?.sipId || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'集群'}>
|
||||
{data?.configuration?.configuration?.shareCluster ? '共享配置' : '独立配置'}
|
||||
</Descriptions.Item>
|
||||
{data?.configuration?.configuration?.cluster.map((i: any, it: number) => (
|
||||
<div key={it}>
|
||||
<div>节点{it + 1}</div>
|
||||
<Descriptions.Item label={'节点名称'}>
|
||||
{i?.clusterNodeId || ''}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'SIP 地址'}>
|
||||
{`${i.host}:${i?.port}`}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={'公网 Host'}>
|
||||
{`${i?.publicHost}:${i?.publicPort}`}
|
||||
</Descriptions.Item>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</Descriptions>
|
||||
</div>
|
||||
</div>
|
||||
{data?.configuration?.configuration.description ? (
|
||||
<div
|
||||
style={{ width: '50%', border: '1px solid #f0f0f0', padding: 10, borderLeft: 'none' }}
|
||||
>
|
||||
<h4>诊断项说明</h4>
|
||||
<p>{data?.configuration?.description}</p>
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return () => <Modal
|
||||
title="人工检查"
|
||||
visible
|
||||
width={1000}
|
||||
cancelText="去修改"
|
||||
okText="确认无误"
|
||||
onOk={() => {
|
||||
emit('save', data)
|
||||
}}
|
||||
onCancel={() => {
|
||||
// TODO 跳转设备和产品
|
||||
}}>
|
||||
<div style={{ display: 'flex' }}>{dataRender()}</div>
|
||||
</Modal>
|
||||
}
|
||||
})
|
||||
|
||||
export default ManualInspection
|
|
@ -0,0 +1,90 @@
|
|||
.statusBox {
|
||||
width: 100%;
|
||||
|
||||
.statusHeader {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.statusContent {
|
||||
width: 100%;
|
||||
margin: 20px 0;
|
||||
border: 1px solid #ececec;
|
||||
border-bottom: none;
|
||||
|
||||
.statusItem {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #ececec;
|
||||
|
||||
.statusLeft {
|
||||
display: flex;
|
||||
|
||||
.statusImg {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin: 15px 20px 0 0;
|
||||
}
|
||||
|
||||
.statusContext {
|
||||
.statusTitle {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.statusDesc {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info {
|
||||
margin-top: 10px;
|
||||
color: #646464;
|
||||
font-size: 14px;
|
||||
|
||||
.infoItem {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.statusRight {
|
||||
margin-top: 10px;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading {
|
||||
animation: loading 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
75% {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.alert {
|
||||
height: 40px;
|
||||
padding-left: 10px;
|
||||
color: rgba(0, 0, 0, 0.55);
|
||||
line-height: 40px;
|
||||
background-color: #f6f6f6;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,262 @@
|
|||
import { getImage } from '@/utils/comm';
|
||||
import { VNode } from 'vue';
|
||||
|
||||
export type ListProps = {
|
||||
key: string;
|
||||
name: string;
|
||||
desc?: string;
|
||||
status: 'loading' | 'error' | 'success' | 'warning';
|
||||
text?: string;
|
||||
info?: VNode | null;
|
||||
};
|
||||
|
||||
export const TextColorMap = new Map();
|
||||
TextColorMap.set('loading', 'black');
|
||||
TextColorMap.set('error', 'red');
|
||||
TextColorMap.set('success', 'green');
|
||||
TextColorMap.set('warning', '#FAB247');
|
||||
|
||||
export const StatusMap = new Map();
|
||||
StatusMap.set('error', getImage('/diagnose/status/error.png'));
|
||||
StatusMap.set('success', getImage('/diagnose/status/success.png'));
|
||||
StatusMap.set('warning', getImage('/diagnose/status/warning.png'));
|
||||
StatusMap.set('loading', getImage('/diagnose/status/loading.png'));
|
||||
|
||||
export const networkInitList: ListProps[] = [
|
||||
// {
|
||||
// key: 'access',
|
||||
// name: '设备接入配置',
|
||||
// desc: '诊断该设备所属产品是否已配置“设备接入”方式,未配置将导致设备连接失败。',
|
||||
// status: 'loading',
|
||||
// text: '正在诊断中...',
|
||||
// info: null,
|
||||
// },
|
||||
{
|
||||
key: 'network',
|
||||
name: '网络组件',
|
||||
desc: '诊断网络组件配置是否正确,配置错误将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'gateway',
|
||||
name: '设备接入网关',
|
||||
desc: '诊断设备接入网关状态是否正常,禁用状态将导致连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'product',
|
||||
name: '产品状态',
|
||||
desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'device',
|
||||
name: '设备状态',
|
||||
desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
];
|
||||
|
||||
export const childInitList: ListProps[] = [
|
||||
// {
|
||||
// key: 'access',
|
||||
// name: '设备接入配置',
|
||||
// desc: '诊断该设备所属产品是否已配置“设备接入”方式,未配置将导致设备连接失败。',
|
||||
// status: 'loading',
|
||||
// text: '正在诊断中...',
|
||||
// info: null,
|
||||
// },
|
||||
// {
|
||||
// key: 'network',
|
||||
// name: '网络组件',
|
||||
// desc: '诊断网络组件配置是否正确,配置错误将导致设备连接失败',
|
||||
// status: 'loading',
|
||||
// text: '正在诊断中...',
|
||||
// info: null,
|
||||
// },
|
||||
{
|
||||
key: 'gateway',
|
||||
name: '设备接入网关',
|
||||
desc: '诊断设备接入网关状态是否正常,网关配置是否正确',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'parent-device',
|
||||
name: '网关父设备',
|
||||
desc: '诊断网关父设备状态是否正常,禁用或离线将导致连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'product',
|
||||
name: '产品状态',
|
||||
desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'device',
|
||||
name: '设备状态',
|
||||
desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
];
|
||||
|
||||
export const cloudInitList: ListProps[] = [
|
||||
// {
|
||||
// key: 'access',
|
||||
// name: '设备接入配置',
|
||||
// desc: '诊断该设备所属产品是否已配置“设备接入”方式,未配置将导致设备连接失败。',
|
||||
// status: 'loading',
|
||||
// text: '正在诊断中...',
|
||||
// info: null,
|
||||
// },
|
||||
{
|
||||
key: 'gateway',
|
||||
name: '设备接入网关',
|
||||
desc: '诊断设备接入网关状态是否正常,网关配置是否正确',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'product',
|
||||
name: '产品状态',
|
||||
desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'device',
|
||||
name: '设备状态',
|
||||
desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
];
|
||||
|
||||
export const channelInitList: ListProps[] = [
|
||||
// {
|
||||
// key: 'access',
|
||||
// name: '设备接入配置',
|
||||
// desc: '诊断该设备所属产品是否已配置“设备接入”方式,未配置将导致设备连接失败。',
|
||||
// status: 'loading',
|
||||
// text: '正在诊断中...',
|
||||
// info: null,
|
||||
// },
|
||||
{
|
||||
key: 'gateway',
|
||||
name: '设备接入网关',
|
||||
desc: '诊断设备接入网关状态是否正常,禁用状态将导致连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'product',
|
||||
name: '产品状态',
|
||||
desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'device',
|
||||
name: '设备状态',
|
||||
desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
];
|
||||
|
||||
export const mediaInitList: ListProps[] = [
|
||||
// {
|
||||
// key: 'access',
|
||||
// name: '设备接入配置',
|
||||
// desc: '诊断该设备所属产品是否已配置“设备接入”方式,未配置将导致设备连接失败。',
|
||||
// status: 'loading',
|
||||
// text: '正在诊断中...',
|
||||
// info: null,
|
||||
// },
|
||||
{
|
||||
key: 'gateway',
|
||||
name: '设备接入网关',
|
||||
desc: '诊断设备接入网关状态是否正常,禁用状态将导致连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'product',
|
||||
name: '产品状态',
|
||||
desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
{
|
||||
key: 'device',
|
||||
name: '设备状态',
|
||||
desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'loading',
|
||||
text: '正在诊断中...',
|
||||
info: null,
|
||||
},
|
||||
];
|
||||
|
||||
export const modifyArrayList = (oldList: ListProps[], item: ListProps, index?: number) => {
|
||||
let newList: ListProps[] = [];
|
||||
if (index !== 0 && !index) {
|
||||
// 添加
|
||||
for (let i = 0; i < oldList.length; i++) {
|
||||
const dt = oldList[i];
|
||||
if (item.key === dt.key) {
|
||||
newList.push(item);
|
||||
} else {
|
||||
newList.push(dt);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 修改
|
||||
oldList.splice(index, 0, item);
|
||||
newList = [...oldList];
|
||||
}
|
||||
return newList;
|
||||
};
|
||||
|
||||
export const isExit = (arr1: any[], arr2: any[]) => {
|
||||
return arr1.find((item) => arr2.includes(item));
|
||||
};
|
||||
|
||||
export const gatewayList = [
|
||||
'websocket-server',
|
||||
'http-server-gateway',
|
||||
'udp-device-gateway',
|
||||
'coap-server-gateway',
|
||||
'mqtt-client-gateway',
|
||||
'tcp-server-gateway',
|
||||
];
|
||||
|
||||
export const urlMap = new Map();
|
||||
urlMap.set('mqtt-client-gateway', 'topic');
|
||||
urlMap.set('http-server-gateway', 'url');
|
||||
urlMap.set('websocket-server', 'url');
|
||||
urlMap.set('coap-server-gateway', 'url');
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
<template>
|
||||
<a-card>
|
||||
<div class="diagnose">
|
||||
<div class="diagnose-header" :style="{background: headerColorMap.get(topState)}">
|
||||
<div class="diagnose-top">
|
||||
<div class="diagnose-img">
|
||||
<div v-if="topState === 'loading'" style="width: 100%; height: 100%; position: relative">
|
||||
<img :src="headerImgMap.get(topState)" style="height: 100%; position: absolute; z-index: 2" />
|
||||
<img :src="getImage('/diagnose/loading-1.png')" style="height: 100%" />
|
||||
</div>
|
||||
<img v-else :src="headerImgMap.get(topState)" style="height: 100%" />
|
||||
</div>
|
||||
<div class="diagnose-text">
|
||||
<div class="diagnose-title">{{headerTitleMap.get(topState)}}</div>
|
||||
<div class="diagnose-desc">
|
||||
<template v-if="topState !== 'loading'">{{headerDescMap.get(topState)}}</template>
|
||||
<template v-else>已诊断{{count}}个</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="diagnose-progress">
|
||||
<a-progress
|
||||
:percent="percent"
|
||||
:showInfo="false"
|
||||
size="small"
|
||||
:strokeColor="progressMap.get(topState)"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
<div class="diagnose-radio">
|
||||
<div class="diagnose-radio-item" :class="item.key === 'message' && topState !== 'success' ? 'disabled' : ''" v-for="item in tabList" :key="item.key" :style="activeKey === item.key ? {...activeStyle} : {}" @click="onTabChange(item.key)">
|
||||
{{item.text}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Message v-if="activeKey === 'message'" />
|
||||
<Status v-else :providerType="providerType" @countChange="countChange" @percentChange="percentChange" @stateChange="stateChange" />
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { headerImgMap, headerColorMap, headerTitleMap, headerDescMap, progressMap } from './util'
|
||||
import { getImage } from '@/utils/comm';
|
||||
import Status from './Status/index'
|
||||
import Message from './Message/index.vue'
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
|
||||
type TypeProps = 'network' | 'child-device' | 'media' | 'cloud' | 'channel'
|
||||
|
||||
const instanceStore = useInstanceStore()
|
||||
|
||||
const tabList = [
|
||||
{ key: 'status', text: '连接状态' },
|
||||
{ key: 'message', text: '消息通信' },
|
||||
];
|
||||
|
||||
const activeStyle = {
|
||||
background: '#FFFFFF',
|
||||
border: '1px solid rgba(0, 0, 0, 0.09)',
|
||||
borderRadius: '2px 2px 0px 0px',
|
||||
color: '#000000BF',
|
||||
};
|
||||
|
||||
const topState = ref<'loading' | 'success' | 'error'>('loading')
|
||||
const count = ref<number>(0)
|
||||
const percent = ref<number>(0)
|
||||
const activeKey = ref<'status' | 'message'>('status')
|
||||
const providerType = ref()
|
||||
|
||||
|
||||
const onTabChange = (key: 'status' | 'message') => {
|
||||
if(topState.value === 'success'){
|
||||
activeKey.value = key
|
||||
}
|
||||
}
|
||||
|
||||
const percentChange = (num: number) => {
|
||||
if(num === 0){
|
||||
percent.value = 0
|
||||
} else if( percent.value < 100 && !num) {
|
||||
percent.value += 20
|
||||
} else {
|
||||
percent.value = num
|
||||
}
|
||||
}
|
||||
|
||||
const stateChange = (_type: 'loading' | 'success' | 'error') => {
|
||||
topState.value = _type
|
||||
}
|
||||
|
||||
const countChange = (num: number) => {
|
||||
count.value = num
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const provider = instanceStore.current?.accessProvider;
|
||||
if (provider === 'fixed-media' || provider === 'gb28181-2016') {
|
||||
providerType.value = 'media'
|
||||
} else if (provider === 'OneNet' || provider === 'Ctwing') {
|
||||
providerType.value = 'cloud'
|
||||
} else if (provider === 'modbus-tcp' || provider === 'opc-ua') {
|
||||
providerType.value = 'channel'
|
||||
} else if (provider === 'child-device') {
|
||||
providerType.value = 'child-device'
|
||||
} else {
|
||||
providerType.value = 'network'
|
||||
}
|
||||
topState.value = 'loading';
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.diagnose {
|
||||
.diagnose-header {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
margin-bottom: 20px;
|
||||
padding: 15px 25px;
|
||||
|
||||
.diagnose-top {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
.diagnose-img {
|
||||
width: 65px;
|
||||
height: 65px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.diagnose-text {
|
||||
.diagnose-title {
|
||||
color: #000c;
|
||||
font-weight: 700;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.diagnose-desc {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.diagnose-progress {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.diagnose-radio {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
|
||||
.diagnose-radio-item {
|
||||
width: 150px;
|
||||
height: 35px;
|
||||
margin-right: 8px;
|
||||
color: #00000073;
|
||||
line-height: 35px;
|
||||
text-align: center;
|
||||
background: #f2f2f2;
|
||||
border-radius: 2px 2px 0 0;
|
||||
cursor: pointer;
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.diagnose-loading {
|
||||
animation: diagnose-loading 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes diagnose-loading {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,30 @@
|
|||
import { getImage } from '@/utils/comm';
|
||||
|
||||
export const headerImgMap = new Map();
|
||||
headerImgMap.set('loading', getImage('/diagnose/loading-2.png'));
|
||||
headerImgMap.set('error', getImage('/diagnose/error.png'));
|
||||
headerImgMap.set('success', getImage('/diagnose/success.png'));
|
||||
|
||||
export const headerColorMap = new Map();
|
||||
headerColorMap.set('loading', 'linear-gradient(89.95deg, #E6F5FF 0.03%, #E9EAFF 99.95%)');
|
||||
headerColorMap.set(
|
||||
'error',
|
||||
'linear-gradient(89.95deg, rgba(231, 173, 86, 0.1) 0.03%, rgba(247, 111, 93, 0.1) 99.95%)',
|
||||
);
|
||||
headerColorMap.set('success', 'linear-gradient(89.95deg, #E8F8F7 0.03%, #EBEFFA 99.95%)');
|
||||
|
||||
|
||||
export const headerTitleMap = new Map();
|
||||
headerTitleMap.set('loading', '正在诊断中');
|
||||
headerTitleMap.set('error', '发现连接问题');
|
||||
headerTitleMap.set('success', '连接状态正常');
|
||||
|
||||
export const headerDescMap = new Map();
|
||||
headerDescMap.set('loading', '已诊断XX个');
|
||||
headerDescMap.set('error', '请处理连接异常');
|
||||
headerDescMap.set('success', '现在可调试消息通信');
|
||||
|
||||
export const progressMap = new Map();
|
||||
progressMap.set('loading', '#597EF7');
|
||||
progressMap.set('error', '#FAB247');
|
||||
progressMap.set('success', '#32D4A4');
|
|
@ -44,6 +44,7 @@ import Info from './Info/index.vue';
|
|||
import Running from './Running/index.vue'
|
||||
import Metadata from '../../components/Metadata/index.vue';
|
||||
import ChildDevice from './ChildDevice/index.vue';
|
||||
import Diagnose from './Diagnose/index.vue'
|
||||
import { _deploy, _disconnect } from '@/api/device/instance'
|
||||
import { message } from 'ant-design-vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
|
@ -52,7 +53,7 @@ const route = useRoute();
|
|||
const instanceStore = useInstanceStore()
|
||||
|
||||
const statusMap = new Map();
|
||||
statusMap.set('online', 'processing');
|
||||
statusMap.set('online', 'success');
|
||||
statusMap.set('offline', 'error');
|
||||
statusMap.set('notActive', 'warning');
|
||||
|
||||
|
@ -72,7 +73,11 @@ const list = [
|
|||
{
|
||||
key: 'ChildDevice',
|
||||
tab: '子设备'
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'Diagnose',
|
||||
tab: '设备诊断'
|
||||
},
|
||||
]
|
||||
|
||||
const tabs = {
|
||||
|
@ -80,6 +85,7 @@ const tabs = {
|
|||
Metadata,
|
||||
Running,
|
||||
ChildDevice,
|
||||
Diagnose
|
||||
}
|
||||
|
||||
watch(
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<Search :columns="columns" target="device-instance" />
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="device-instance"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JTable
|
||||
ref="instanceRef"
|
||||
:columns="columns"
|
||||
|
@ -267,6 +271,13 @@ import Export from './Export/index.vue';
|
|||
import Process from './Process/index.vue';
|
||||
import Save from './Save/index.vue';
|
||||
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
|
||||
import {
|
||||
getProviders,
|
||||
queryGatewayList,
|
||||
queryNoPagingPost,
|
||||
queryOrgThree,
|
||||
} from '@/api/device/product';
|
||||
import { queryTree } from '@/api/device/category';
|
||||
|
||||
const router = useRouter();
|
||||
const instanceRef = ref<Record<string, any>>({});
|
||||
|
@ -290,33 +301,172 @@ const columns = [
|
|||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '设备名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '产品名称',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
queryNoPagingPost({ paging: false }).then((resp: any) => {
|
||||
resolve(
|
||||
resp.result.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '禁用', value: 'notActive' },
|
||||
{ label: '离线', value: 'offline' },
|
||||
{ label: '在线', value: 'online' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'classifiedId',
|
||||
dataIndex: 'classifiedId',
|
||||
title: '产品分类',
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'treeSelect',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
queryTree({ paging: false }).then((resp: any) => {
|
||||
resolve(resp.result);
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'accessProvider',
|
||||
title: '网关类型',
|
||||
dataIndex: 'accessProvider',
|
||||
valueType: 'select',
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
getProviders().then((resp: any) => {
|
||||
resolve(
|
||||
resp.result.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: `accessProvider is ${item.id}`,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'productId$product-info',
|
||||
dataIndex: 'productId$product-info',
|
||||
title: '接入方式',
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
queryGatewayList({}).then((resp: any) => {
|
||||
resolve(
|
||||
resp.result.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: `accessId is ${item.id}`,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'deviceType',
|
||||
title: '设备类型',
|
||||
valueType: 'select',
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '直连设备', value: 'device' },
|
||||
{ label: '网关子设备', value: 'childrenDevice' },
|
||||
{ label: '网关设备', value: 'gateway' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'id$dim-assets',
|
||||
title: '所属组织',
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'treeSelect',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
queryOrgThree({}).then((resp: any) => {
|
||||
const formatValue = (list: any[]) => {
|
||||
const _list: any[] = [];
|
||||
list.forEach((item) => {
|
||||
if (item.children) {
|
||||
item.children = formatValue(item.children);
|
||||
}
|
||||
_list.push({
|
||||
...item,
|
||||
id: JSON.stringify({
|
||||
assetType: 'device',
|
||||
targets: [
|
||||
{
|
||||
type: 'org',
|
||||
id: item.id,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
});
|
||||
return _list;
|
||||
};
|
||||
resolve(formatValue(resp.result));
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'describe',
|
||||
key: 'describe',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
|
@ -543,4 +693,9 @@ const saveBtn = () => {
|
|||
visible.value = false;
|
||||
instanceRef.value?.reload();
|
||||
};
|
||||
|
||||
const handleSearch = (_params: any) => {
|
||||
console.log(_params);
|
||||
params.value = _params;
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -11,143 +11,312 @@
|
|||
>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="24">
|
||||
<a-form-item label="名称" name="name" :rules=" [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]">
|
||||
<a-input placeholder="请输入名称" v-model:value="modelRef.name" />
|
||||
<a-form-item
|
||||
label="名称"
|
||||
name="name"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
placeholder="请输入名称"
|
||||
v-model:value="modelRef.name"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item :name="['accessConfig', 'regionId']" :rules="[{
|
||||
required: true,
|
||||
message: '请选择服务地址',
|
||||
}]">
|
||||
<a-form-item
|
||||
:name="['accessConfig', 'regionId']"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择服务地址',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
服务地址
|
||||
<a-tooltip title="阿里云内部给每台机器设置的唯一编号">
|
||||
<a-tooltip
|
||||
title="阿里云内部给每台机器设置的唯一编号"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px;" />
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-select placeholder="请选择服务地址" v-model:value="modelRef.accessConfig.regionId" show-search :filter-option="filterOption" @blur="productChange">
|
||||
<a-select-option v-for="item in regionsList" :key="item.id" :value="item.id" :label="item.name">{{item.name}}</a-select-option>
|
||||
<a-select
|
||||
placeholder="请选择服务地址"
|
||||
v-model:value="
|
||||
modelRef.accessConfig.regionId
|
||||
"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
@blur="productChange"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in regionsList"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
:label="item.name"
|
||||
>{{ item.name }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item :name="['accessConfig', 'instanceId']">
|
||||
<a-form-item
|
||||
:name="['accessConfig', 'instanceId']"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
实例ID
|
||||
<a-tooltip title="阿里云物联网平台中的实例ID,没有则不填">
|
||||
<a-tooltip
|
||||
title="阿里云物联网平台中的实例ID,没有则不填"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px;" />
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-input placeholder="请输入实例ID" v-model:value="modelRef.accessConfig.instanceId" @blur="productChange" />
|
||||
<a-input
|
||||
placeholder="请输入实例ID"
|
||||
v-model:value="
|
||||
modelRef.accessConfig.instanceId
|
||||
"
|
||||
@blur="productChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item :name="['accessConfig', 'accessKeyId']" :rules="[{
|
||||
required: true,
|
||||
message: '请输入accessKey',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]">
|
||||
<a-form-item
|
||||
:name="['accessConfig', 'accessKeyId']"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入accessKey',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
accessKey
|
||||
<a-tooltip title="用于程序通知方式调用云服务API的用户标识">
|
||||
<a-tooltip
|
||||
title="用于程序通知方式调用云服务API的用户标识"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px;" />
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-input placeholder="请输入accessKey" v-model:value="modelRef.accessConfig.accessKeyId" @blur="productChange" />
|
||||
<a-input
|
||||
placeholder="请输入accessKey"
|
||||
v-model:value="
|
||||
modelRef.accessConfig.accessKeyId
|
||||
"
|
||||
@blur="productChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item :name="['accessConfig', 'accessSecret']" :rules="[{
|
||||
required: true,
|
||||
message: '请输入accessSecret',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]">
|
||||
<a-form-item
|
||||
:name="['accessConfig', 'accessSecret']"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入accessSecret',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
accessSecret
|
||||
<a-tooltip title="用于程序通知方式调用云服务费API的秘钥标识">
|
||||
<a-tooltip
|
||||
title="用于程序通知方式调用云服务费API的秘钥标识"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px;" />
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-input placeholder="请输入accessSecret" v-model:value="modelRef.accessConfig.accessSecret" @blur="productChange" />
|
||||
<a-input
|
||||
placeholder="请输入accessSecret"
|
||||
v-model:value="
|
||||
modelRef.accessConfig.accessSecret
|
||||
"
|
||||
@blur="productChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item name="bridgeProductKey" :rules="{
|
||||
<a-form-item
|
||||
name="bridgeProductKey"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '请选择网桥产品',
|
||||
}">
|
||||
}"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
网桥产品
|
||||
<a-tooltip title="物联网平台对应的阿里云产品">
|
||||
<a-tooltip
|
||||
title="物联网平台对应的阿里云产品"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px;" />
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-select placeholder="请选择网桥产品" v-model:value="modelRef.bridgeProductKey" show-search :filter-option="filterOption">
|
||||
<a-select-option v-for="item in aliyunProductList" :key="item.productKey" :value="item.productKey" :label="item.productName">{{item.productName}}</a-select-option>
|
||||
<a-select
|
||||
placeholder="请选择网桥产品"
|
||||
v-model:value="
|
||||
modelRef.bridgeProductKey
|
||||
"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in aliyunProductList"
|
||||
:key="item.productKey"
|
||||
:value="item.productKey"
|
||||
:label="item.productName"
|
||||
>{{
|
||||
item.productName
|
||||
}}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<p>产品映射</p>
|
||||
<a-collapse v-if="modelRef.mappings.length" :activeKey="modelRef.mappings.map((_, _index) => _index)">
|
||||
<a-collapse-panel v-for="(item, index) in modelRef.mappings" :key="index" :header="item.productKey ? aliyunProductList.find(i => i.productKey === item.productKey)?.productName : `产品映射${index + 1}`">
|
||||
<template #extra><AIcon type="DeleteOutlined" @click="delItem(index)" /></template>
|
||||
<a-collapse
|
||||
v-if="modelRef.mappings.length"
|
||||
:activeKey="activeKey"
|
||||
@change="onCollChange"
|
||||
>
|
||||
<a-collapse-panel
|
||||
v-for="(
|
||||
item, index
|
||||
) in modelRef.mappings"
|
||||
:key="index"
|
||||
:header="
|
||||
item.productKey
|
||||
? aliyunProductList.find(
|
||||
(i) =>
|
||||
i.productKey ===
|
||||
item.productKey,
|
||||
)?.productName
|
||||
: `产品映射${index + 1}`
|
||||
"
|
||||
>
|
||||
<template #extra
|
||||
><AIcon
|
||||
type="DeleteOutlined"
|
||||
@click="delItem(index)"
|
||||
/></template>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="阿里云产品" :name="['mappings', index, 'productKey']" :rules="{
|
||||
required: true,
|
||||
message: '请选择阿里云产品',
|
||||
}">
|
||||
<a-select placeholder="请选择阿里云产品" v-model:value="item.productKey" show-search :filter-option="filterOption">
|
||||
<a-select-option v-for="i in getAliyunProductList(item.productKey)" :key="i.productKey" :value="i.productKey" :label="i.productName">{{i.productName}}</a-select-option>
|
||||
<a-form-item
|
||||
label="阿里云产品"
|
||||
:name="[
|
||||
'mappings',
|
||||
index,
|
||||
'productKey',
|
||||
]"
|
||||
:rules="{
|
||||
required: true,
|
||||
message:
|
||||
'请选择阿里云产品',
|
||||
}"
|
||||
>
|
||||
<a-select
|
||||
placeholder="请选择阿里云产品"
|
||||
v-model:value="
|
||||
item.productKey
|
||||
"
|
||||
show-search
|
||||
:filter-option="
|
||||
filterOption
|
||||
"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="i in getAliyunProductList(
|
||||
item.productKey,
|
||||
)"
|
||||
:key="i.productKey"
|
||||
:value="
|
||||
i.productKey
|
||||
"
|
||||
:label="
|
||||
i.productName
|
||||
"
|
||||
>{{
|
||||
i.productName
|
||||
}}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="平台产品" :name="['mappings', index, 'productId']" :rules="{
|
||||
required: true,
|
||||
message: '请选择平台产品',
|
||||
}">
|
||||
<a-select placeholder="请选择平台产品" v-model:value="item.productId" show-search :filter-option="filterOption">
|
||||
<a-select-option v-for="i in getPlatProduct(item.productId)" :key="i.id" :value="item.id" :label="i.name">{{i.name}}</a-select-option>
|
||||
<a-form-item
|
||||
label="平台产品"
|
||||
:name="[
|
||||
'mappings',
|
||||
index,
|
||||
'productId',
|
||||
]"
|
||||
:rules="{
|
||||
required: true,
|
||||
message:
|
||||
'请选择平台产品',
|
||||
}"
|
||||
>
|
||||
<a-select
|
||||
placeholder="请选择平台产品"
|
||||
v-model:value="
|
||||
item.productId
|
||||
"
|
||||
show-search
|
||||
:filter-option="
|
||||
filterOption
|
||||
"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="i in getPlatProduct(
|
||||
item.productId,
|
||||
)"
|
||||
:key="i.id"
|
||||
:value="item.id"
|
||||
:label="i.name"
|
||||
>{{
|
||||
i.name
|
||||
}}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
@ -156,17 +325,26 @@
|
|||
</a-collapse>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-button type="dashed" style="width: 100%; margin-top: 10px" @click="addItem">
|
||||
<a-button
|
||||
type="dashed"
|
||||
style="width: 100%; margin-top: 10px"
|
||||
@click="addItem"
|
||||
>
|
||||
<AIcon
|
||||
type="PlusOutlined"
|
||||
style="margin-left: 2px;" />添加
|
||||
style="margin-left: 2px"
|
||||
/>添加
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="24" style="margin-top: 20px">
|
||||
<a-form-item label="说明" name="description" :rules="{
|
||||
max: 200,
|
||||
message: '最多输入200个字符',
|
||||
}">
|
||||
<a-form-item
|
||||
label="说明"
|
||||
name="description"
|
||||
:rules="{
|
||||
max: 200,
|
||||
message: '最多输入200个字符',
|
||||
}"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="modelRef.description"
|
||||
placeholder="请输入说明"
|
||||
|
@ -178,7 +356,12 @@
|
|||
</a-row>
|
||||
</a-form>
|
||||
<div v-if="type === 'edit'">
|
||||
<a-button :loading="loading" type="primary" @click="saveBtn">保存</a-button>
|
||||
<a-button
|
||||
:loading="loading"
|
||||
type="primary"
|
||||
@click="saveBtn"
|
||||
>保存</a-button
|
||||
>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
|
@ -190,8 +373,14 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Doc from './doc.vue'
|
||||
import {savePatch, detail, getRegionsList, getAliyunProductsList, queryProductList } from '@/api/northbound/alicloud'
|
||||
import Doc from './doc.vue';
|
||||
import {
|
||||
savePatch,
|
||||
detail,
|
||||
getRegionsList,
|
||||
getAliyunProductsList,
|
||||
queryProductList,
|
||||
} from '@/api/northbound/alicloud';
|
||||
import _ from 'lodash';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
|
@ -207,127 +396,153 @@ const modelRef = reactive({
|
|||
regionId: undefined,
|
||||
instanceId: undefined,
|
||||
accessKeyId: undefined,
|
||||
accessSecret: undefined
|
||||
accessSecret: undefined,
|
||||
},
|
||||
bridgeProductKey: undefined,
|
||||
bridgeProductName: undefined,
|
||||
mappings: [{
|
||||
productKey: undefined,
|
||||
productId: undefined,
|
||||
}],
|
||||
description: undefined
|
||||
mappings: [
|
||||
{
|
||||
productKey: undefined,
|
||||
productId: undefined,
|
||||
},
|
||||
],
|
||||
description: undefined,
|
||||
});
|
||||
|
||||
const addItem = () => {
|
||||
activeKey.value.push(String(modelRef.mappings.length));
|
||||
modelRef.mappings.push({
|
||||
productKey: undefined,
|
||||
productId: undefined,
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const delItem = (index: number) => {
|
||||
modelRef.mappings.splice(index, 1)
|
||||
}
|
||||
modelRef.mappings.splice(index, 1);
|
||||
};
|
||||
|
||||
const productList = ref<Record<string, any>[]>([])
|
||||
const regionsList = ref<Record<string, any>[]>([])
|
||||
const aliyunProductList = ref<Record<string, any>[]>([])
|
||||
const loading = ref<boolean>(false)
|
||||
const type = ref<'edit' | 'view'>('view')
|
||||
const productList = ref<Record<string, any>[]>([]);
|
||||
const regionsList = ref<Record<string, any>[]>([]);
|
||||
const aliyunProductList = ref<Record<string, any>[]>([]);
|
||||
const loading = ref<boolean>(false);
|
||||
const type = ref<'edit' | 'view'>('edit');
|
||||
const activeKey = ref<string[]>(['0']);
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
const queryRegionsList = async () => {
|
||||
const resp = await getRegionsList()
|
||||
if(resp.status === 200){
|
||||
regionsList.value = resp.result as Record<string, any>[]
|
||||
const resp = await getRegionsList();
|
||||
if (resp.status === 200) {
|
||||
regionsList.value = resp.result as Record<string, any>[];
|
||||
}
|
||||
}
|
||||
};
|
||||
const getProduct = async () => {
|
||||
const resp = await queryProductList({
|
||||
paging: false,
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
})
|
||||
if(resp.status === 200){
|
||||
productList.value = (resp?.result as Record<string, any>[])
|
||||
paging: false,
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
productList.value = resp?.result as Record<string, any>[];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getAliyunProduct = async (data: any) => {
|
||||
if(data.regionId && data.accessKeyId && data.accessSecret){
|
||||
const resp: any = await getAliyunProductsList(data)
|
||||
if(resp.status === 200){
|
||||
aliyunProductList.value = (resp?.result?.data as Record<string, any>[])
|
||||
if (data.regionId && data.accessKeyId && data.accessSecret) {
|
||||
const resp: any = await getAliyunProductsList(data);
|
||||
if (resp.status === 200) {
|
||||
aliyunProductList.value = resp?.result?.data as Record<
|
||||
string,
|
||||
any
|
||||
>[];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const productChange = () => {
|
||||
const data = modelRef.accessConfig
|
||||
getAliyunProduct(data)
|
||||
}
|
||||
const data = modelRef.accessConfig;
|
||||
getAliyunProduct(data);
|
||||
};
|
||||
|
||||
const getPlatProduct = (val: string) => {
|
||||
const arr = modelRef.mappings.map(item => item?.productId) || []
|
||||
const checked = _.cloneDeep(arr)
|
||||
const _index = checked.findIndex(i => i === val)
|
||||
checked.splice(_index, 1)
|
||||
const list = productList.value.filter((i: any) => !checked.includes(i?.id as any))
|
||||
return list || []
|
||||
}
|
||||
const arr = modelRef.mappings.map((item) => item?.productId) || [];
|
||||
const checked = _.cloneDeep(arr);
|
||||
const _index = checked.findIndex((i) => i === val);
|
||||
checked.splice(_index, 1);
|
||||
const list = productList.value.filter(
|
||||
(i: any) => !checked.includes(i?.id as any),
|
||||
);
|
||||
return list || [];
|
||||
};
|
||||
|
||||
const getAliyunProductList = (val: string) => {
|
||||
const items = modelRef.mappings.map((item) => item?.productKey) || []
|
||||
const checked = _.cloneDeep(items)
|
||||
const _index = checked.findIndex(i => i === val)
|
||||
checked.splice(_index, 1)
|
||||
const list = aliyunProductList.value?.filter((i: any) => !checked.includes(i?.productKey as any))
|
||||
return list || []
|
||||
}
|
||||
|
||||
const saveBtn = async () => {
|
||||
const data = await formRef.value.validate()
|
||||
const product = (aliyunProductList.value || []).find(
|
||||
(item: any) => item?.bridgeProductKey === data?.bridgeProductKey,
|
||||
const items = modelRef.mappings.map((item) => item?.productKey) || [];
|
||||
const checked = _.cloneDeep(items);
|
||||
const _index = checked.findIndex((i) => i === val);
|
||||
checked.splice(_index, 1);
|
||||
const list = aliyunProductList.value?.filter(
|
||||
(i: any) => !checked.includes(i?.productKey as any),
|
||||
);
|
||||
data.bridgeProductName = product?.productName || '';
|
||||
loading.value = true;
|
||||
const resp = await savePatch(toRaw(modelRef));
|
||||
loading.value = false;
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
formRef.value.resetFields();
|
||||
router.push('/iot/northbound/AliCloud/');
|
||||
}
|
||||
}
|
||||
return list || [];
|
||||
};
|
||||
|
||||
const onCollChange = (_key: string[]) => {
|
||||
activeKey.value = _key;
|
||||
};
|
||||
|
||||
const saveBtn = () => {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async (data: any) => {
|
||||
const product = (aliyunProductList.value || []).find(
|
||||
(item: any) =>
|
||||
item?.bridgeProductKey === data?.bridgeProductKey,
|
||||
);
|
||||
data.bridgeProductName = product?.productName || '';
|
||||
loading.value = true;
|
||||
const resp = await savePatch(toRaw(modelRef));
|
||||
loading.value = false;
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
formRef.value.resetFields();
|
||||
router.push('/iot/northbound/AliCloud');
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
const _arr = err.errorFields.map((i: any) => i.name);
|
||||
_arr.map((item: string | any[]) => {
|
||||
if (item.length === 3 && !activeKey.value.includes(item[1])) {
|
||||
activeKey.value.push(item[1]);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
watch(
|
||||
() => route.params?.id,
|
||||
async (newId) => {
|
||||
if(newId){
|
||||
queryRegionsList()
|
||||
getProduct()
|
||||
if (newId) {
|
||||
queryRegionsList();
|
||||
getProduct();
|
||||
if (newId === ':id' || !newId) return;
|
||||
const resp = await detail(newId as string)
|
||||
const resp = await detail(newId as string);
|
||||
const _data: any = resp.result;
|
||||
if (_data) {
|
||||
getAliyunProduct(_data?.accessConfig)
|
||||
getAliyunProduct(_data?.accessConfig);
|
||||
}
|
||||
Object.assign(modelRef, _data)
|
||||
Object.assign(modelRef, _data);
|
||||
}
|
||||
},
|
||||
{immediate: true, deep: true}
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
|
||||
watch(
|
||||
() => route.query.type,
|
||||
(newVal) => {
|
||||
if(newVal){
|
||||
type.value = newVal as 'edit' | 'view'
|
||||
if (newVal) {
|
||||
type.value = newVal as 'edit' | 'view';
|
||||
}
|
||||
},
|
||||
{immediate: true, deep: true}
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
</script>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<Search :columns="columns" target="northbound-dueros" :params="params" />
|
||||
<Search :columns="columns" target="northbound-dueros" @search="handleSearch" />
|
||||
<JTable
|
||||
ref="instanceRef"
|
||||
:columns="columns"
|
||||
|
@ -166,11 +166,17 @@ const columns = [
|
|||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '网桥产品',
|
||||
dataIndex: 'bridgeProductName',
|
||||
key: 'bridgeProductName',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
|
@ -182,6 +188,13 @@ const columns = [
|
|||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '正常', value: 'enabled' },
|
||||
{ label: '禁用', value: 'disabled' }
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
|
@ -303,4 +316,8 @@ const getActions = (
|
|||
return actions.filter((i: ActionsType) => i.key !== 'view');
|
||||
return actions;
|
||||
};
|
||||
|
||||
const handleSearch = (_params: any) => {
|
||||
params.value = _params
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -127,10 +127,15 @@ const funcChange = (val: string) => {
|
|||
const saveBtn = () => new Promise((resolve) => {
|
||||
formRef.value.validate()
|
||||
.then(() => {
|
||||
resolve(toRaw(modelRef))
|
||||
const _arr = toRaw(modelRef).value?.message?.inputs || []
|
||||
if(_arr.length && !_arr.every((_a: any) => _a.value)){
|
||||
resolve(false)
|
||||
} else {
|
||||
resolve(toRaw(modelRef))
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
resolve(false)
|
||||
resolve(err)
|
||||
});
|
||||
})
|
||||
|
||||
|
|
|
@ -11,98 +11,247 @@
|
|||
>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="24">
|
||||
<a-form-item label="名称" name="name" :rules=" [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]">
|
||||
<a-input placeholder="请输入名称" v-model:value="modelRef.name" />
|
||||
<a-form-item
|
||||
label="名称"
|
||||
name="name"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入名称',
|
||||
},
|
||||
{
|
||||
max: 64,
|
||||
message: '最多输入64个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
placeholder="请输入名称"
|
||||
v-model:value="modelRef.name"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="产品" name="id" :rules="[{
|
||||
required: true,
|
||||
message: '请选择产品',
|
||||
}]">
|
||||
<a-select :disabled="modelRef.id !== ':id'" placeholder="请选择产品" v-model:value="modelRef.id" show-search :filter-option="filterOption" @change="productChange">
|
||||
<a-select-option v-for="item in productList" :key="item.id" :value="item.id" :label="item.name">{{item.name}}</a-select-option>
|
||||
<a-form-item
|
||||
label="产品"
|
||||
name="id"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择产品',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-select
|
||||
:disabled="type !== 'edit' && modelRef.id && modelRef.id !== ':id'"
|
||||
placeholder="请选择产品"
|
||||
v-model:value="modelRef.id"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
@change="productChange"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in productList"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
:label="item.name"
|
||||
>{{ item.name }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item name="applianceType" :rules="{
|
||||
<a-form-item
|
||||
name="applianceType"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '请选择设备类型',
|
||||
}">
|
||||
}"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
设备类型
|
||||
<a-tooltip title="DuerOS平台拟定的规范">
|
||||
<a-tooltip
|
||||
title="DuerOS平台拟定的规范"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
style="margin-left: 2px;" />
|
||||
style="margin-left: 2px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-select placeholder="请选择设备类型" v-model:value="modelRef.applianceType" show-search :filter-option="filterOption" @change="typeChange">
|
||||
<a-select-option v-for="item in typeList" :key="item.id" :value="item.id" :label="item.name">{{item.name}}</a-select-option>
|
||||
<a-select
|
||||
placeholder="请选择设备类型"
|
||||
v-model:value="modelRef.applianceType"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
@change="typeChange"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in typeList"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
:label="item.name"
|
||||
>{{ item.name }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="productName" v-show="false" label="产品名称">
|
||||
<a-input v-model:value="modelRef.productName" />
|
||||
<a-form-item
|
||||
name="productName"
|
||||
v-show="false"
|
||||
label="产品名称"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modelRef.productName"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<p>动作映射</p>
|
||||
<a-collapse v-if="modelRef.actionMappings.length" :activeKey="modelRef.actionMappings.map((_, _index) => _index)">
|
||||
<a-collapse-panel v-for="(item, index) in modelRef.actionMappings" :key="index" :header="item.action ? getTypesActions(item.action).find(i => i.id === item.action)?.name : `动作映射${index + 1}`">
|
||||
<template #extra><AIcon type="DeleteOutlined" @click="delItem(index)" /></template>
|
||||
<a-collapse
|
||||
v-if="modelRef.actionMappings.length"
|
||||
:activeKey="actionActiveKey"
|
||||
@change="onActionCollChange"
|
||||
>
|
||||
<a-collapse-panel
|
||||
v-for="(
|
||||
item, index
|
||||
) in modelRef.actionMappings"
|
||||
:key="index"
|
||||
:header="
|
||||
item.action
|
||||
? getTypesActions(
|
||||
item.action,
|
||||
).find(
|
||||
(i) =>
|
||||
i.id === item.action,
|
||||
)?.name
|
||||
: `动作映射${index + 1}`
|
||||
"
|
||||
>
|
||||
<template #extra
|
||||
><AIcon
|
||||
type="DeleteOutlined"
|
||||
@click="delItem(index)"
|
||||
/></template>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item :name="['actionMappings', index, 'action']" :rules="{
|
||||
required: true,
|
||||
message: '请选择动作',
|
||||
}">
|
||||
<a-form-item
|
||||
:name="[
|
||||
'actionMappings',
|
||||
index,
|
||||
'action',
|
||||
]"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '请选择动作',
|
||||
}"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
动作
|
||||
<a-tooltip title="DuerOS平台拟定的设备类型具有的相关动作">
|
||||
<AIcon type="QuestionCircleOutlined" />
|
||||
<a-tooltip
|
||||
title="DuerOS平台拟定的设备类型具有的相关动作"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-select placeholder="请选择动作" v-model:value="item.action" show-search :filter-option="filterOption">
|
||||
<a-select-option v-for="i in getTypesActions(item.action)" :key="i.id" :value="i.id" :label="i.name">{{i.name}}</a-select-option>
|
||||
<a-select
|
||||
placeholder="请选择动作"
|
||||
v-model:value="
|
||||
item.action
|
||||
"
|
||||
show-search
|
||||
:filter-option="
|
||||
filterOption
|
||||
"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="i in getTypesActions(
|
||||
item.action,
|
||||
)"
|
||||
:key="i.id"
|
||||
:value="i.id"
|
||||
:label="i.name"
|
||||
>{{
|
||||
i.name
|
||||
}}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item :name="['actionMappings', index, 'actionType']" :rules="{
|
||||
required: true,
|
||||
message: '请选择操作',
|
||||
}">
|
||||
<a-form-item
|
||||
:name="[
|
||||
'actionMappings',
|
||||
index,
|
||||
'actionType',
|
||||
]"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '请选择操作',
|
||||
}"
|
||||
>
|
||||
<template #label>
|
||||
<span>
|
||||
操作
|
||||
<a-tooltip title="映射物联网平台中所选产品具备的动作">
|
||||
<AIcon type="QuestionCircleOutlined" />
|
||||
<a-tooltip
|
||||
title="映射物联网平台中所选产品具备的动作"
|
||||
>
|
||||
<AIcon
|
||||
type="QuestionCircleOutlined"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<a-select placeholder="请选择操作" v-model:value="item.actionType" show-search :filter-option="filterOption">
|
||||
<a-select-option value="command">下发指令</a-select-option>
|
||||
<a-select-option value="latestData">获取历史数据</a-select-option>
|
||||
<a-select
|
||||
placeholder="请选择操作"
|
||||
v-model:value="
|
||||
item.actionType
|
||||
"
|
||||
show-search
|
||||
:filter-option="
|
||||
filterOption
|
||||
"
|
||||
>
|
||||
<a-select-option
|
||||
value="command"
|
||||
>下发指令</a-select-option
|
||||
>
|
||||
<a-select-option
|
||||
value="latestData"
|
||||
>获取历史数据</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24" v-if="item.actionType">
|
||||
<a-form-item :name="['actionMappings', index, 'command']">
|
||||
<Command ref="command" :metadata="findProductMetadata" v-model:modelValue="item.command" :actionType="item.actionType" />
|
||||
<a-col
|
||||
:span="24"
|
||||
v-if="item.actionType"
|
||||
>
|
||||
<a-form-item
|
||||
:name="[
|
||||
'actionMappings',
|
||||
index,
|
||||
'command',
|
||||
]"
|
||||
>
|
||||
<Command
|
||||
ref="command"
|
||||
:metadata="
|
||||
findProductMetadata
|
||||
"
|
||||
v-model:modelValue="
|
||||
item.command
|
||||
"
|
||||
:actionType="
|
||||
item.actionType
|
||||
"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
@ -110,35 +259,118 @@
|
|||
</a-collapse>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-button type="dashed" style="width: 100%; margin-top: 10px" @click="addItem">
|
||||
<a-button
|
||||
type="dashed"
|
||||
style="width: 100%; margin-top: 10px"
|
||||
@click="addItem"
|
||||
>
|
||||
<AIcon
|
||||
type="PlusOutlined"
|
||||
style="margin-left: 2px;" />新增动作
|
||||
style="margin-left: 2px"
|
||||
/>新增动作
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<p style="margin-top: 20px">属性映射</p>
|
||||
<a-collapse v-if="modelRef.propertyMappings.length" :activeKey="modelRef.propertyMappings.map((_, _index) => _index)">
|
||||
<a-collapse-panel v-for="(item, index) in modelRef.propertyMappings" :key="index" :header="item.source ? getDuerOSProperties(item.source).find(i => i.id === item.source)?.name : `属性映射${index + 1}`">
|
||||
<template #extra><AIcon type="DeleteOutlined" @click="delPropertyItem(index)" /></template>
|
||||
<a-collapse
|
||||
v-if="modelRef.propertyMappings.length"
|
||||
:activeKey="propertyActiveKey"
|
||||
@change="onPropertyCollChange"
|
||||
>
|
||||
<a-collapse-panel
|
||||
v-for="(
|
||||
item, index
|
||||
) in modelRef.propertyMappings"
|
||||
:key="index"
|
||||
:header="
|
||||
item.source
|
||||
? getDuerOSProperties(
|
||||
item.source,
|
||||
).find(
|
||||
(i) =>
|
||||
i.id === item.source,
|
||||
)?.name
|
||||
: `属性映射${index + 1}`
|
||||
"
|
||||
>
|
||||
<template #extra
|
||||
><AIcon
|
||||
type="DeleteOutlined"
|
||||
@click="delPropertyItem(index)"
|
||||
/></template>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="DuerOS属性" :name="['propertyMappings', index, 'source']" :rules="{
|
||||
required: true,
|
||||
message: '请选择DuerOS属性',
|
||||
}">
|
||||
<a-select placeholder="请选择DuerOS属性" v-model:value="item.source" show-search :filter-option="filterOption">
|
||||
<a-select-option v-for="i in getDuerOSProperties(item.source)" :key="i.id" :value="i.id">{{i.name}}</a-select-option>
|
||||
<a-form-item
|
||||
label="DuerOS属性"
|
||||
:name="[
|
||||
'propertyMappings',
|
||||
index,
|
||||
'source',
|
||||
]"
|
||||
:rules="{
|
||||
required: true,
|
||||
message:
|
||||
'请选择DuerOS属性',
|
||||
}"
|
||||
>
|
||||
<a-select
|
||||
placeholder="请选择DuerOS属性"
|
||||
v-model:value="
|
||||
item.source
|
||||
"
|
||||
show-search
|
||||
:filter-option="
|
||||
filterOption
|
||||
"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="i in getDuerOSProperties(
|
||||
item.source,
|
||||
)"
|
||||
:key="i.id"
|
||||
:value="i.id"
|
||||
>{{
|
||||
i.name
|
||||
}}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="平台属性" :name="['propertyMappings', index, 'target']" :rules="{
|
||||
required: true,
|
||||
message: '请选择平台属性',
|
||||
}">
|
||||
<a-select placeholder="请选择平台属性" v-model:value="item.target" mode="tags" show-search :filter-option="filterOption">
|
||||
<a-select-option v-for="i in getProductProperties(item.target)" :key="i.id" :value="item.id">{{i.name}}</a-select-option>
|
||||
<a-form-item
|
||||
label="平台属性"
|
||||
:name="[
|
||||
'propertyMappings',
|
||||
index,
|
||||
'target',
|
||||
]"
|
||||
:rules="{
|
||||
required: true,
|
||||
message:
|
||||
'请选择平台属性',
|
||||
}"
|
||||
>
|
||||
<a-select
|
||||
placeholder="请选择平台属性"
|
||||
v-model:value="
|
||||
item.target
|
||||
"
|
||||
mode="tags"
|
||||
show-search
|
||||
:filter-option="
|
||||
filterOption
|
||||
"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="i in getProductProperties(
|
||||
item.target,
|
||||
)"
|
||||
:key="i.id"
|
||||
:value="item.id"
|
||||
>{{
|
||||
i.name
|
||||
}}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
@ -147,17 +379,26 @@
|
|||
</a-collapse>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-button type="dashed" style="width: 100%; margin-top: 10px" @click="addPropertyItem">
|
||||
<a-button
|
||||
type="dashed"
|
||||
style="width: 100%; margin-top: 10px"
|
||||
@click="addPropertyItem"
|
||||
>
|
||||
<AIcon
|
||||
type="PlusOutlined"
|
||||
style="margin-left: 2px;" />新增属性
|
||||
style="margin-left: 2px"
|
||||
/>新增属性
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="24" style="margin-top: 20px">
|
||||
<a-form-item label="说明" name="description" :rules="{
|
||||
max: 200,
|
||||
message: '最多输入200个字符',
|
||||
}">
|
||||
<a-form-item
|
||||
label="说明"
|
||||
name="description"
|
||||
:rules="{
|
||||
max: 200,
|
||||
message: '最多输入200个字符',
|
||||
}"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="modelRef.description"
|
||||
placeholder="请输入说明"
|
||||
|
@ -169,7 +410,12 @@
|
|||
</a-row>
|
||||
</a-form>
|
||||
<div v-if="type === 'edit'">
|
||||
<a-button :loading="loading" type="primary" @click="saveBtn">保存</a-button>
|
||||
<a-button
|
||||
:loading="loading"
|
||||
type="primary"
|
||||
@click="saveBtn"
|
||||
>保存</a-button
|
||||
>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
|
@ -181,9 +427,14 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Doc from './doc.vue'
|
||||
import Command from './command/index.vue'
|
||||
import { queryProductList, queryTypes, savePatch, detail } from '@/api/northbound/dueros'
|
||||
import Doc from './doc.vue';
|
||||
import Command from './command/index.vue';
|
||||
import {
|
||||
queryProductList,
|
||||
queryTypes,
|
||||
savePatch,
|
||||
detail,
|
||||
} from '@/api/northbound/dueros';
|
||||
import _ from 'lodash';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
|
@ -197,26 +448,51 @@ const modelRef = reactive({
|
|||
name: undefined,
|
||||
applianceType: undefined,
|
||||
productName: undefined,
|
||||
actionMappings: [{
|
||||
actionType: undefined,
|
||||
action: undefined,
|
||||
command: {
|
||||
messageType: undefined,
|
||||
message: {
|
||||
properties: undefined,
|
||||
functionId: undefined,
|
||||
inputs: []
|
||||
}
|
||||
}
|
||||
}],
|
||||
propertyMappings: [{
|
||||
source: undefined,
|
||||
target: []
|
||||
}],
|
||||
description: undefined
|
||||
actionMappings: [
|
||||
{
|
||||
actionType: undefined,
|
||||
action: undefined,
|
||||
command: {
|
||||
messageType: undefined,
|
||||
message: {
|
||||
properties: undefined,
|
||||
functionId: undefined,
|
||||
inputs: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
propertyMappings: [
|
||||
{
|
||||
source: undefined,
|
||||
target: [],
|
||||
},
|
||||
],
|
||||
description: undefined,
|
||||
});
|
||||
|
||||
const productList = ref<Record<string, any>[]>([]);
|
||||
const typeList = ref<Record<string, any>[]>([]);
|
||||
const command = ref([]);
|
||||
const loading = ref<boolean>(false);
|
||||
const type = ref<'edit' | 'view'>('edit');
|
||||
const actionActiveKey = ref<string[]>(['0']);
|
||||
const propertyActiveKey = ref<string[]>(['0']);
|
||||
|
||||
const onPropertyCollChange = (_key: string[]) => {
|
||||
propertyActiveKey.value = _key;
|
||||
};
|
||||
|
||||
const onActionCollChange = (_key: string[]) => {
|
||||
actionActiveKey.value = _key;
|
||||
};
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
const addItem = () => {
|
||||
actionActiveKey.value.push(String(modelRef.actionMappings.length));
|
||||
modelRef.actionMappings.push({
|
||||
actionType: undefined,
|
||||
action: undefined,
|
||||
|
@ -225,161 +501,182 @@ const addItem = () => {
|
|||
message: {
|
||||
properties: undefined,
|
||||
functionId: undefined,
|
||||
inputs: []
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const productList = ref<Record<string, any>[]>([])
|
||||
const typeList = ref<Record<string, any>[]>([])
|
||||
const command = ref([])
|
||||
const loading = ref<boolean>(false)
|
||||
const type = ref<'edit' | 'view'>('view')
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
inputs: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const delItem = (index: number) => {
|
||||
modelRef.actionMappings.splice(index, 1)
|
||||
}
|
||||
modelRef.actionMappings.splice(index, 1);
|
||||
};
|
||||
|
||||
const addPropertyItem = () => {
|
||||
propertyActiveKey.value.push(String(modelRef.propertyMappings.length));
|
||||
modelRef.propertyMappings.push({
|
||||
source: undefined,
|
||||
target: []
|
||||
})
|
||||
}
|
||||
target: [],
|
||||
});
|
||||
};
|
||||
|
||||
const delPropertyItem = (index: number) => {
|
||||
modelRef.propertyMappings.splice(index, 1)
|
||||
}
|
||||
modelRef.propertyMappings.splice(index, 1);
|
||||
};
|
||||
|
||||
const productChange = (value: string) => {
|
||||
modelRef.propertyMappings = modelRef.propertyMappings.map(item => {
|
||||
return {source: item.source, target: []}
|
||||
})
|
||||
const item = productList.value.find(item => item.id === value)
|
||||
if(item){
|
||||
modelRef.productName = item.name
|
||||
modelRef.propertyMappings = modelRef.propertyMappings.map((item) => {
|
||||
return { source: item.source, target: [] };
|
||||
});
|
||||
const item = productList.value.find((item) => item.id === value);
|
||||
if (item) {
|
||||
modelRef.productName = item.name;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const typeChange = () => {
|
||||
modelRef.propertyMappings = modelRef.propertyMappings.map(item => {
|
||||
return {source: undefined, target: item.target}
|
||||
})
|
||||
modelRef.actionMappings = modelRef.actionMappings.map(item => {
|
||||
return {...item, action: undefined}
|
||||
})
|
||||
}
|
||||
modelRef.propertyMappings = modelRef.propertyMappings.map((item) => {
|
||||
return { source: undefined, target: item.target };
|
||||
});
|
||||
modelRef.actionMappings = modelRef.actionMappings.map((item) => {
|
||||
return { ...item, action: undefined };
|
||||
});
|
||||
};
|
||||
|
||||
const findApplianceType = computed(() => {
|
||||
if(!modelRef.applianceType) return
|
||||
return typeList.value.find(item => item.id === modelRef.applianceType)
|
||||
})
|
||||
if (!modelRef.applianceType) return;
|
||||
return typeList.value.find((item) => item.id === modelRef.applianceType);
|
||||
});
|
||||
|
||||
const findProductMetadata = computed(() => {
|
||||
if(!modelRef.id) return
|
||||
const _product = productList.value?.find((item: any) => item.id === modelRef.id)
|
||||
return _product?.metadata && JSON.parse(_product.metadata || '{}')
|
||||
})
|
||||
if (!modelRef.id) return;
|
||||
const _product = productList.value?.find(
|
||||
(item: any) => item.id === modelRef.id,
|
||||
);
|
||||
return _product?.metadata && JSON.parse(_product.metadata || '{}');
|
||||
});
|
||||
|
||||
// 查询产品列表
|
||||
const getProduct = async (id?: string) => {
|
||||
const resp = await queryProductList(id)
|
||||
if(resp.status === 200){
|
||||
productList.value = (resp?.result as Record<string, any>[])
|
||||
const resp = await queryProductList(id);
|
||||
if (resp.status === 200) {
|
||||
productList.value = resp?.result as Record<string, any>[];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getTypes = async () => {
|
||||
const resp = await queryTypes()
|
||||
if(resp.status === 200){
|
||||
typeList.value = (resp?.result as Record<string, any>[])
|
||||
const resp = await queryTypes();
|
||||
if (resp.status === 200) {
|
||||
typeList.value = resp?.result as Record<string, any>[];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getDuerOSProperties = (val: string) => {
|
||||
const arr = modelRef.propertyMappings.map(item => item?.source) || []
|
||||
const checked = _.cloneDeep(arr)
|
||||
const _index = checked.findIndex(i => i === val)
|
||||
const arr = modelRef.propertyMappings.map((item) => item?.source) || [];
|
||||
const checked = _.cloneDeep(arr);
|
||||
const _index = checked.findIndex((i) => i === val);
|
||||
// 去掉重复的
|
||||
checked.splice(_index, 1)
|
||||
checked.splice(_index, 1);
|
||||
const targetList = findApplianceType.value?.properties;
|
||||
const list = targetList?.filter((i: {id: string}) => !checked.includes(i?.id as any))
|
||||
return list || []
|
||||
}
|
||||
const list = targetList?.filter(
|
||||
(i: { id: string }) => !checked.includes(i?.id as any),
|
||||
);
|
||||
return list || [];
|
||||
};
|
||||
|
||||
const getProductProperties = (val: string[]) => {
|
||||
const items = modelRef.propertyMappings.map((item: {target: string[]}) => item?.target.map(j => j)) || []
|
||||
const checked = _.flatMap(items)
|
||||
const _checked: any[] = []
|
||||
checked.map(_item => {
|
||||
if(!val.includes(_item)){
|
||||
_checked.push(_item)
|
||||
const items =
|
||||
modelRef.propertyMappings.map((item: { target: string[] }) =>
|
||||
item?.target.map((j) => j),
|
||||
) || [];
|
||||
const checked = _.flatMap(items);
|
||||
const _checked: any[] = [];
|
||||
checked.map((_item) => {
|
||||
if (!val.includes(_item)) {
|
||||
_checked.push(_item);
|
||||
}
|
||||
})
|
||||
const sourceList = findProductMetadata.value?.properties
|
||||
const list = sourceList?.filter((i: { id: string }) => !_checked.includes(i.id))
|
||||
return list || []
|
||||
}
|
||||
});
|
||||
const sourceList = findProductMetadata.value?.properties;
|
||||
const list = sourceList?.filter(
|
||||
(i: { id: string }) => !_checked.includes(i.id),
|
||||
);
|
||||
return list || [];
|
||||
};
|
||||
|
||||
const getTypesActions = (val: string) => {
|
||||
const items = modelRef.actionMappings.map((item) => item?.action) || []
|
||||
const checked = _.cloneDeep(items)
|
||||
const _index = checked.findIndex(i => i === val)
|
||||
checked.splice(_index, 1)
|
||||
const actionsList = findApplianceType.value?.actions || []
|
||||
const list = actionsList?.filter((i: { id: string, name: string }) => !checked.includes(i?.id as any))
|
||||
return list || []
|
||||
}
|
||||
const items = modelRef.actionMappings.map((item) => item?.action) || [];
|
||||
const checked = _.cloneDeep(items);
|
||||
const _index = checked.findIndex((i) => i === val);
|
||||
checked.splice(_index, 1);
|
||||
const actionsList = findApplianceType.value?.actions || [];
|
||||
const list = actionsList?.filter(
|
||||
(i: { id: string; name: string }) => !checked.includes(i?.id as any),
|
||||
);
|
||||
return list || [];
|
||||
};
|
||||
const saveBtn = async () => {
|
||||
const tasks = []
|
||||
for(let i = 0; i < command.value.length; i++){
|
||||
const tasks: any[] = [];
|
||||
for (let i = 0; i < command.value.length; i++) {
|
||||
const res = await (command.value[i] as any)?.saveBtn()
|
||||
tasks.push(res)
|
||||
if(!res) break
|
||||
}
|
||||
const data = await formRef.value.validate()
|
||||
if(tasks.every(item => item) && data){
|
||||
loading.value = true;
|
||||
const resp = await savePatch(toRaw(modelRef));
|
||||
loading.value = false;
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
formRef.value.resetFields();
|
||||
router.push('/iot/northbound/DuerOS/');
|
||||
if(!res || (res?.errorFields && res.errorFields.length)) {
|
||||
actionActiveKey.value.push(String(i));
|
||||
tasks.push(false);
|
||||
} else {
|
||||
tasks.push(res);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async (data: any) => {
|
||||
if (tasks.every((item) => item) && data) {
|
||||
loading.value = true;
|
||||
const resp = await savePatch(toRaw(modelRef));
|
||||
loading.value = false;
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
formRef.value.resetFields();
|
||||
router.push('/iot/northbound/DuerOS/');
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
const _arr = err.errorFields.map((item: any) => item.name);
|
||||
_arr.map((item: string | any[]) => {
|
||||
if (item.length >= 3) {
|
||||
if(item[0] === 'propertyMappings' && !propertyActiveKey.value.includes(item[1])){
|
||||
propertyActiveKey.value.push(item[1]);
|
||||
}
|
||||
if(item[0] === 'actionMappings' && !actionActiveKey.value.includes(item[1])){
|
||||
actionActiveKey.value.push(item[1]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
watch(
|
||||
() => route.params?.id,
|
||||
async (newId) => {
|
||||
if(newId){
|
||||
getProduct(newId as string)
|
||||
getTypes()
|
||||
if (newId) {
|
||||
getProduct(newId as string);
|
||||
getTypes();
|
||||
if (newId === ':id') return;
|
||||
const resp = await detail(newId as string)
|
||||
const resp = await detail(newId as string);
|
||||
const _data: any = resp.result;
|
||||
if (_data) {
|
||||
_data.applianceType = _data?.applianceType?.value;
|
||||
_data.applianceType = _data?.applianceType?.value;
|
||||
}
|
||||
Object.assign(modelRef, _data)
|
||||
Object.assign(modelRef, _data);
|
||||
}
|
||||
},
|
||||
{immediate: true, deep: true}
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => route.query.type,
|
||||
(newVal) => {
|
||||
if(newVal){
|
||||
type.value = newVal as 'edit' | 'view'
|
||||
if (newVal) {
|
||||
type.value = newVal as 'edit' | 'view';
|
||||
}
|
||||
},
|
||||
{immediate: true, deep: true}
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
</script>
|
|
@ -1,6 +1,10 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<Search :columns="columns" target="northbound-dueros" :params="params" />
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="northbound-dueros"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JTable
|
||||
ref="instanceRef"
|
||||
:columns="columns"
|
||||
|
@ -22,16 +26,12 @@
|
|||
:statusText="slotProps.state?.text"
|
||||
:statusNames="{
|
||||
enabled: 'success',
|
||||
disabled: 'error'
|
||||
disabled: 'error',
|
||||
}"
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img
|
||||
:src="
|
||||
getImage('/cloud/dueros.png')
|
||||
"
|
||||
/>
|
||||
<img :src="getImage('/cloud/dueros.png')" />
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
|
@ -43,9 +43,7 @@
|
|||
</h3>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
产品
|
||||
</div>
|
||||
<div class="card-item-content-text">产品</div>
|
||||
<div>{{ slotProps?.productName }}</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
|
@ -103,7 +101,7 @@
|
|||
/>
|
||||
</template>
|
||||
<template #applianceType="slotProps">
|
||||
{{slotProps.applianceType.text}}
|
||||
{{ slotProps.applianceType.text }}
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
|
@ -145,12 +143,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
query,
|
||||
_undeploy,
|
||||
_deploy,
|
||||
_delete
|
||||
} from '@/api/northbound/dueros';
|
||||
import { query, _undeploy, _deploy, _delete, queryProductList, queryTypes } from '@/api/northbound/dueros';
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
@ -169,17 +162,48 @@ const columns = [
|
|||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '产品名称',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
queryProductList().then((resp: any) => {
|
||||
resolve(
|
||||
resp.result.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '设备类型',
|
||||
dataIndex: 'applianceType',
|
||||
key: 'applianceType',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: () =>
|
||||
new Promise((resolve) => {
|
||||
queryTypes().then((resp: any) => {
|
||||
resolve(
|
||||
resp.result.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '说明',
|
||||
|
@ -191,6 +215,13 @@ const columns = [
|
|||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '正常', value: 'enabled' },
|
||||
{ label: '禁用', value: 'disabled' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
|
@ -216,8 +247,8 @@ const handleView = (id: string) => {
|
|||
router.push({
|
||||
path: '/iot/northbound/DuerOS/detail/' + id,
|
||||
query: {
|
||||
type: 'view'
|
||||
}
|
||||
type: 'view',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -249,8 +280,8 @@ const getActions = (
|
|||
router.push({
|
||||
path: '/iot/northbound/DuerOS/detail/' + data.id,
|
||||
query: {
|
||||
type: 'edit'
|
||||
}
|
||||
type: 'edit',
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -313,4 +344,8 @@ const getActions = (
|
|||
return actions.filter((i: ActionsType) => i.key !== 'view');
|
||||
return actions;
|
||||
};
|
||||
|
||||
const handleSearch = (_params: any) => {
|
||||
params.value = _params;
|
||||
};
|
||||
</script>
|
||||
|
|
Loading…
Reference in New Issue