feat: 完善插件管理
4
build.sh
|
@ -1,3 +1,3 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
docker build -t registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:1.0.0 .
|
docker build -t registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:2.1 .
|
||||||
docker push registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:1.0.0
|
docker push registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:2.1
|
||||||
|
|
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 9.3 KiB |
After Width: | Height: | Size: 3.5 KiB |
|
@ -573,4 +573,17 @@ export const queryLogsType = () => server.get(`/dictionary/device-log-type/items
|
||||||
|
|
||||||
export const getDeviceNumber = (data?:any) => server.post<number>('/device-instance/_count', data)
|
export const getDeviceNumber = (data?:any) => server.post<number>('/device-instance/_count', data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入映射设备
|
||||||
|
* @param productId
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
export const importDeviceByPlugin = (productId: string, data: any[]) => server.post(`/device/instance/plugin/${productId}/import`, data)
|
||||||
|
|
||||||
|
export const metadateMapById = (productId: string, data: ant[]) => server.patch(`/device/metadata/mapping/product/${productId}`, data)
|
||||||
|
|
||||||
|
export const getMetadateMapById = (productId: string) => server.get(`/device/metadata/mapping/product/${productId}`)
|
||||||
|
|
||||||
|
export const getInkingDevices = (data: string[]) => server.post('/plugin/mapping/device/_all', data)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -63,11 +63,12 @@ export const put = function <T>(url: string, data = {}) {
|
||||||
* @param {Object} [data]
|
* @param {Object} [data]
|
||||||
* @returns {AxiosInstance}
|
* @returns {AxiosInstance}
|
||||||
*/
|
*/
|
||||||
export const patch = function <T>(url: string, data = {}) {
|
export const patch = function <T>(url: string, data = {}, ext: any = {}) {
|
||||||
return request<any, AxiosResponseRewrite<T>>({
|
return request<any, AxiosResponseRewrite<T>>({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url,
|
url,
|
||||||
data
|
data,
|
||||||
|
...ext
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Inkling from './index.vue'
|
||||||
|
|
||||||
|
export default Inkling
|
|
@ -0,0 +1,72 @@
|
||||||
|
<template>
|
||||||
|
<j-modal
|
||||||
|
:width="800"
|
||||||
|
:mask-closable="false"
|
||||||
|
:visible="true"
|
||||||
|
title="设备ID映射"
|
||||||
|
:confirmLoading="loading"
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<InklingDevice
|
||||||
|
v-model:value='checkKey'
|
||||||
|
:accessId='accessId'
|
||||||
|
/>
|
||||||
|
</j-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang='ts' name='InklingModal'>
|
||||||
|
import InklingDevice from '@/views/device/components/InklingDevice'
|
||||||
|
import { onlyMessage } from '@/utils/comm'
|
||||||
|
import { savePluginData } from '@/api/link/plugin'
|
||||||
|
|
||||||
|
type Emit = {
|
||||||
|
(e: 'cancel'): void
|
||||||
|
(e: 'submit', data: string): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
accessId: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
channelId: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<Emit>()
|
||||||
|
const checkKey = ref(props.id)
|
||||||
|
const loading = ref(false)
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const handleOk = async () => {
|
||||||
|
if (checkKey.value) {
|
||||||
|
const res = await savePluginData(
|
||||||
|
'device',
|
||||||
|
props.channelId!,
|
||||||
|
route.params.id as string,
|
||||||
|
checkKey.value
|
||||||
|
).catch(() => ({ success: false }))
|
||||||
|
if (res.success) {
|
||||||
|
emit('submit', checkKey.value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onlyMessage('请选择设备', 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('cancel')
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -14,12 +14,36 @@
|
||||||
<j-descriptions-item label="设备ID">{{
|
<j-descriptions-item label="设备ID">{{
|
||||||
instanceStore.current?.id
|
instanceStore.current?.id
|
||||||
}}</j-descriptions-item>
|
}}</j-descriptions-item>
|
||||||
|
<j-descriptions-item v-if='instanceStore.current?.accessProvider === "plugin_gateway"'>
|
||||||
|
<template #label>
|
||||||
|
<div>
|
||||||
|
第三方系统设备ID
|
||||||
|
<j-tooltip>
|
||||||
|
<template #title>
|
||||||
|
<p>通过调用SDK或HTTP请求的方式接入第三方系统设备数据时,第三方系统与平台当前设备对应的设备ID。</p>
|
||||||
|
如双方ID值一致,则无需填写
|
||||||
|
</template>
|
||||||
|
<a-icon type='QuestionCircleOutlined' />
|
||||||
|
</j-tooltip>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<j-button v-if='!inklingDeviceId' type="link" @click='giveAnInkling'>映射</j-button>
|
||||||
|
<div v-else style='display: flex;justify-content: space-between;align-items: center;'>
|
||||||
|
<div style='flex: 1 1 auto;'>
|
||||||
|
<j-ellipsis>{{ inklingDeviceId }}</j-ellipsis>
|
||||||
|
</div>
|
||||||
|
<j-button type='link'>
|
||||||
|
<a-icon
|
||||||
|
type='EditOutlined'
|
||||||
|
@click='inkingVisible = true'
|
||||||
|
/>
|
||||||
|
</j-button>
|
||||||
|
</div>
|
||||||
|
</j-descriptions-item>
|
||||||
<j-descriptions-item label="产品名称">{{
|
<j-descriptions-item label="产品名称">{{
|
||||||
instanceStore.current?.productName
|
instanceStore.current?.productName
|
||||||
}}</j-descriptions-item>
|
}}</j-descriptions-item>
|
||||||
<!-- <j-descriptions-item label="产品分类">{{-->
|
|
||||||
<!-- instanceStore.current?.classifiedName-->
|
|
||||||
<!-- }}</j-descriptions-item>-->
|
|
||||||
<j-descriptions-item label="设备类型">{{
|
<j-descriptions-item label="设备类型">{{
|
||||||
instanceStore.current?.deviceType?.text
|
instanceStore.current?.deviceType?.text
|
||||||
}}</j-descriptions-item>
|
}}</j-descriptions-item>
|
||||||
|
@ -83,6 +107,14 @@
|
||||||
@close="visible = false"
|
@close="visible = false"
|
||||||
@save="saveBtn"
|
@save="saveBtn"
|
||||||
/>
|
/>
|
||||||
|
<InkingModal
|
||||||
|
v-if='inkingVisible'
|
||||||
|
:id='inklingDeviceId'
|
||||||
|
:channelId='channelId'
|
||||||
|
:accessId='instanceStore.current.accessId'
|
||||||
|
@cancel="inkingVisible = false"
|
||||||
|
@submit='saveInkling'
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -91,10 +123,16 @@ import Save from '../../Save/index.vue';
|
||||||
import Config from './components/Config/index.vue';
|
import Config from './components/Config/index.vue';
|
||||||
import Tags from './components/Tags/index.vue';
|
import Tags from './components/Tags/index.vue';
|
||||||
import Relation from './components/Relation/index.vue';
|
import Relation from './components/Relation/index.vue';
|
||||||
|
import InkingModal from './components/InklingModal'
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { detail as queryPluginAccessDetail } from '@/api/link/accessConfig'
|
||||||
|
import { getPluginData } from '@/api/link/plugin'
|
||||||
|
|
||||||
const visible = ref<boolean>(false);
|
const visible = ref<boolean>(false);
|
||||||
|
const inkingVisible = ref<boolean>(false);
|
||||||
const instanceStore = useInstanceStore();
|
const instanceStore = useInstanceStore();
|
||||||
|
const inklingDeviceId = ref()
|
||||||
|
const channelId = ref()
|
||||||
|
|
||||||
const saveBtn = () => {
|
const saveBtn = () => {
|
||||||
if (instanceStore.current?.id) {
|
if (instanceStore.current?.id) {
|
||||||
|
@ -102,4 +140,37 @@ const saveBtn = () => {
|
||||||
}
|
}
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const saveInkling = (id: string) => {
|
||||||
|
if (instanceStore.current?.id) {
|
||||||
|
instanceStore.refresh(instanceStore.current?.id);
|
||||||
|
}
|
||||||
|
channelId.value = id
|
||||||
|
giveAnInkling()
|
||||||
|
}
|
||||||
|
|
||||||
|
const giveAnInkling = () => {
|
||||||
|
inkingVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryInkling = () => {
|
||||||
|
if (instanceStore.current?.accessProvider === 'plugin_gateway') {
|
||||||
|
queryPluginAccessDetail(instanceStore.current?.accessId).then(async res => {
|
||||||
|
if (res.success) {
|
||||||
|
channelId.value = res.result.channelId
|
||||||
|
const pluginRes = await getPluginData('device', res.result.channelId, instanceStore.current?.id)
|
||||||
|
if (pluginRes.success) {
|
||||||
|
inklingDeviceId.value = pluginRes.result?.externalId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => instanceStore.current?.id, () => {
|
||||||
|
if (instanceStore.current?.id) {
|
||||||
|
queryInkling()
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
</script>
|
</script>
|
|
@ -0,0 +1,173 @@
|
||||||
|
<template>
|
||||||
|
<div class='file'>
|
||||||
|
<j-form layout='vertical'>
|
||||||
|
<j-form-item label='文件格式' >
|
||||||
|
<div class='file-type-label'>
|
||||||
|
<a-radio-group class='file-type-radio' v-model:value="modelRef.file.fileType" >
|
||||||
|
<a-radio-button value="xlsx">xlsx</a-radio-button>
|
||||||
|
<a-radio-button value="csv">csv</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
<a-checkbox v-model:checked="modelRef.file.autoDeploy">自动启用</a-checkbox>
|
||||||
|
</div>
|
||||||
|
</j-form-item>
|
||||||
|
<j-form-item label="文件上传">
|
||||||
|
<j-upload
|
||||||
|
v-model:fileList="modelRef.upload"
|
||||||
|
name="file"
|
||||||
|
:action="FILE_UPLOAD"
|
||||||
|
:headers="{
|
||||||
|
'X-Access-Token': LocalStore.get(TOKEN_KEY),
|
||||||
|
}"
|
||||||
|
:maxCount="1"
|
||||||
|
:showUploadList="false"
|
||||||
|
@change="uploadChange"
|
||||||
|
:accept="
|
||||||
|
modelRef?.file?.fileType ? `.${modelRef?.file?.fileType}` : '.xlsx'
|
||||||
|
"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
>
|
||||||
|
<j-button style='width: 760px;'>
|
||||||
|
<template #icon><AIcon type="UploadOutlined" /></template>
|
||||||
|
上传文件
|
||||||
|
</j-button>
|
||||||
|
</j-upload>
|
||||||
|
</j-form-item>
|
||||||
|
<j-form-item label='下载模板'>
|
||||||
|
<div class='file-download'>
|
||||||
|
<j-button @click="downFile('xlsx')">.xlsx</j-button>
|
||||||
|
<j-button @click="downFile('csv')">.csv</j-button>
|
||||||
|
</div>
|
||||||
|
</j-form-item>
|
||||||
|
</j-form>
|
||||||
|
<div v-if="importLoading">
|
||||||
|
<j-badge v-if="flag" status="processing" text="进行中" />
|
||||||
|
<j-badge v-else status="success" text="已完成" />
|
||||||
|
<span>总数量:{{ count }}</span>
|
||||||
|
<p style="color: red">{{ errMessage }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang='ts' name='DeviceImportFile'>
|
||||||
|
import { FILE_UPLOAD } from '@/api/comm';
|
||||||
|
import { TOKEN_KEY } from '@/utils/variable';
|
||||||
|
import { LocalStore, onlyMessage } from '@/utils/comm';
|
||||||
|
import { downloadFileByUrl } from '@/utils/utils';
|
||||||
|
import {
|
||||||
|
deviceImport,
|
||||||
|
templateDownload,
|
||||||
|
} from '@/api/device/instance';
|
||||||
|
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||||
|
import { message } from 'jetlinks-ui-components'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
product: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const modelRef = reactive({
|
||||||
|
product: props.product,
|
||||||
|
upload: [],
|
||||||
|
file: {
|
||||||
|
fileType: 'xlsx',
|
||||||
|
autoDeploy: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const importLoading = ref<boolean>(false);
|
||||||
|
const flag = ref<boolean>(false);
|
||||||
|
const count = ref<number>(0);
|
||||||
|
const errMessage = ref<string>('');
|
||||||
|
|
||||||
|
const downFile = async (type: string) => {
|
||||||
|
const res: any = await templateDownload(props.product!, type);
|
||||||
|
if (res) {
|
||||||
|
const blob = new Blob([res], { type: type });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
downloadFileByUrl(url, `设备导入模板`, type);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const beforeUpload = (_file: any) => {
|
||||||
|
const fileType = modelRef.file?.fileType === 'csv' ? 'csv' : 'xlsx';
|
||||||
|
const isCsv = _file.type === 'text/csv';
|
||||||
|
const isXlsx = _file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
||||||
|
if (!isCsv && fileType !== 'xlsx') {
|
||||||
|
onlyMessage('请上传.csv格式文件', 'warning');
|
||||||
|
}
|
||||||
|
if (!isXlsx && fileType !== 'csv') {
|
||||||
|
onlyMessage('请上传.xlsx格式文件', 'warning');
|
||||||
|
}
|
||||||
|
return (isCsv && fileType !== 'xlsx') || (isXlsx && fileType !== 'csv');
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitData = async (fileUrl: string) => {
|
||||||
|
if (!!fileUrl) {
|
||||||
|
count.value = 0;
|
||||||
|
errMessage.value = '';
|
||||||
|
flag.value = true;
|
||||||
|
const autoDeploy = !!modelRef?.file?.autoDeploy || false;
|
||||||
|
importLoading.value = true;
|
||||||
|
let dt = 0;
|
||||||
|
const source = new EventSourcePolyfill(
|
||||||
|
deviceImport(props.product!, fileUrl, autoDeploy),
|
||||||
|
);
|
||||||
|
source.onmessage = (e: any) => {
|
||||||
|
const res = JSON.parse(e.data);
|
||||||
|
if (res.success) {
|
||||||
|
const temp = res.result.total;
|
||||||
|
dt += temp;
|
||||||
|
count.value = dt;
|
||||||
|
} else {
|
||||||
|
errMessage.value = res.message || '失败';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
source.onerror = (e: { status: number }) => {
|
||||||
|
if (e.status === 403) errMessage.value = '暂无权限,请联系管理员';
|
||||||
|
flag.value = false;
|
||||||
|
source.close();
|
||||||
|
};
|
||||||
|
source.onopen = () => {};
|
||||||
|
} else {
|
||||||
|
message.error('请先上传文件');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadChange = async (info: Record<string, any>) => {
|
||||||
|
if (info.file.status === 'done') {
|
||||||
|
const resp: any = info.file.response || { result: '' };
|
||||||
|
await submitData(resp?.result || '');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang='less'>
|
||||||
|
.file {
|
||||||
|
.file-type-label {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.file-type-radio {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
:deep(.ant-radio-button-wrapper) {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-download {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
>button {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,17 +1,39 @@
|
||||||
<template>
|
<template>
|
||||||
<j-modal
|
<j-modal
|
||||||
:maskClosable="false"
|
:maskClosable="false"
|
||||||
:visible="true"
|
:visible="visible"
|
||||||
width="800px"
|
width="800px"
|
||||||
title="导入"
|
title="批量导入"
|
||||||
|
@cancel='cancel'
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<!-- 选择产品 -->
|
<!-- 选择产品 -->
|
||||||
<div v-if='steps === 0'>
|
<div v-if='steps === 0'>
|
||||||
<Product
|
<Product
|
||||||
v-model:rowKey='importData.productId'
|
v-model:rowKey='importData.productId'
|
||||||
|
@change='productChange'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if='steps === 1'>
|
||||||
|
<j-form :layout="'vertical'">
|
||||||
|
<j-form-item required label='选择导入方式'>
|
||||||
|
<j-card-select
|
||||||
|
:value="[importData.type]"
|
||||||
|
:column='typeOptions.length'
|
||||||
|
:options="typeOptions"
|
||||||
|
@change='typeChange'
|
||||||
|
>
|
||||||
|
<template #image='{image}'>
|
||||||
|
<img :src='image' />
|
||||||
|
</template>
|
||||||
|
</j-card-select>
|
||||||
|
</j-form-item>
|
||||||
|
</j-form>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<File v-if='importData.type ==="file"' :product='importData.productId' />
|
||||||
|
<Plugin v-else :accessId='productDetail.accessId' @change='pluginChange'/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<j-button v-if='steps === 0' @click='cancel' >取消</j-button>
|
<j-button v-if='steps === 0' @click='cancel' >取消</j-button>
|
||||||
|
@ -20,26 +42,79 @@
|
||||||
<j-button v-if='steps === 2' @click='save' type='primary'>确认</j-button>
|
<j-button v-if='steps === 2' @click='save' type='primary'>确认</j-button>
|
||||||
</template>
|
</template>
|
||||||
</j-modal>
|
</j-modal>
|
||||||
|
<j-modal
|
||||||
|
:maskClosable="false"
|
||||||
|
:visible="importVisible"
|
||||||
|
width="400px"
|
||||||
|
title="导入完成"
|
||||||
|
@cancel='importCancel'
|
||||||
|
@ok='importCancel'
|
||||||
|
>
|
||||||
|
<a-icon type='CheckOutlined' style='color: #2F54EB;' /> 已完成 新增设备 <span style='color: #2F54EB;'>{{count}}</span>
|
||||||
|
</j-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang='ts' setup name='DeviceImport'>
|
<script lang='ts' setup name='DeviceImport'>
|
||||||
import Product from './product.vue'
|
import Product from './product.vue'
|
||||||
import { onlyMessage } from '@/utils/comm'
|
import { getImage, onlyMessage } from '@/utils/comm'
|
||||||
|
import File from './file.vue'
|
||||||
import { queryList } from '@/api/device/product';
|
import Plugin from './plugin.vue'
|
||||||
|
import { importDeviceByPlugin } from '@/api/device/instance'
|
||||||
|
|
||||||
const emit = defineEmits(['cancel', 'save']);
|
const emit = defineEmits(['cancel', 'save']);
|
||||||
|
|
||||||
const steps = ref(0) // 步骤
|
const steps = ref(0) // 步骤
|
||||||
|
|
||||||
const importData = reactive({
|
const importData = reactive<{productId?: string, type?: string}>({
|
||||||
productId: undefined,
|
productId: undefined,
|
||||||
type: undefined,
|
type: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const productDetail = ref()
|
||||||
|
const deviceList = ref<any[]>([])
|
||||||
|
const visible = ref(true)
|
||||||
|
const importVisible = ref(false)
|
||||||
|
const count = ref(0)
|
||||||
|
|
||||||
|
const typeOptions = computed(() => {
|
||||||
|
const array = [
|
||||||
|
{
|
||||||
|
value: 'file',
|
||||||
|
label: '文件导入',
|
||||||
|
subLabel: '支持上传XLSX、CSV格式文件',
|
||||||
|
iconUrl: getImage('/device/import1.png'),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
if (productDetail.value?.accessProvider === 'plugin_gateway') {
|
||||||
|
array.push({
|
||||||
|
value: 'plugin',
|
||||||
|
label: '插件导入',
|
||||||
|
subLabel: '读取插件中的设备信息同步至平台',
|
||||||
|
iconUrl: getImage('/device/import2.png'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return array
|
||||||
|
})
|
||||||
|
|
||||||
|
const typeChange = (types: string[]) => {
|
||||||
|
importData.type = types[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const productChange = (detail: any) => {
|
||||||
|
productDetail.value = detail
|
||||||
|
}
|
||||||
|
|
||||||
const next = () => {
|
const next = () => {
|
||||||
if (steps.value === 0 && !importData.productId) {
|
if (steps.value === 0) {
|
||||||
return onlyMessage('请选择产品', 'error')
|
if (!importData.productId) {
|
||||||
|
return onlyMessage('请选择产品', 'error')
|
||||||
|
}
|
||||||
|
if (productDetail.value?.accessProvider !== 'plugin_gateway') {
|
||||||
|
importData.type = 'file'
|
||||||
|
importData.productId = productDetail.value?.id
|
||||||
|
steps.value = 2
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (steps.value === 1 && !importData.type) {
|
if (steps.value === 1 && !importData.type) {
|
||||||
return onlyMessage('请选择导入方式', 'error')
|
return onlyMessage('请选择导入方式', 'error')
|
||||||
|
@ -48,7 +123,7 @@ const next = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const prev = () => {
|
const prev = () => {
|
||||||
if (steps.value === 2 && importData.type) {
|
if (productDetail.value?.accessProvider !== 'plugin_gateway') {
|
||||||
steps.value = 0
|
steps.value = 0
|
||||||
} else {
|
} else {
|
||||||
steps.value -= 1
|
steps.value -= 1
|
||||||
|
@ -59,7 +134,33 @@ const cancel = () => {
|
||||||
emit('cancel')
|
emit('cancel')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pluginChange = (options: any[]) => {
|
||||||
|
deviceList.value = options
|
||||||
|
}
|
||||||
|
|
||||||
const save = () => {
|
const save = () => {
|
||||||
|
if (importData.type === 'file') {
|
||||||
|
cancel()
|
||||||
|
emit('save')
|
||||||
|
} else {
|
||||||
|
if (deviceList.value.length) {
|
||||||
|
importDeviceByPlugin(importData.productId!, deviceList.value).then(res => {
|
||||||
|
if (res.success) {
|
||||||
|
onlyMessage('操作成功')
|
||||||
|
// cancel()
|
||||||
|
visible.value = false
|
||||||
|
importVisible.value = true
|
||||||
|
count.value = res.result?.[0]?.result?.updated
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
onlyMessage('请选择设备', 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const importCancel = () => {
|
||||||
|
importVisible.value = false
|
||||||
emit('save')
|
emit('save')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
<template>
|
||||||
|
<div style=''>
|
||||||
|
<InklingDevice
|
||||||
|
:accessId='accessId'
|
||||||
|
:multiple='true'
|
||||||
|
@change='change'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang='ts' name='DeviceImportPlugin'>
|
||||||
|
import InklingDevice from '@/views/device/components/InklingDevice'
|
||||||
|
|
||||||
|
type Emit = {
|
||||||
|
(e: 'change', data: any[]): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
accessId: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<Emit>()
|
||||||
|
|
||||||
|
const change = (options: any[]) => {
|
||||||
|
emit('change', options)
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang='less'>
|
||||||
|
:deep(.device-import-product) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-right: 0px;
|
||||||
|
padding-left: 0px;
|
||||||
|
padding-top: 6px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -3,56 +3,72 @@
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
type='simple'
|
type='simple'
|
||||||
@search="handleSearch"
|
@search="handleSearch"
|
||||||
class="scene-search"
|
class="device-import-product"
|
||||||
target="device-import-product"
|
target="device-import-product"
|
||||||
/>
|
/>
|
||||||
<j-divider style='margin: 0' />
|
<j-divider style='margin: 0' />
|
||||||
<j-pro-table
|
<j-scrollbar :height='400'>
|
||||||
model='CARD'
|
<j-pro-table
|
||||||
:columns='columns'
|
model='CARD'
|
||||||
:params='params'
|
:columns='columns'
|
||||||
:request='productQuery'
|
:params='params'
|
||||||
:gridColumn='2'
|
:request='queryProductList'
|
||||||
:gridColumns='[2,2,2]'
|
:gridColumn='2'
|
||||||
:bodyStyle='{
|
:defaultParams="{
|
||||||
paddingRight: 0,
|
terms: [
|
||||||
paddingLeft: 0
|
{
|
||||||
}'
|
column: 'state',
|
||||||
>
|
value: '1',
|
||||||
<template #card="slotProps">
|
type: 'and'
|
||||||
<CardBox
|
},
|
||||||
:value='slotProps'
|
{
|
||||||
:active="rowKey === slotProps.id"
|
column: 'accessProvider',
|
||||||
:status="slotProps.state"
|
value: props?.type
|
||||||
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
|
}
|
||||||
:statusNames="{ 1: 'processing', 0: 'error', }"
|
],
|
||||||
@click="handleClick"
|
sorts: [{ name: 'createTime', order: 'desc' }]
|
||||||
>
|
}"
|
||||||
<template #img>
|
:gridColumns='[2,2,2]'
|
||||||
<slot name="img">
|
:bodyStyle='{
|
||||||
<img width='80' height='80' :src="slotProps.photoUrl || getImage('/device-product.png')" />
|
paddingRight: 0,
|
||||||
</slot>
|
paddingLeft: 0
|
||||||
</template>
|
}'
|
||||||
<template #content>
|
>
|
||||||
<div style='width: calc(100% - 100px)'>
|
<template #card="slotProps">
|
||||||
<Ellipsis>
|
<CardBox
|
||||||
<span style="font-size: 16px;font-weight: 600" >
|
:value='slotProps'
|
||||||
{{ slotProps.name }}
|
:active="rowKey === slotProps.id"
|
||||||
</span>
|
:status="slotProps.state"
|
||||||
</Ellipsis>
|
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
|
||||||
</div>
|
:statusNames="{ 1: 'processing', 0: 'error', }"
|
||||||
<j-row>
|
@click="handleClick"
|
||||||
<j-col :span="12">
|
>
|
||||||
<div class="card-item-content-text">
|
<template #img>
|
||||||
设备类型
|
<slot name="img">
|
||||||
</div>
|
<img width='80' height='80' :src="slotProps.photoUrl || getImage('/device-product.png')" />
|
||||||
<div>直连设备</div>
|
</slot>
|
||||||
</j-col>
|
</template>
|
||||||
</j-row>
|
<template #content>
|
||||||
</template>
|
<div style='width: calc(100% - 100px)'>
|
||||||
</CardBox>
|
<Ellipsis>
|
||||||
</template>
|
<span style="font-size: 16px;font-weight: 600" >
|
||||||
</j-pro-table>
|
{{ slotProps.name }}
|
||||||
|
</span>
|
||||||
|
</Ellipsis>
|
||||||
|
</div>
|
||||||
|
<j-row>
|
||||||
|
<j-col :span="12">
|
||||||
|
<div class="card-item-content-text">
|
||||||
|
设备类型
|
||||||
|
</div>
|
||||||
|
<div>直连设备</div>
|
||||||
|
</j-col>
|
||||||
|
</j-row>
|
||||||
|
</template>
|
||||||
|
</CardBox>
|
||||||
|
</template>
|
||||||
|
</j-pro-table>
|
||||||
|
</j-scrollbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang='ts' name='Product'>
|
<script setup lang='ts' name='Product'>
|
||||||
|
@ -112,32 +128,18 @@ const columns = [
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '接入方式',
|
|
||||||
dataIndex: 'accessName',
|
|
||||||
width: 150,
|
|
||||||
ellipsis: true,
|
|
||||||
search: {
|
|
||||||
type: 'select',
|
|
||||||
options: () => queryGatewayList().then((resp: any) =>
|
|
||||||
resp.result.map((item: any) => ({
|
|
||||||
label: item.name, value: item.id
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '设备类型',
|
title: '设备类型',
|
||||||
dataIndex: 'deviceType',
|
dataIndex: 'deviceType',
|
||||||
width: 150,
|
width: 150,
|
||||||
search: {
|
// search: {
|
||||||
type: 'select',
|
// type: 'select',
|
||||||
options: [
|
// options: [
|
||||||
{ label: '直连设备', value: 'device' },
|
// { label: '直连设备', value: 'device' },
|
||||||
{ label: '网关子设备', value: 'childrenDevice' },
|
// { label: '网关子设备', value: 'childrenDevice' },
|
||||||
{ label: '网关设备', value: 'gateway' },
|
// { label: '网关设备', value: 'gateway' },
|
||||||
]
|
// ]
|
||||||
}
|
// }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
|
@ -156,88 +158,16 @@ const columns = [
|
||||||
dataIndex: 'describe',
|
dataIndex: 'describe',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
width: 300,
|
width: 300,
|
||||||
},
|
|
||||||
{
|
|
||||||
dataIndex: 'classifiedId',
|
|
||||||
title: '分类',
|
|
||||||
hideInTable: true,
|
|
||||||
search: {
|
search: {
|
||||||
type: 'treeSelect',
|
type: 'string',
|
||||||
options: () => {
|
|
||||||
return new Promise((res => {
|
|
||||||
queryTree({ paging: false }).then(resp => {
|
|
||||||
res(resp.result)
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
componentProps: {
|
|
||||||
fieldNames: {
|
|
||||||
label: 'name',
|
|
||||||
value: 'id',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
dataIndex: 'id$dim-assets',
|
|
||||||
title: '所属组织',
|
|
||||||
hideInTable: true,
|
|
||||||
search: {
|
|
||||||
type: 'treeSelect',
|
|
||||||
options: () => new Promise((resolve) => {
|
|
||||||
getTreeData_api({ paging: false }).then((resp: any) => {
|
|
||||||
const formatValue = (list: any[]) => {
|
|
||||||
return list.map((item: any) => {
|
|
||||||
if (item.children) {
|
|
||||||
item.children = formatValue(item.children);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
value: JSON.stringify({
|
|
||||||
assetType: 'product',
|
|
||||||
targets: [
|
|
||||||
{
|
|
||||||
type: 'org',
|
|
||||||
id: item.id,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
resolve(formatValue(resp.result) || [])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const handleSearch = (p: any) => {
|
const handleSearch = (p: any) => {
|
||||||
params.value = p
|
params.value = p
|
||||||
}
|
}
|
||||||
|
|
||||||
const productQuery = async (p: any) => {
|
|
||||||
const sorts: any = [];
|
|
||||||
|
|
||||||
if (props.rowKey) {
|
|
||||||
sorts.push({
|
|
||||||
name: 'id',
|
|
||||||
value: props.rowKey,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
sorts.push({ name: 'createTime', order: 'desc' });
|
|
||||||
p.sorts = sorts
|
|
||||||
const resp = await queryProductList(p)
|
|
||||||
if (resp.success && props.rowKey && firstFind.value) {
|
|
||||||
const productItem = (resp.result as { data: any[]}).data.find((item: any) => item.id === props.rowKey)
|
|
||||||
emit('update:detail', productItem)
|
|
||||||
firstFind.value = false
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...resp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleClick = (detail: any) => {
|
const handleClick = (detail: any) => {
|
||||||
emit('update:rowKey', detail.id)
|
emit('update:rowKey', detail.id)
|
||||||
emit('change', detail)
|
emit('change', detail)
|
||||||
|
@ -246,7 +176,7 @@ const handleClick = (detail: any) => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang='less'>
|
<style scoped lang='less'>
|
||||||
.search {
|
:deep(.device-import-product) {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
padding-right: 0px;
|
padding-right: 0px;
|
||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
|
|
|
@ -271,7 +271,7 @@
|
||||||
</page-container>
|
</page-container>
|
||||||
<Import
|
<Import
|
||||||
v-if="importVisible"
|
v-if="importVisible"
|
||||||
@close="importVisible = false"
|
@cancel="importVisible = false"
|
||||||
@save="onRefresh"
|
@save="onRefresh"
|
||||||
/>
|
/>
|
||||||
<Export
|
<Export
|
||||||
|
@ -308,7 +308,7 @@ import {
|
||||||
} from '@/api/device/instance';
|
} from '@/api/device/instance';
|
||||||
import { getImage, LocalStore } from '@/utils/comm';
|
import { getImage, LocalStore } from '@/utils/comm';
|
||||||
import { message } from 'jetlinks-ui-components';
|
import { message } from 'jetlinks-ui-components';
|
||||||
import Import from './Import/index.vue';
|
import Import from './Import/modal.vue';
|
||||||
import Export from './Export/index.vue';
|
import Export from './Export/index.vue';
|
||||||
import Process from './Process/index.vue';
|
import Process from './Process/index.vue';
|
||||||
import Save from './Save/index.vue';
|
import Save from './Save/index.vue';
|
||||||
|
|
|
@ -129,6 +129,7 @@ import { getImage } from '@/utils/comm';
|
||||||
import { queryList, getAccessConfig } from '@/api/device/product'
|
import { queryList, getAccessConfig } from '@/api/device/product'
|
||||||
import { message } from 'jetlinks-ui-components'
|
import { message } from 'jetlinks-ui-components'
|
||||||
import { useMenuStore } from '@/store/menu';
|
import { useMenuStore } from '@/store/menu';
|
||||||
|
import { getProductByPluginId } from '@/api/link/plugin'
|
||||||
|
|
||||||
type Emit = {
|
type Emit = {
|
||||||
(e: 'submit', data: any): void
|
(e: 'submit', data: any): void
|
||||||
|
@ -255,16 +256,25 @@ const findProvidersByProvider = (provider: string) => {
|
||||||
*/
|
*/
|
||||||
const submitData = async () => {
|
const submitData = async () => {
|
||||||
if (selectedRowKeys.value.length) {
|
if (selectedRowKeys.value.length) {
|
||||||
loading.value= true
|
if (checkData.value.channel === 'plugin') {
|
||||||
const resp = await getAccessConfig(props.productId!, checkData.value.id).catch(() => ({ success: false, result: {}}))
|
const resp = await getProductByPluginId(checkData.value.channelId).catch(() => ({ success: false, result: []}))
|
||||||
// 返回外部组件需要的数据
|
|
||||||
loading.value = false
|
|
||||||
if (resp.success) {
|
|
||||||
// const providers = findProvidersByProvider((resp.result as any)[0]?.provider)
|
|
||||||
emit('submit', {
|
emit('submit', {
|
||||||
access: {...checkData.value},
|
access: {...checkData.value},
|
||||||
metadata: resp.result
|
productTypes: resp.result
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
loading.value= true
|
||||||
|
const resp = await getAccessConfig(props.productId!, checkData.value.id).catch(() => ({ success: false, result: {}}))
|
||||||
|
// 返回外部组件需要的数据
|
||||||
|
loading.value = false
|
||||||
|
if (resp.success) {
|
||||||
|
// const providers = findProvidersByProvider((resp.result as any)[0]?.provider)
|
||||||
|
emit('submit', {
|
||||||
|
access: {...checkData.value},
|
||||||
|
metadata: resp.result
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
message.error('请选择接入方式');
|
message.error('请选择接入方式');
|
||||||
|
|
|
@ -73,6 +73,19 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-else>{{ '暂无连接信息' }}</div>
|
<div v-else>{{ '暂无连接信息' }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 产品类型 -->
|
||||||
|
<j-form ref="pluginFormRef" :model="productData" layout="vertical" v-if='productTypes.length'>
|
||||||
|
<j-form-item name='id' label='产品类型' :rules='[{ required: true, message: "请选择产品类型"}]'>
|
||||||
|
<j-select
|
||||||
|
v-model:value='productData.id'
|
||||||
|
:options='productTypes'
|
||||||
|
@change='productTypeChange'
|
||||||
|
placeholder='请选择产品类型'
|
||||||
|
/>
|
||||||
|
</j-form-item>
|
||||||
|
|
||||||
|
</j-form>
|
||||||
|
<!-- 其它接入配置 -->
|
||||||
<Title
|
<Title
|
||||||
v-if="metadata?.name"
|
v-if="metadata?.name"
|
||||||
:data="metadata?.name"
|
:data="metadata?.name"
|
||||||
|
@ -160,6 +173,7 @@
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="submitDevice"
|
@click="submitDevice"
|
||||||
hasPermission="device/Instance:update"
|
hasPermission="device/Instance:update"
|
||||||
|
:loading='submitLoading'
|
||||||
>保存</PermissionButton
|
>保存</PermissionButton
|
||||||
>
|
>
|
||||||
</j-col>
|
</j-col>
|
||||||
|
@ -246,6 +260,15 @@
|
||||||
@cancel=' visible = false'
|
@cancel=' visible = false'
|
||||||
@submit='checkAccess'
|
@submit='checkAccess'
|
||||||
/>
|
/>
|
||||||
|
<!-- 物模型处理方式 -->
|
||||||
|
<MetaDataModal
|
||||||
|
v-if='metadataVisible'
|
||||||
|
:metadata='productData.metadata'
|
||||||
|
:access='access'
|
||||||
|
:data='metadataModalCacheData'
|
||||||
|
@cancel=' () => { metadataVisible = false, metadataModalCacheData = {}}'
|
||||||
|
@submit='MetaDataModalSubmit'
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup name='AccessConfig'>
|
<script lang="ts" setup name='AccessConfig'>
|
||||||
|
@ -257,20 +280,20 @@ import { usePermissionStore } from '@/store/permission';
|
||||||
import { steps, steps1 } from './util';
|
import { steps, steps1 } from './util';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import {
|
import {
|
||||||
getProviders,
|
getProviders,
|
||||||
_deploy,
|
_deploy,
|
||||||
_undeploy,
|
_undeploy,
|
||||||
queryList,
|
queryList,
|
||||||
getConfigView,
|
getConfigView,
|
||||||
getConfigMetadata,
|
getConfigMetadata,
|
||||||
productGuide,
|
productGuide,
|
||||||
productGuideSave,
|
productGuideSave,
|
||||||
getStoragList,
|
getStoragList,
|
||||||
saveDevice,
|
saveDevice,
|
||||||
updateDevice,
|
updateDevice,
|
||||||
detail,
|
detail,
|
||||||
modify,
|
modify, getAccessConfig
|
||||||
} from '@/api/device/product';
|
} from '@/api/device/product'
|
||||||
|
|
||||||
import Driver from 'driver.js';
|
import Driver from 'driver.js';
|
||||||
import 'driver.js/dist/driver.min.css';
|
import 'driver.js/dist/driver.min.css';
|
||||||
|
@ -280,6 +303,9 @@ import { useMenuStore } from '@/store/menu';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { accessConfigTypeFilter } from '@/utils/setting';
|
import { accessConfigTypeFilter } from '@/utils/setting';
|
||||||
import AccessModal from './accessModal.vue'
|
import AccessModal from './accessModal.vue'
|
||||||
|
import MetaDataModal from './metadataModal.vue'
|
||||||
|
import { getPluginData, getProductByPluginId, savePluginData } from '@/api/link/plugin'
|
||||||
|
import { detail as queryPluginAccessDetail } from '@/api/link/accessConfig'
|
||||||
|
|
||||||
const productStore = useProductStore();
|
const productStore = useProductStore();
|
||||||
const tableRef = ref();
|
const tableRef = ref();
|
||||||
|
@ -319,6 +345,17 @@ const form = reactive<Record<string, any>>({
|
||||||
const formData = reactive<Record<string, any>>({
|
const formData = reactive<Record<string, any>>({
|
||||||
data: productStore.current?.configuration || {},
|
data: productStore.current?.configuration || {},
|
||||||
});
|
});
|
||||||
|
// 产品类型
|
||||||
|
const productTypes = ref([])
|
||||||
|
const productData = reactive({
|
||||||
|
id: undefined,
|
||||||
|
metadata: {} // 物模型
|
||||||
|
})
|
||||||
|
const pluginFormRef = ref()
|
||||||
|
const metadataVisible = ref(false)
|
||||||
|
const metadataModalCacheData = ref()
|
||||||
|
|
||||||
|
const submitLoading = ref(false)
|
||||||
/**
|
/**
|
||||||
* 显示弹窗
|
* 显示弹窗
|
||||||
*/
|
*/
|
||||||
|
@ -571,11 +608,27 @@ const checkAccess = async (data: any) => {
|
||||||
visible.value = false
|
visible.value = false
|
||||||
accessId.value = data.access.id
|
accessId.value = data.access.id
|
||||||
access.value = data.access
|
access.value = data.access
|
||||||
metadata.value = data.metadata[0]
|
|
||||||
config.value = data.access?.transportDetail || {}
|
config.value = data.access?.transportDetail || {}
|
||||||
handleColumns()
|
productTypes.value = []
|
||||||
markdownToHtml.value = config.value?.document ? marked(config.value.document) : '';
|
productData.id = undefined
|
||||||
getGuide(!!data.metadata.length); //
|
productData.metadata = {}
|
||||||
|
if (data.access.channel === 'plugin') { // 插件设备
|
||||||
|
markdownToHtml.value = ''
|
||||||
|
productTypes.value = data.productTypes.map(item => ({ ...item, label: item.name, value: item.id}))
|
||||||
|
} else {
|
||||||
|
metadata.value = data.metadata[0]
|
||||||
|
handleColumns()
|
||||||
|
markdownToHtml.value = config.value?.document ? marked(config.value.document) : '';
|
||||||
|
getGuide(!!data.metadata.length); //
|
||||||
|
|
||||||
|
if (data.access?.transportDetail?.metadata) {
|
||||||
|
productData.metadata = JSON.parse(data.access?.transportDetail?.metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const productTypeChange = (id: string, items: any) => {
|
||||||
|
productData.metadata = items?.metadata || {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -623,20 +676,32 @@ const getData = async (accessId?: string) => {
|
||||||
// if (metadataResp.success) {
|
// if (metadataResp.success) {
|
||||||
// metadata.value = (metadataResp.result?.[0] as ConfigMetadata[]) || [];
|
// metadata.value = (metadataResp.result?.[0] as ConfigMetadata[]) || [];
|
||||||
// }
|
// }
|
||||||
queryAccessDetail(_accessId);
|
queryAccessDetail(_accessId);
|
||||||
|
if (productStore.current?.accessProvider === 'plugin_gateway') {
|
||||||
|
queryPluginAccessDetail(_accessId).then(async res => { //
|
||||||
|
if (res.success) {
|
||||||
|
const pluginRes = await getPluginData('product', res.result.channelId, productStore.current?.id)
|
||||||
|
const resp = await getProductByPluginId(res.result.channelId).catch(() => ({ success: false, result: []}))
|
||||||
|
if (resp.success) {
|
||||||
|
productTypes.value = resp.result.map(item => {
|
||||||
|
if (pluginRes?.result?.externalId === item.id) {
|
||||||
|
productData.id = pluginRes?.result?.externalId
|
||||||
|
productData.metadata = JSON.stringify(item.metadata || {})
|
||||||
|
}
|
||||||
|
return { ...item, label: item.name, value: item.id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
} else {
|
||||||
getConfigDetail(
|
getConfigDetail(
|
||||||
productStore.current?.messageProtocol || '',
|
productStore.current?.messageProtocol || '',
|
||||||
productStore.current?.transportProtocol || '',
|
productStore.current?.transportProtocol || '',
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
// else {
|
|
||||||
// if (productStore.current?.id) {
|
|
||||||
// getConfigMetadata(productStore.current?.id).then((resp: any) => {
|
|
||||||
// metadata.value = resp?.result[0] as ConfigMetadata[];
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
getStoragList().then((resp: any) => {
|
getStoragList().then((resp: any) => {
|
||||||
if (resp.status === 200) {
|
if (resp.status === 200) {
|
||||||
storageList.value = resp.result;
|
storageList.value = resp.result;
|
||||||
|
@ -648,47 +713,80 @@ const getData = async (accessId?: string) => {
|
||||||
* 保存设备接入
|
* 保存设备接入
|
||||||
*/
|
*/
|
||||||
const submitDevice = async () => {
|
const submitDevice = async () => {
|
||||||
const res = await formRef.value.validate();
|
if (pluginFormRef.value) { // 插件
|
||||||
const values = { storePolicy: form.storePolicy, ...formData.data };
|
const pluginRef = await pluginFormRef.value.validate();
|
||||||
const result: any = {};
|
if (!pluginRef) return
|
||||||
flatObj(values, result);
|
|
||||||
const { storePolicy, ...extra } = result;
|
|
||||||
const id = productStore.current?.id;
|
|
||||||
//TODO 二次确认是否覆盖物模型
|
|
||||||
// 更新选择设备(设备接入)
|
|
||||||
const accessObj = {
|
|
||||||
...productStore.current,
|
|
||||||
transportProtocol: access.value?.transport,
|
|
||||||
protocolName: access.value?.protocolDetail?.name,
|
|
||||||
accessId: access.value?.id,
|
|
||||||
accessName: access.value?.name,
|
|
||||||
accessProvider: access.value?.provider,
|
|
||||||
messageProtocol: access.value?.protocol,
|
|
||||||
}
|
}
|
||||||
const updateDeviceResp = await updateDevice(accessObj)
|
|
||||||
|
|
||||||
if (!updateDeviceResp.success) return
|
const res = await formRef.value.validate();
|
||||||
|
if (!res) return
|
||||||
// 更新产品配置信息
|
const values = { storePolicy: form.storePolicy, ...formData.data };
|
||||||
const resp = await modify(id || '', {
|
const id = productStore.current?.id;
|
||||||
id: id,
|
// 该产品是否有物模型,有则弹窗进行处理
|
||||||
configuration: { ...extra },
|
const _metadata = JSON.parse(productStore.current?.metadata || '{}')
|
||||||
storePolicy: storePolicy,
|
if (_metadata.properties?.length || _metadata.events?.length || _metadata.functions?.length || _metadata.tags?.length) {
|
||||||
});
|
metadataModalCacheData.value = {
|
||||||
if (resp.status === 200) {
|
id,
|
||||||
message.success('操作成功!');
|
values,
|
||||||
productStore.current!.storePolicy = storePolicy;
|
productTypeId: productData.id
|
||||||
if ((window as any).onTabSaveSuccess) {
|
}
|
||||||
if (resp.result) {
|
metadataVisible.value = true
|
||||||
(window as any).onTabSaveSuccess(resp);
|
} else {
|
||||||
setTimeout(() => window.close(), 300);
|
updateAccessData(id, values)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
getDetailInfo();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateAccessData = async (id: string, values: any) => {
|
||||||
|
const result: any = {};
|
||||||
|
flatObj(values, result);
|
||||||
|
const { storePolicy, ...extra } = result;
|
||||||
|
// 更新选择设备(设备接入)
|
||||||
|
const accessObj = {
|
||||||
|
...productStore.current,
|
||||||
|
metadata: JSON.stringify(productData.metadata || "{}"),
|
||||||
|
transportProtocol: access.value?.transport,
|
||||||
|
protocolName: access.value?.protocolDetail?.name,
|
||||||
|
accessId: access.value?.id,
|
||||||
|
accessName: access.value?.name,
|
||||||
|
accessProvider: access.value?.provider,
|
||||||
|
messageProtocol: access.value?.protocol,
|
||||||
|
}
|
||||||
|
submitLoading.value = true
|
||||||
|
const updateDeviceResp = await updateDevice(accessObj).catch(() => { success: false})
|
||||||
|
|
||||||
|
if (!updateDeviceResp.success) {
|
||||||
|
submitLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (access.value?.provider === "plugin_gateway") {
|
||||||
|
await savePluginData(
|
||||||
|
'product',
|
||||||
|
access.value?.channelId,
|
||||||
|
productStore.current.id,
|
||||||
|
productData.id
|
||||||
|
).catch(() => ({}))
|
||||||
|
}
|
||||||
|
// 更新产品配置信息
|
||||||
|
const resp = await modify(id || '', {
|
||||||
|
id: id,
|
||||||
|
configuration: { ...extra },
|
||||||
|
storePolicy: storePolicy,
|
||||||
|
});
|
||||||
|
submitLoading.value = false
|
||||||
|
if (resp.status === 200) {
|
||||||
|
message.success('操作成功!');
|
||||||
|
productStore.current!.storePolicy = storePolicy;
|
||||||
|
if ((window as any).onTabSaveSuccess) {
|
||||||
|
if (resp.result) {
|
||||||
|
(window as any).onTabSaveSuccess(resp);
|
||||||
|
setTimeout(() => window.close(), 300);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getDetailInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const flatObj = (obj: any, result: any) => {
|
const flatObj = (obj: any, result: any) => {
|
||||||
Object.keys(obj).forEach((key: string) => {
|
Object.keys(obj).forEach((key: string) => {
|
||||||
if (typeof obj[key] === 'string') {
|
if (typeof obj[key] === 'string') {
|
||||||
|
@ -699,8 +797,15 @@ const flatObj = (obj: any, result: any) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDetailInfo = () => {};
|
const getDetailInfo = async () => {
|
||||||
|
await productStore.getDetail(productStore.detail.id)
|
||||||
|
MetaDataModalSubmit()
|
||||||
|
};
|
||||||
|
|
||||||
|
const MetaDataModalSubmit = () => {
|
||||||
|
// 跳转物模型标签
|
||||||
|
productStore.tabActiveKey = 'Metadata'
|
||||||
|
}
|
||||||
|
|
||||||
getProvidersList()
|
getProvidersList()
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,259 @@
|
||||||
|
<template>
|
||||||
|
<j-modal
|
||||||
|
title="选择处理方式"
|
||||||
|
visible
|
||||||
|
width="900px"
|
||||||
|
okText="确定"
|
||||||
|
cancelText="取消"
|
||||||
|
:confirmLoading='loading'
|
||||||
|
@ok="submitData"
|
||||||
|
@cancel="cancel"
|
||||||
|
>
|
||||||
|
<div class='tip'>
|
||||||
|
<a-icon type='ExclamationCircleOutlined'/>
|
||||||
|
平台
|
||||||
|
<span style='font-weight: bold;padding:0 4px;'>物模型</span>
|
||||||
|
中已有数据,请选择处理方式。
|
||||||
|
<j-tooltip title='默认采用覆盖的方式处理功能、事件、标签下的数据'>
|
||||||
|
<a-icon type='QuestionCircleOutlined' />
|
||||||
|
</j-tooltip>
|
||||||
|
</div>
|
||||||
|
<j-form :layout="'vertical'" ref='formRef' :model='handleData'>
|
||||||
|
<j-form-item label='处理方式' :rules='[{ required: true, message: "请选择处理方式"}]' >
|
||||||
|
<j-card-select
|
||||||
|
v-model:value="handleData.type"
|
||||||
|
:column='4'
|
||||||
|
:options="options"
|
||||||
|
>
|
||||||
|
<template #image='{image}'>
|
||||||
|
<img :src='image' />
|
||||||
|
</template>
|
||||||
|
</j-card-select>
|
||||||
|
</j-form-item>
|
||||||
|
</j-form>
|
||||||
|
</j-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang='ts' setup name='MetadataModal'>
|
||||||
|
import { useProductStore } from '@/store/product';
|
||||||
|
import { getImage } from '@/utils/comm'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { modify, updateDevice } from '@/api/device/product'
|
||||||
|
import { message } from 'jetlinks-ui-components'
|
||||||
|
import { savePluginData } from '@/api/link/plugin'
|
||||||
|
|
||||||
|
type Emit = {
|
||||||
|
(e: 'submit'): void
|
||||||
|
(e: 'cancel'): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits<Emit>()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
metadata: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
access: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const productStore = useProductStore();
|
||||||
|
const { current: productDetail } = storeToRefs(productStore)
|
||||||
|
const formRef = ref()
|
||||||
|
const handleData = reactive({
|
||||||
|
type: undefined
|
||||||
|
})
|
||||||
|
const loading = ref(false)
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
value: 'intersection',
|
||||||
|
label: '取交集',
|
||||||
|
subLabel: '仅保留标识一致的属性',
|
||||||
|
iconUrl: getImage('/device/intersection.png'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'union',
|
||||||
|
label: '取并集',
|
||||||
|
subLabel: '保留平台、插件中的所有属性',
|
||||||
|
iconUrl: getImage('/device/union.png'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'ignore',
|
||||||
|
label: '忽略',
|
||||||
|
subLabel: '仅保留平台中的属性',
|
||||||
|
iconUrl: getImage('/device/ignore.png'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'cover',
|
||||||
|
label: '覆盖',
|
||||||
|
subLabel: '仅保留插件中的属性',
|
||||||
|
iconUrl: getImage('/device/cover.png'),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
const flatObj = (obj: any, result: any) => {
|
||||||
|
Object.keys(obj).forEach((key: string) => {
|
||||||
|
if (typeof obj[key] === 'string') {
|
||||||
|
result[key] = obj[key];
|
||||||
|
} else {
|
||||||
|
flatObj(obj[key], result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const updateAccessData = async (id: string, values: any, metadata: string) => {
|
||||||
|
const result: any = {};
|
||||||
|
flatObj(values, result);
|
||||||
|
const { storePolicy, ...extra } = result;
|
||||||
|
// 更新选择设备(设备接入)
|
||||||
|
const accessObj = {
|
||||||
|
...productDetail.value,
|
||||||
|
metadata: JSON.stringify(metadata),
|
||||||
|
transportProtocol: props.access?.transport,
|
||||||
|
protocolName: props.access?.protocolDetail?.name,
|
||||||
|
accessId: props.access?.id,
|
||||||
|
accessName: props.access?.name,
|
||||||
|
accessProvider: props.access?.provider,
|
||||||
|
messageProtocol: props.access?.protocol,
|
||||||
|
}
|
||||||
|
loading.value = true
|
||||||
|
const updateDeviceResp = await updateDevice(accessObj)
|
||||||
|
|
||||||
|
if (!updateDeviceResp.success) {
|
||||||
|
loading.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.access?.provider === 'plugin_gateway') {
|
||||||
|
await savePluginData(
|
||||||
|
'product',
|
||||||
|
props.access.channelId,
|
||||||
|
props.data.id,
|
||||||
|
props.data.productTypeId
|
||||||
|
).catch(() => ({}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新产品配置信息
|
||||||
|
const resp = await modify(id || '', {
|
||||||
|
id: id,
|
||||||
|
configuration: { ...extra },
|
||||||
|
storePolicy: storePolicy,
|
||||||
|
});
|
||||||
|
loading.value = false
|
||||||
|
if (resp.status === 200) {
|
||||||
|
message.success('操作成功!');
|
||||||
|
productStore.current!.storePolicy = storePolicy;
|
||||||
|
if ((window as any).onTabSaveSuccess) {
|
||||||
|
if (resp.result) {
|
||||||
|
(window as any).onTabSaveSuccess(resp);
|
||||||
|
setTimeout(() => window.close(), 300);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await productStore.getDetail(productDetail.value.id)
|
||||||
|
emit('submit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const submitData = () => {
|
||||||
|
formRef.value.validate().then((res) => {
|
||||||
|
if (res) {
|
||||||
|
let metadata = JSON.parse(productDetail.value?.metadata || '{}') // 产品物模型
|
||||||
|
switch (handleData.type![0]) {
|
||||||
|
case 'intersection': // 交集
|
||||||
|
metadata.properties = IntersectionFn(metadata.properties, props.metadata.properties)
|
||||||
|
metadata.events = IntersectionFn(metadata.events, props.metadata.events)
|
||||||
|
metadata.functions = IntersectionFn(metadata.functions, props.metadata.functions)
|
||||||
|
metadata.tags = IntersectionFn(metadata.tags, props.metadata.tags)
|
||||||
|
break;
|
||||||
|
case 'union': // 并集
|
||||||
|
metadata.properties = UnionFn(metadata.properties, props.metadata.properties)
|
||||||
|
metadata.functions = UnionFn(metadata.functions, props.metadata.functions)
|
||||||
|
metadata.events = UnionFn(metadata.events, props.metadata.events)
|
||||||
|
metadata.tags = UnionFn(metadata.tags, props.metadata.tags)
|
||||||
|
break;
|
||||||
|
case 'cover': // 覆盖
|
||||||
|
metadata = props.metadata
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
updateAccessData(
|
||||||
|
props.data.id,
|
||||||
|
props.data.values,
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
emit('cancel')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 交集处理函数, 只保留来自插件中的属性
|
||||||
|
* @param DataA 产品物模型
|
||||||
|
* @param DataB 插件物模型
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
const IntersectionFn = (DataA: any[] = [], DataB: any[] = []): any[] => {
|
||||||
|
const newData: any[] = []
|
||||||
|
if (!DataA.length) return []
|
||||||
|
DataB.forEach((item) => {
|
||||||
|
console.log(item, item.id)
|
||||||
|
if (DataA.some((aItem) => aItem.id === item.id)) {
|
||||||
|
newData.push(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return newData
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 并集函数处理,保留平台、插件中的所有属性,ID重复时,只保留来自插件中的1条属性。
|
||||||
|
* @param DataA 产品物模型
|
||||||
|
* @param DataB 插件物模型
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
const UnionFn = (DataA: any[] = [], DataB: any[] = []): any[] => {
|
||||||
|
const dataMap = new Map()
|
||||||
|
|
||||||
|
DataB.forEach((item) => {
|
||||||
|
dataMap.set(item.id, item)
|
||||||
|
})
|
||||||
|
|
||||||
|
DataA.forEach((item) => {
|
||||||
|
if (!dataMap.has(item.id)) {
|
||||||
|
dataMap.set(item.id, item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log(DataA, DataB, [...dataMap.values()])
|
||||||
|
return [...dataMap.values()]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang='less'>
|
||||||
|
.tip {
|
||||||
|
background: #F6F6F6;
|
||||||
|
color: #999;
|
||||||
|
padding: 10px 26px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
:deep(.j-card-item) {
|
||||||
|
padding: 16px !important;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Index from './index.vue'
|
||||||
|
|
||||||
|
export default Index
|
|
@ -0,0 +1,225 @@
|
||||||
|
<template>
|
||||||
|
<div class='metadata-map'>
|
||||||
|
<div class='left'>
|
||||||
|
<j-input-search
|
||||||
|
style='width: 350px;margin-bottom:24px;'
|
||||||
|
placeholder='搜索平台属性名称'
|
||||||
|
@search='search'
|
||||||
|
/>
|
||||||
|
<j-table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="dataSource"
|
||||||
|
:pagination='false'
|
||||||
|
:rowSelection='{
|
||||||
|
selectedRowKeys: selectedKeys,
|
||||||
|
hideSelectAll: true,
|
||||||
|
columnWidth: 0
|
||||||
|
}'
|
||||||
|
rowKey='id'
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, text, record }">
|
||||||
|
<template v-if='column.dataIndex === "name"'>
|
||||||
|
<span class='metadata-title'>{{ text }} ({{ record.id }})</span>
|
||||||
|
</template>
|
||||||
|
<template v-if='column.dataIndex === "plugin"'>
|
||||||
|
<j-select
|
||||||
|
v-model:value='record.plugin'
|
||||||
|
style='width: 100%'
|
||||||
|
@change='(id) => pluginChange(record, id)'
|
||||||
|
>
|
||||||
|
<j-select-option
|
||||||
|
v-for='(item, index) in pluginOptions'
|
||||||
|
:key='index + "_" + item.id'
|
||||||
|
:value='item.value'
|
||||||
|
:disabled='selectedPluginKeys.includes(item.id)'
|
||||||
|
>{{ item.label }} ({{ item.id }})</j-select-option>
|
||||||
|
</j-select>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</j-table>
|
||||||
|
</div>
|
||||||
|
<div class='right'>
|
||||||
|
<div class='title'>
|
||||||
|
功能说明
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
该功能用于将插件中的
|
||||||
|
<b>物模型属性标识</b>与
|
||||||
|
<b>平台物模型属性标识</b>进行映射,当两方属性标识不一致时,可在当前页面直接修改映射管理,系统将以映射后的物模型属性进行数据处理。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
未完成映射的属性标识“目标属性”列数据为空,代表该属性值来源以在平台配置的来源为准。
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
数据条背景亮起代表<b>标识一致</b>或<b>已完成映射</b>的属性。
|
||||||
|
</p>
|
||||||
|
<div class='title'>
|
||||||
|
功能图示
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<img :src='getImage("/device/matadataMap.png")' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang='ts' name='MetadataMap'>
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { useProductStore } from '@/store/product';
|
||||||
|
import { detail as queryPluginAccessDetail } from '@/api/link/accessConfig'
|
||||||
|
import { getPluginData, getProductByPluginId } from '@/api/link/plugin'
|
||||||
|
import { getImage, onlyMessage } from '@/utils/comm'
|
||||||
|
import { getMetadateMapById, metadateMapById } from '@/api/device/instance'
|
||||||
|
|
||||||
|
const productStore = useProductStore();
|
||||||
|
const { current: productDetail } = storeToRefs(productStore)
|
||||||
|
const dataSource = ref([])
|
||||||
|
const pluginOptions = ref<any[]>([])
|
||||||
|
|
||||||
|
const tableFilter = (value: string, record: any) => {
|
||||||
|
console.log(value, record)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '序号',
|
||||||
|
dataIndex: 'index',
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '平台属性',
|
||||||
|
dataIndex: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '目标属性',
|
||||||
|
dataIndex: 'plugin',
|
||||||
|
filters: [
|
||||||
|
{ text: '置顶已映射数据', value: 'already' },
|
||||||
|
{ text: '置顶未映射数据', value: 'not' },
|
||||||
|
],
|
||||||
|
onFilter: tableFilter
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const selectedKeys = computed(() => {
|
||||||
|
return dataSource.value.filter(item => !!item?.plugin).map(item => item.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectedPluginKeys = computed(() => {
|
||||||
|
return dataSource.value.filter(item => !!item?.plugin).map(item => item.plugin)
|
||||||
|
})
|
||||||
|
|
||||||
|
const getMetadataMapData = () => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
getMetadateMapById(productDetail.value?.id).then(res => {
|
||||||
|
if (res.success) {
|
||||||
|
resolve(res.result?.filter(item => item.customMapping)?.map(item => {
|
||||||
|
return {
|
||||||
|
id: item.metadataId,
|
||||||
|
pluginId: item.originalId
|
||||||
|
}
|
||||||
|
}) || [])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const getDefaultMetadata = async () => {
|
||||||
|
const metadata = JSON.parse(productDetail.value?.metadata || '{}')
|
||||||
|
const properties = metadata.properties
|
||||||
|
const pluginMedata = await getPluginMetadata()
|
||||||
|
const pluginProperties = pluginMedata?.properties || []
|
||||||
|
const metadataMap = await getMetadataMapData()
|
||||||
|
pluginOptions.value = pluginProperties.map(item => ({...item, label: item.name, value: item.id}))
|
||||||
|
|
||||||
|
const concatProperties = [ ...pluginProperties.map(item => ({ id: item.id, pluginId: item.id})), ...metadataMap]
|
||||||
|
dataSource.value = properties?.map((item: any, index: number) => {
|
||||||
|
|
||||||
|
const _m = concatProperties.find(p => p.id === item.id)
|
||||||
|
return {
|
||||||
|
index: index + 1,
|
||||||
|
id: item.id, // 产品物模型id
|
||||||
|
name: item.name,
|
||||||
|
type: item.valueType?.type,
|
||||||
|
plugin: _m?.pluginId, // 插件物模型id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPluginMetadata = (): Promise<{ properties: any[]}> => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
queryPluginAccessDetail(productDetail.value?.accessId!).then(async res => {
|
||||||
|
if (res.success) {
|
||||||
|
const _channelId = (res.result as any)!.channelId
|
||||||
|
const pluginRes = await getPluginData('product', _channelId, productDetail.value?.id).catch(() => ({ success: false, result: {}}))
|
||||||
|
const resp = await getProductByPluginId(_channelId).catch(() => ({ success: false, result: []}))
|
||||||
|
if (resp.success) {
|
||||||
|
const _item = (resp.result as any[])?.find((item: any) => item.id === (pluginRes?.result as any)?.externalId)
|
||||||
|
|
||||||
|
resolve(_item ? _item.metadata : { properties: [] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve({ properties: [] })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginChange = async (value: any, id: string) => {
|
||||||
|
const res = await metadateMapById(productDetail.value?.id, [{
|
||||||
|
metadataType: 'property',
|
||||||
|
metadataId: value.id,
|
||||||
|
originalId: id
|
||||||
|
}])
|
||||||
|
if (res.success) {
|
||||||
|
onlyMessage('操作成功')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultMetadata()
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang='less'>
|
||||||
|
.metadata-map {
|
||||||
|
position: relative;
|
||||||
|
min-height: 100%;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
margin-right: 424px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
position: absolute;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
height: 100%;
|
||||||
|
width: 400px;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: rgba(#000, .85);
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
p {
|
||||||
|
initial-letter: 28px;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata-title {
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-table-selection-column) {
|
||||||
|
padding: 0;
|
||||||
|
label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -112,6 +112,7 @@ import Info from './BasicInfo/indev.vue';
|
||||||
import Device from './DeviceAccess/index.vue';
|
import Device from './DeviceAccess/index.vue';
|
||||||
import Metadata from '../../../device/components/Metadata/index.vue';
|
import Metadata from '../../../device/components/Metadata/index.vue';
|
||||||
import DataAnalysis from './DataAnalysis/index.vue';
|
import DataAnalysis from './DataAnalysis/index.vue';
|
||||||
|
import MetadataMap from './MetadataMap'
|
||||||
// import Metadata from '../../../components/Metadata/index.vue';
|
// import Metadata from '../../../components/Metadata/index.vue';
|
||||||
import {
|
import {
|
||||||
_deploy,
|
_deploy,
|
||||||
|
@ -163,6 +164,7 @@ const tabs = {
|
||||||
Metadata,
|
Metadata,
|
||||||
Device,
|
Device,
|
||||||
DataAnalysis,
|
DataAnalysis,
|
||||||
|
MetadataMap
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
@ -280,6 +282,9 @@ const getProtocol = async () => {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (productStore.current?.accessProvider === 'plugin_gateway') {
|
||||||
|
list.value.push({ key: 'MetadataMap', tab: '物模型映射'})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
import InklingDevice from './index.vue'
|
||||||
|
export default InklingDevice
|
|
@ -0,0 +1,305 @@
|
||||||
|
<template>
|
||||||
|
<div class='inkling-device'>
|
||||||
|
<j-spin :spinning='spinning'>
|
||||||
|
<div class='search-box'>
|
||||||
|
<div class='search-warp'>
|
||||||
|
<j-advanced-search
|
||||||
|
v-if='!spinning'
|
||||||
|
:columns='columns'
|
||||||
|
type='simple'
|
||||||
|
@search='handleSearch'
|
||||||
|
class='device-inkling'
|
||||||
|
target='device-inkling'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class='multiple' v-if='multiple'>
|
||||||
|
<j-checkbox @change='checkChange'>全选</j-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class='device-list-warp'>
|
||||||
|
<j-scrollbar v-if='deviceList.length'>
|
||||||
|
<j-spin :spinning='deviceSpinning'>
|
||||||
|
<div class='device-list-items'>
|
||||||
|
<div
|
||||||
|
v-for='item in deviceList'
|
||||||
|
:class='{
|
||||||
|
"device-list-item": true,
|
||||||
|
"active": checkKeys.includes(item.id),
|
||||||
|
"disabled": disabledKeys.includes(item.id)
|
||||||
|
}'
|
||||||
|
@click='() => deviceClick(item.id, item)'
|
||||||
|
>
|
||||||
|
<template v-if='disabledKeys.includes(item.id)'>
|
||||||
|
<j-tooltip
|
||||||
|
title='该设备已绑定平台设备'
|
||||||
|
>
|
||||||
|
<span class='item-title'>{{ item.id }}</span>
|
||||||
|
</j-tooltip>
|
||||||
|
</template>
|
||||||
|
<span v-else class='item-title'>
|
||||||
|
{{ item.id }}
|
||||||
|
</span>
|
||||||
|
<a-icon
|
||||||
|
v-if='checkKeys.includes(item.id)'
|
||||||
|
type='CheckOutlined'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</j-spin>
|
||||||
|
</j-scrollbar>
|
||||||
|
<j-empty
|
||||||
|
v-else
|
||||||
|
description='暂无数据'
|
||||||
|
style='padding-top: 24px'
|
||||||
|
/>
|
||||||
|
<div class='device-list-pagination'>
|
||||||
|
<j-pagination
|
||||||
|
v-if='showPage'
|
||||||
|
:total='pageData.total'
|
||||||
|
:current='pageData.pageIndex + 1'
|
||||||
|
:pageSize='pageData.pageSize'
|
||||||
|
:show-total='() => {
|
||||||
|
const minSize = pageData.pageIndex * pageData.pageSize + 1;
|
||||||
|
const MaxSize = (pageData.pageIndex + 1) * pageData.pageSize;
|
||||||
|
return `第 ${minSize} - ${MaxSize > pageData.total ? pageData.total : MaxSize } 条/总共 ${pageData.total} 条`;
|
||||||
|
}'
|
||||||
|
@change='pageChange'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</j-spin>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang='ts' name='InklingDevice'>
|
||||||
|
|
||||||
|
import { getCommandsByAccess, getCommandsDevicesByAccessId } from '@/api/link/accessConfig'
|
||||||
|
import { getInkingDevices } from '@/api/device/instance'
|
||||||
|
import { isArray } from 'lodash-es'
|
||||||
|
|
||||||
|
type Emit = {
|
||||||
|
(e: 'update:value', data: string | string[]): void
|
||||||
|
(e: 'change', data: any | any[]): void
|
||||||
|
}
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: [String, Array],
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
accessId: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<Emit>()
|
||||||
|
|
||||||
|
const spinning = ref(true)
|
||||||
|
const deviceSpinning = ref(false)
|
||||||
|
const deviceList = ref([])
|
||||||
|
const disabledKeys = ref<string[]>([])
|
||||||
|
const checkKeys = ref<string[]>([])
|
||||||
|
const checkCache = ref<Map<string, any>>(new Map())
|
||||||
|
const showPage = ref(false)
|
||||||
|
const pageData = reactive({
|
||||||
|
pageSize: 10,
|
||||||
|
pageIndex: 0,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
const params = ref({
|
||||||
|
terms: []
|
||||||
|
})
|
||||||
|
|
||||||
|
const columns = ref([])
|
||||||
|
|
||||||
|
const queryInkingDevices = (data: string[]) => {
|
||||||
|
return new Promise(async (resolve) => {
|
||||||
|
if (!data.length) {
|
||||||
|
resolve(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await getInkingDevices(data)
|
||||||
|
if (res) {
|
||||||
|
disabledKeys.value = res.result?.map(item => item.externalId)
|
||||||
|
}
|
||||||
|
resolve(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDeviceList = async () => {
|
||||||
|
const resp = await getCommandsDevicesByAccessId(props.accessId!, {
|
||||||
|
pageIndex: pageData.pageIndex,
|
||||||
|
pageSize: pageData.pageSize,
|
||||||
|
terms: params.value.terms
|
||||||
|
}).catch(() => ({ success: false }))
|
||||||
|
if (resp.success) {
|
||||||
|
await queryInkingDevices(resp.result?.data.map(item => item.id) || [])
|
||||||
|
deviceList.value = resp.result?.data || []
|
||||||
|
pageData.total = resp.result?.total || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkChange = (e: any) => { // 全选
|
||||||
|
if (e.target.checked) {
|
||||||
|
const keys = deviceList.value.filter(item => {
|
||||||
|
// 过滤已选中和已绑定
|
||||||
|
const type = !checkKeys.value.includes(item.id) && !disabledKeys.value.includes(item.id)
|
||||||
|
if (type && checkCache.value.has(item.id)) {
|
||||||
|
checkCache.value.set(item.id, item)
|
||||||
|
}
|
||||||
|
return type
|
||||||
|
}).map(item => item.id)
|
||||||
|
checkKeys.value = [...checkKeys.value, ...keys]
|
||||||
|
emit('update:value', checkKeys.value)
|
||||||
|
emit('change', [...checkCache.value.values()])
|
||||||
|
} else {
|
||||||
|
checkCache.value.clear()
|
||||||
|
checkKeys.value = []
|
||||||
|
emit('update:value', [])
|
||||||
|
emit('change', [])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSearch = (p: any) => { // 查询
|
||||||
|
pageData.pageIndex = 0
|
||||||
|
params.value = p
|
||||||
|
getDeviceList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageChange = (page: number, pageSize: number) => { // 分页变化
|
||||||
|
pageData.pageSize = pageSize
|
||||||
|
pageData.pageIndex = page - 1
|
||||||
|
getDeviceList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
if (props.accessId) {
|
||||||
|
const resp = await getCommandsByAccess(props.accessId)
|
||||||
|
if (resp.success) {
|
||||||
|
const item = resp.result?.[0]
|
||||||
|
if (item) {
|
||||||
|
showPage.value = item.id === 'QueryDevicePage' // 分页
|
||||||
|
columns.value = item.expands?.terms?.map(t => ({
|
||||||
|
title: t.name,
|
||||||
|
dataIndex: t.id,
|
||||||
|
search: {
|
||||||
|
type: t.valueType.type
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spinning.value = false
|
||||||
|
await getDeviceList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deviceClick = (id: string, option: any) => {
|
||||||
|
if (option.disabled || disabledKeys.value.includes(id)) return
|
||||||
|
|
||||||
|
const _check = new Set(checkKeys.value)
|
||||||
|
|
||||||
|
if (props.multiple) { // 多选
|
||||||
|
if (_check.has(id)) {
|
||||||
|
_check.delete(id)
|
||||||
|
checkCache.value.delete(id)
|
||||||
|
} else {
|
||||||
|
checkCache.value.set(id, option)
|
||||||
|
_check.add(id)
|
||||||
|
}
|
||||||
|
checkKeys.value = [..._check.values()]
|
||||||
|
emit('update:value', checkKeys.value)
|
||||||
|
emit('change', [...checkCache.value.values()])
|
||||||
|
} else {
|
||||||
|
checkKeys.value = [id]
|
||||||
|
emit('update:value', id)
|
||||||
|
emit('change', option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.value, (newValue) => {
|
||||||
|
if (!newValue) {
|
||||||
|
checkKeys.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (isArray(newValue)) {
|
||||||
|
checkKeys.value = newValue
|
||||||
|
} else {
|
||||||
|
checkKeys.value = [newValue as string]
|
||||||
|
}
|
||||||
|
}, { immediate: true, deep: true })
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
init()
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang='less'>
|
||||||
|
.inkling-device {
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
padding-bottom: 24px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
gap: 24px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
:deep(.device-inkling) {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-warp {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiple {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.device-list-warp {
|
||||||
|
.device-list-items {
|
||||||
|
.device-list-item {
|
||||||
|
padding: 10px 16px;
|
||||||
|
color: #4F4F4F;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
> .item-title {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(47, 84, 235, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: rgba(153, 153, 153, 0.06);
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
background-color: rgba(153, 153, 153, 0.06);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-list-pagination {
|
||||||
|
margin-top: 24px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -469,7 +469,7 @@ export default {
|
||||||
id: '1-4-9',
|
id: '1-4-9',
|
||||||
parentId: '1-4',
|
parentId: '1-4',
|
||||||
path: 'T4zX-A0TC-BFum',
|
path: 'T4zX-A0TC-BFum',
|
||||||
sortIndex: 9999,
|
sortIndex: 9998,
|
||||||
level: 1,
|
level: 1,
|
||||||
name: '远程升级',
|
name: '远程升级',
|
||||||
code: 'device/Firmware',
|
code: 'device/Firmware',
|
||||||
|
@ -493,6 +493,34 @@ export default {
|
||||||
accessDescription: '此菜单不支持数据权限控制',
|
accessDescription: '此菜单不支持数据权限控制',
|
||||||
granted: true,
|
granted: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: '1-4-10',
|
||||||
|
parentId: '1-4',
|
||||||
|
path: 'T4zX-A0TC-BFum',
|
||||||
|
sortIndex: 9999,
|
||||||
|
level: 1,
|
||||||
|
name: '插件管理',
|
||||||
|
code: 'link/plugin',
|
||||||
|
icon: 'BoxPlotOutlined',
|
||||||
|
url: '/iot/link/plugin',
|
||||||
|
buttons: [
|
||||||
|
{ id: 'view', name: '查看', enabled: true, granted: true },
|
||||||
|
{ id: 'update', name: '编辑', enabled: true, granted: true },
|
||||||
|
{ id: 'delete', name: '删除', enabled: true, granted: true },
|
||||||
|
{
|
||||||
|
id: 'add',
|
||||||
|
name: '新增',
|
||||||
|
enabled: true,
|
||||||
|
granted: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
accessSupport: { text: '不支持', value: 'unsupported' },
|
||||||
|
assetAccesses: [],
|
||||||
|
options: {},
|
||||||
|
createTime: 1659344075524,
|
||||||
|
accessDescription: '此菜单不支持数据权限控制',
|
||||||
|
granted: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
[ROLEKEYS.complex]: [
|
[ROLEKEYS.complex]: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1436,6 +1436,63 @@ export default [
|
||||||
supportDataAccess: false,
|
supportDataAccess: false,
|
||||||
indirectMenus: ['8ddbb67de5f65514105d47b448bfd70e']
|
indirectMenus: ['8ddbb67de5f65514105d47b448bfd70e']
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
code: 'link/plugin',
|
||||||
|
name: '插件管理',
|
||||||
|
owner: 'iot',
|
||||||
|
//parentId: '1-4',
|
||||||
|
id: 'a20354876e9519e48f5ed6710ba6efb3',
|
||||||
|
sortIndex: 10,
|
||||||
|
url: '/iot/link/plugin',
|
||||||
|
icon: 'BoxPlotOutlined',
|
||||||
|
showPage: ['plugin-driver'],
|
||||||
|
permissions: [],
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
id: 'view',
|
||||||
|
name: '查看',
|
||||||
|
permissions: [
|
||||||
|
{
|
||||||
|
permission: 'plugin-driver',
|
||||||
|
actions: ['save'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'update',
|
||||||
|
name: '编辑',
|
||||||
|
permissions: [
|
||||||
|
{
|
||||||
|
permission: 'plugin-driver',
|
||||||
|
actions: ['save'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'delete',
|
||||||
|
name: '删除',
|
||||||
|
permissions: [
|
||||||
|
{
|
||||||
|
permission: 'plugin-driver',
|
||||||
|
actions: ['delete'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'add',
|
||||||
|
name: '新增',
|
||||||
|
permissions: [
|
||||||
|
{
|
||||||
|
permission: 'plugin-driver',
|
||||||
|
actions: ['save'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
],
|
||||||
|
accessSupport: { text: "不支持", value: "unsupported" },
|
||||||
|
supportDataAccess: false,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -135,11 +135,11 @@ const submitData = async () => {
|
||||||
const judgeInitSet = async () => {
|
const judgeInitSet = async () => {
|
||||||
const resp: any = await getInit();
|
const resp: any = await getInit();
|
||||||
if (resp.status === 200 && resp.result.length) {
|
if (resp.status === 200 && resp.result.length) {
|
||||||
window.location.href = '/';
|
// window.location.href = '/';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
// judgeInitSet();
|
judgeInitSet();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|
|
@ -90,7 +90,6 @@
|
||||||
{
|
{
|
||||||
max: 64,
|
max: 64,
|
||||||
message: '最多可输入64个字符',
|
message: '最多可输入64个字符',
|
||||||
trigger: 'blur',
|
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
name='name'
|
name='name'
|
||||||
|
@ -325,10 +324,10 @@ const saveData = () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const resp =
|
const resp =
|
||||||
paramsId === ':id'
|
paramsId === ':id'
|
||||||
? await save(params)
|
? await save(params).catch(() => { success: false})
|
||||||
: await update({ ...params, id: paramsId });
|
: await update({ ...params, id: paramsId }).catch(() => { success: false});
|
||||||
loading.value = false
|
loading.value = false
|
||||||
if (resp.status === 200) {
|
if (resp.success) {
|
||||||
onlyMessage('操作成功', 'success');
|
onlyMessage('操作成功', 'success');
|
||||||
history.back();
|
history.back();
|
||||||
if ((window as any).onTabSaveSuccess) {
|
if ((window as any).onTabSaveSuccess) {
|
||||||
|
|
|
@ -74,7 +74,7 @@
|
||||||
import { ID_Rule, Max_Length_64, Max_Length_200, RequiredStringFn } from '@/components/Form/rules'
|
import { ID_Rule, Max_Length_64, Max_Length_200, RequiredStringFn } from '@/components/Form/rules'
|
||||||
import UploadFile from './UploadFile.vue'
|
import UploadFile from './UploadFile.vue'
|
||||||
import { FileUploadResult } from '@/views/link/plugin/typings'
|
import { FileUploadResult } from '@/views/link/plugin/typings'
|
||||||
import { add, vailIdFn } from '@/api/link/plugin'
|
import { add, update, vailIdFn } from '@/api/link/plugin'
|
||||||
import { message } from 'jetlinks-ui-components'
|
import { message } from 'jetlinks-ui-components'
|
||||||
import { TypeMap } from './util'
|
import { TypeMap } from './util'
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ const fileType = ref(props.data.type)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
const vailId = async (_: any, value: string) => {
|
const vailId = async (_: any, value: string) => {
|
||||||
if (!!props.data.id && value) { // 新增校验
|
if (!props.data.id && value) { // 新增校验
|
||||||
const resp = await vailIdFn(value)
|
const resp = await vailIdFn(value)
|
||||||
if (resp.success && resp.result) {
|
if (resp.success && resp.result) {
|
||||||
return Promise.reject('ID重复');
|
return Promise.reject('ID重复');
|
||||||
|
@ -135,7 +135,7 @@ const handleSave = async () => {
|
||||||
const data = await formRef.value.validate()
|
const data = await formRef.value.validate()
|
||||||
if (data) {
|
if (data) {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const resp = await add(modelRef).catch(() => { success: false })
|
const resp = props.data.id ? await update(modelRef).catch(() => { success: false }) : await add(modelRef).catch(() => { success: false })
|
||||||
loading.value = false
|
loading.value = false
|
||||||
if (resp.success) {
|
if (resp.success) {
|
||||||
message.success('操作成功!');
|
message.success('操作成功!');
|
||||||
|
|