Merge branch 'dev'
# Conflicts: # src/views/notice/Template/Detail/index.vue
11
README.md
|
|
@ -24,7 +24,16 @@ yarn add jetlinks-ui-components@latest
|
|||
yarn dev:force
|
||||
|
||||
```
|
||||
## Node
|
||||
* node >= 18.14.0
|
||||
|
||||
## 浏览器兼容
|
||||
* Chrome >= 100
|
||||
* Firefox >= 100
|
||||
* Edge >= 100
|
||||
不支持IE
|
||||
|
||||
### 备注
|
||||
|
||||
项目在开发模式下,首页加载慢属于正常现象;
|
||||
* 项目在开发模式下,首页加载慢属于正常现象;
|
||||
* 打开F12后页面卡顿是`vuetools`引起,[https://github.com/vuejs/devtools/issues/1987](https://github.com/vuejs/devtools/issues/1987)
|
||||
4
build.sh
|
|
@ -1,3 +1,3 @@
|
|||
#!/usr/bin/env bash
|
||||
docker build -t registry.cn-shenzhen.aliyuncs.com/jetlinks/jetlinks-ui-vue:1.0.0 .
|
||||
docker push 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:2.1
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
"event-source-polyfill": "^1.0.31",
|
||||
"global": "^4.4.0",
|
||||
"jetlinks-store": "^0.0.3",
|
||||
"jetlinks-ui-components": "^1.0.8",
|
||||
"jetlinks-ui-components": "^1.0.9",
|
||||
"js-cookie": "^3.0.1",
|
||||
"less": "^4.1.3",
|
||||
"less-loader": "^11.1.0",
|
||||
|
|
|
|||
|
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 |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 5.9 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)
|
||||
|
||||
/**
|
||||
* 导入映射设备
|
||||
* @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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -146,8 +146,8 @@ const getBackgroundColor = (code: string | number) => {
|
|||
const _color = color[code] || color.default;
|
||||
return `linear-gradient(
|
||||
188.4deg,
|
||||
rgba(${_color}, 0.03) 22.94%,
|
||||
rgba(${_color}, 0) 94.62%
|
||||
rgba(${_color}, 0.03) 30%,
|
||||
rgba(${_color}, 0) 80%
|
||||
)`;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ onUnmounted(() => {
|
|||
if (ws.value) {
|
||||
ws.value.unsubscribe?.();
|
||||
}
|
||||
clearAction()
|
||||
})
|
||||
|
||||
const options = ref<{ label: string, value: string }[]>()
|
||||
|
|
|
|||
|
|
@ -32,20 +32,14 @@ export const defaultBranches = [
|
|||
{
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: undefined,
|
||||
value: {
|
||||
source: 'fixed',
|
||||
value: undefined
|
||||
},
|
||||
termType: undefined,
|
||||
key: 'params_1',
|
||||
type: 'and',
|
||||
},
|
||||
],
|
||||
column: undefined,
|
||||
value: {
|
||||
source: 'fixed',
|
||||
value: undefined
|
||||
},
|
||||
termType: undefined,
|
||||
key: 'params_1',
|
||||
type: 'and',
|
||||
key: 'terms_1_terms_1',
|
||||
},
|
||||
],
|
||||
type: 'and',
|
||||
|
|
@ -124,6 +118,7 @@ export const useSceneStore = defineStore('scene', () => {
|
|||
branches.push(null);
|
||||
}
|
||||
}
|
||||
console.log(branches)
|
||||
data.value = {
|
||||
...result,
|
||||
trigger: result.trigger || {},
|
||||
|
|
|
|||
|
|
@ -63,11 +63,12 @@ export const put = function <T>(url: string, data = {}) {
|
|||
* @param {Object} [data]
|
||||
* @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>>({
|
||||
method: 'PATCH',
|
||||
url,
|
||||
data
|
||||
data,
|
||||
...ext
|
||||
})
|
||||
}
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -72,6 +72,13 @@
|
|||
}"
|
||||
:params="params"
|
||||
>
|
||||
<template #completeTime="slotProps">
|
||||
<span>{{
|
||||
moment(slotProps.completeTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)
|
||||
}}</span>
|
||||
</template>
|
||||
<template #createTime="slotProps">
|
||||
<span>{{
|
||||
moment(slotProps.createTime).format(
|
||||
|
|
|
|||
|
|
@ -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">{{
|
||||
instanceStore.current?.id
|
||||
}}</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="产品名称">{{
|
||||
instanceStore.current?.productName
|
||||
}}</j-descriptions-item>
|
||||
<!-- <j-descriptions-item label="产品分类">{{-->
|
||||
<!-- instanceStore.current?.classifiedName-->
|
||||
<!-- }}</j-descriptions-item>-->
|
||||
<j-descriptions-item label="设备类型">{{
|
||||
instanceStore.current?.deviceType?.text
|
||||
}}</j-descriptions-item>
|
||||
|
|
@ -83,6 +107,14 @@
|
|||
@close="visible = false"
|
||||
@save="saveBtn"
|
||||
/>
|
||||
<InkingModal
|
||||
v-if='inkingVisible'
|
||||
:id='inklingDeviceId'
|
||||
:channelId='channelId'
|
||||
:accessId='instanceStore.current.accessId'
|
||||
@cancel="inkingVisible = false"
|
||||
@submit='saveInkling'
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
@ -91,10 +123,16 @@ import Save from '../../Save/index.vue';
|
|||
import Config from './components/Config/index.vue';
|
||||
import Tags from './components/Tags/index.vue';
|
||||
import Relation from './components/Relation/index.vue';
|
||||
import InkingModal from './components/InklingModal'
|
||||
import moment from 'moment';
|
||||
import { detail as queryPluginAccessDetail } from '@/api/link/accessConfig'
|
||||
import { getPluginData } from '@/api/link/plugin'
|
||||
|
||||
const visible = ref<boolean>(false);
|
||||
const inkingVisible = ref<boolean>(false);
|
||||
const instanceStore = useInstanceStore();
|
||||
const inklingDeviceId = ref()
|
||||
const channelId = ref()
|
||||
|
||||
const saveBtn = () => {
|
||||
if (instanceStore.current?.id) {
|
||||
|
|
@ -102,4 +140,37 @@ const saveBtn = () => {
|
|||
}
|
||||
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>
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
<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"
|
||||
:disabled='disabled'
|
||||
>
|
||||
<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 disabled = ref(false)
|
||||
|
||||
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 = '';
|
||||
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 || '失败';
|
||||
}
|
||||
disabled.value = false
|
||||
};
|
||||
source.onerror = (e: { status: number }) => {
|
||||
if (e.status === 403) errMessage.value = '暂无权限,请联系管理员';
|
||||
flag.value = false;
|
||||
disabled.value = false
|
||||
source.close();
|
||||
};
|
||||
source.onopen = () => {};
|
||||
} else {
|
||||
message.error('请先上传文件');
|
||||
}
|
||||
};
|
||||
|
||||
const uploadChange = async (info: Record<string, any>) => {
|
||||
disabled.value = true
|
||||
console.log(info.file)
|
||||
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>
|
||||
<j-modal
|
||||
:maskClosable="false"
|
||||
:visible="true"
|
||||
:visible="visible"
|
||||
width="800px"
|
||||
title="导入"
|
||||
title="批量导入"
|
||||
@cancel='cancel'
|
||||
>
|
||||
<div>
|
||||
<!-- 选择产品 -->
|
||||
<div v-if='steps === 0'>
|
||||
<Product
|
||||
v-model:rowKey='importData.productId'
|
||||
@change='productChange'
|
||||
/>
|
||||
</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>
|
||||
<template #footer>
|
||||
<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>
|
||||
</template>
|
||||
</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>
|
||||
|
||||
<script lang='ts' setup name='DeviceImport'>
|
||||
import Product from './product.vue'
|
||||
import { onlyMessage } from '@/utils/comm'
|
||||
|
||||
import { queryList } from '@/api/device/product';
|
||||
import { getImage, onlyMessage } from '@/utils/comm'
|
||||
import File from './file.vue'
|
||||
import Plugin from './plugin.vue'
|
||||
import { importDeviceByPlugin } from '@/api/device/instance'
|
||||
|
||||
const emit = defineEmits(['cancel', 'save']);
|
||||
|
||||
const steps = ref(0) // 步骤
|
||||
|
||||
const importData = reactive({
|
||||
const importData = reactive<{productId?: string, type?: string}>({
|
||||
productId: 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 = () => {
|
||||
if (steps.value === 0 && !importData.productId) {
|
||||
return onlyMessage('请选择产品', 'error')
|
||||
if (steps.value === 0) {
|
||||
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) {
|
||||
return onlyMessage('请选择导入方式', 'error')
|
||||
|
|
@ -48,7 +123,7 @@ const next = () => {
|
|||
}
|
||||
|
||||
const prev = () => {
|
||||
if (steps.value === 2 && importData.type) {
|
||||
if (productDetail.value?.accessProvider !== 'plugin_gateway') {
|
||||
steps.value = 0
|
||||
} else {
|
||||
steps.value -= 1
|
||||
|
|
@ -59,7 +134,33 @@ const cancel = () => {
|
|||
emit('cancel')
|
||||
}
|
||||
|
||||
const pluginChange = (options: any[]) => {
|
||||
deviceList.value = options
|
||||
}
|
||||
|
||||
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')
|
||||
}
|
||||
</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"
|
||||
type='simple'
|
||||
@search="handleSearch"
|
||||
class="scene-search"
|
||||
class="device-import-product"
|
||||
target="device-import-product"
|
||||
/>
|
||||
<j-divider style='margin: 0' />
|
||||
<j-pro-table
|
||||
model='CARD'
|
||||
:columns='columns'
|
||||
:params='params'
|
||||
:request='productQuery'
|
||||
:gridColumn='2'
|
||||
:gridColumns='[2,2,2]'
|
||||
:bodyStyle='{
|
||||
paddingRight: 0,
|
||||
paddingLeft: 0
|
||||
}'
|
||||
>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value='slotProps'
|
||||
:active="rowKey === slotProps.id"
|
||||
:status="slotProps.state"
|
||||
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
|
||||
:statusNames="{ 1: 'processing', 0: 'error', }"
|
||||
@click="handleClick"
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img width='80' height='80' :src="slotProps.photoUrl || getImage('/device-product.png')" />
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<div style='width: calc(100% - 100px)'>
|
||||
<Ellipsis>
|
||||
<span style="font-size: 16px;font-weight: 600" >
|
||||
{{ 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 :height='400'>
|
||||
<j-pro-table
|
||||
model='CARD'
|
||||
:columns='columns'
|
||||
:params='params'
|
||||
:request='queryProductList'
|
||||
:gridColumn='2'
|
||||
:defaultParams="{
|
||||
terms: [
|
||||
{
|
||||
column: 'state',
|
||||
value: '1',
|
||||
type: 'and'
|
||||
},
|
||||
{
|
||||
column: 'accessProvider',
|
||||
value: props?.type
|
||||
}
|
||||
],
|
||||
sorts: [{ name: 'createTime', order: 'desc' }]
|
||||
}"
|
||||
:gridColumns='[2,2,2]'
|
||||
:bodyStyle='{
|
||||
paddingRight: 0,
|
||||
paddingLeft: 0
|
||||
}'
|
||||
>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value='slotProps'
|
||||
:active="rowKey === slotProps.id"
|
||||
:status="slotProps.state"
|
||||
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
|
||||
:statusNames="{ 1: 'processing', 0: 'error', }"
|
||||
@click="handleClick"
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img width='80' height='80' :src="slotProps.photoUrl || getImage('/device-product.png')" />
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<div style='width: calc(100% - 100px)'>
|
||||
<Ellipsis>
|
||||
<span style="font-size: 16px;font-weight: 600" >
|
||||
{{ 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>
|
||||
|
||||
<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: '设备类型',
|
||||
dataIndex: 'deviceType',
|
||||
width: 150,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '直连设备', value: 'device' },
|
||||
{ label: '网关子设备', value: 'childrenDevice' },
|
||||
{ label: '网关设备', value: 'gateway' },
|
||||
]
|
||||
}
|
||||
// search: {
|
||||
// type: 'select',
|
||||
// options: [
|
||||
// { label: '直连设备', value: 'device' },
|
||||
// { label: '网关子设备', value: 'childrenDevice' },
|
||||
// { label: '网关设备', value: 'gateway' },
|
||||
// ]
|
||||
// }
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
|
|
@ -156,88 +158,16 @@ const columns = [
|
|||
dataIndex: 'describe',
|
||||
ellipsis: true,
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
dataIndex: 'classifiedId',
|
||||
title: '分类',
|
||||
hideInTable: true,
|
||||
search: {
|
||||
type: 'treeSelect',
|
||||
options: () => {
|
||||
return new Promise((res => {
|
||||
queryTree({ paging: false }).then(resp => {
|
||||
res(resp.result)
|
||||
})
|
||||
}))
|
||||
},
|
||||
componentProps: {
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
}
|
||||
}
|
||||
type: 'string',
|
||||
}
|
||||
},
|
||||
{
|
||||
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) => {
|
||||
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) => {
|
||||
emit('update:rowKey', detail.id)
|
||||
emit('change', detail)
|
||||
|
|
@ -246,7 +176,7 @@ const handleClick = (detail: any) => {
|
|||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
.search {
|
||||
:deep(.device-import-product) {
|
||||
margin-bottom: 0;
|
||||
padding-right: 0px;
|
||||
padding-left: 0px;
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@
|
|||
</page-container>
|
||||
<Import
|
||||
v-if="importVisible"
|
||||
@close="importVisible = false"
|
||||
@cancel="importVisible = false"
|
||||
@save="onRefresh"
|
||||
/>
|
||||
<Export
|
||||
|
|
@ -308,7 +308,7 @@ import {
|
|||
} from '@/api/device/instance';
|
||||
import { getImage, LocalStore } from '@/utils/comm';
|
||||
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 Process from './Process/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 { message } from 'jetlinks-ui-components'
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
import { getProductByPluginId } from '@/api/link/plugin'
|
||||
|
||||
type Emit = {
|
||||
(e: 'submit', data: any): void
|
||||
|
|
@ -255,16 +256,25 @@ const findProvidersByProvider = (provider: string) => {
|
|||
*/
|
||||
const submitData = async () => {
|
||||
if (selectedRowKeys.value.length) {
|
||||
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)
|
||||
if (checkData.value.channel === 'plugin') {
|
||||
const resp = await getProductByPluginId(checkData.value.channelId).catch(() => ({ success: false, result: []}))
|
||||
|
||||
emit('submit', {
|
||||
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 {
|
||||
message.error('请选择接入方式');
|
||||
|
|
|
|||
|
|
@ -73,6 +73,19 @@
|
|||
</div>
|
||||
<div v-else>{{ '暂无连接信息' }}</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
|
||||
v-if="metadata?.name"
|
||||
:data="metadata?.name"
|
||||
|
|
@ -160,6 +173,7 @@
|
|||
type="primary"
|
||||
@click="submitDevice"
|
||||
hasPermission="device/Instance:update"
|
||||
:loading='submitLoading'
|
||||
>保存</PermissionButton
|
||||
>
|
||||
</j-col>
|
||||
|
|
@ -246,6 +260,15 @@
|
|||
@cancel=' visible = false'
|
||||
@submit='checkAccess'
|
||||
/>
|
||||
<!-- 物模型处理方式 -->
|
||||
<MetaDataModal
|
||||
v-if='metadataVisible'
|
||||
:metadata='productData.metadata'
|
||||
:access='access'
|
||||
:data='metadataModalCacheData'
|
||||
@cancel=' () => { metadataVisible = false, metadataModalCacheData = {}}'
|
||||
@submit='MetaDataModalSubmit'
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name='AccessConfig'>
|
||||
|
|
@ -257,20 +280,20 @@ import { usePermissionStore } from '@/store/permission';
|
|||
import { steps, steps1 } from './util';
|
||||
import './index.less';
|
||||
import {
|
||||
getProviders,
|
||||
_deploy,
|
||||
_undeploy,
|
||||
queryList,
|
||||
getConfigView,
|
||||
getConfigMetadata,
|
||||
productGuide,
|
||||
productGuideSave,
|
||||
getStoragList,
|
||||
saveDevice,
|
||||
updateDevice,
|
||||
detail,
|
||||
modify,
|
||||
} from '@/api/device/product';
|
||||
getProviders,
|
||||
_deploy,
|
||||
_undeploy,
|
||||
queryList,
|
||||
getConfigView,
|
||||
getConfigMetadata,
|
||||
productGuide,
|
||||
productGuideSave,
|
||||
getStoragList,
|
||||
saveDevice,
|
||||
updateDevice,
|
||||
detail,
|
||||
modify, getAccessConfig
|
||||
} from '@/api/device/product'
|
||||
|
||||
import Driver from 'driver.js';
|
||||
import 'driver.js/dist/driver.min.css';
|
||||
|
|
@ -280,6 +303,9 @@ import { useMenuStore } from '@/store/menu';
|
|||
import _ from 'lodash';
|
||||
import { accessConfigTypeFilter } from '@/utils/setting';
|
||||
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 tableRef = ref();
|
||||
|
|
@ -319,6 +345,17 @@ const form = reactive<Record<string, any>>({
|
|||
const formData = reactive<Record<string, any>>({
|
||||
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
|
||||
accessId.value = data.access.id
|
||||
access.value = data.access
|
||||
metadata.value = data.metadata[0]
|
||||
config.value = data.access?.transportDetail || {}
|
||||
handleColumns()
|
||||
markdownToHtml.value = config.value?.document ? marked(config.value.document) : '';
|
||||
getGuide(!!data.metadata.length); //
|
||||
productTypes.value = []
|
||||
productData.id = undefined
|
||||
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) {
|
||||
// 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(
|
||||
productStore.current?.messageProtocol || '',
|
||||
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) => {
|
||||
if (resp.status === 200) {
|
||||
storageList.value = resp.result;
|
||||
|
|
@ -648,47 +713,80 @@ const getData = async (accessId?: string) => {
|
|||
* 保存设备接入
|
||||
*/
|
||||
const submitDevice = async () => {
|
||||
const res = await formRef.value.validate();
|
||||
const values = { storePolicy: form.storePolicy, ...formData.data };
|
||||
const result: any = {};
|
||||
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,
|
||||
if (pluginFormRef.value) { // 插件
|
||||
const pluginRef = await pluginFormRef.value.validate();
|
||||
if (!pluginRef) return
|
||||
}
|
||||
const updateDeviceResp = await updateDevice(accessObj)
|
||||
|
||||
if (!updateDeviceResp.success) return
|
||||
|
||||
// 更新产品配置信息
|
||||
const resp = await modify(id || '', {
|
||||
id: id,
|
||||
configuration: { ...extra },
|
||||
storePolicy: storePolicy,
|
||||
});
|
||||
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 res = await formRef.value.validate();
|
||||
if (!res) return
|
||||
const values = { storePolicy: form.storePolicy, ...formData.data };
|
||||
const id = productStore.current?.id;
|
||||
// 该产品是否有物模型,有则弹窗进行处理
|
||||
const _metadata = JSON.parse(productStore.current?.metadata || '{}')
|
||||
if (_metadata.properties?.length || _metadata.events?.length || _metadata.functions?.length || _metadata.tags?.length) {
|
||||
metadataModalCacheData.value = {
|
||||
id,
|
||||
values,
|
||||
productTypeId: productData.id
|
||||
}
|
||||
metadataVisible.value = true
|
||||
} else {
|
||||
updateAccessData(id, values)
|
||||
}
|
||||
};
|
||||
|
||||
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) => {
|
||||
Object.keys(obj).forEach((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()
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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,233 @@
|
|||
<template>
|
||||
<div class='metadata-map'>
|
||||
<div class='left'>
|
||||
<j-input-search
|
||||
style='width: 350px;margin-bottom:24px;'
|
||||
placeholder='搜索平台属性名称'
|
||||
allowClear
|
||||
@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, index }">
|
||||
<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 dataSourceCache = ref([])
|
||||
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',
|
||||
sorter: 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 search = (value: string) => {
|
||||
console.log(value)
|
||||
if (value) {
|
||||
dataSource.value = dataSourceCache.value.filter((item: any) => {
|
||||
return !!item.name?.includes(value)
|
||||
})
|
||||
} else {
|
||||
dataSource.value = dataSourceCache.value
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
dataSourceCache.value = dataSource.value
|
||||
}
|
||||
|
||||
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 Metadata from '../../../device/components/Metadata/index.vue';
|
||||
import DataAnalysis from './DataAnalysis/index.vue';
|
||||
import MetadataMap from './MetadataMap'
|
||||
// import Metadata from '../../../components/Metadata/index.vue';
|
||||
import {
|
||||
_deploy,
|
||||
|
|
@ -163,6 +164,7 @@ const tabs = {
|
|||
Metadata,
|
||||
Device,
|
||||
DataAnalysis,
|
||||
MetadataMap
|
||||
};
|
||||
|
||||
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>
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
<j-input-search v-model:value="searchValue" placeholder="请输入名称" @search="handleSearch" allowClear></j-input-search>
|
||||
</div>
|
||||
<div>
|
||||
<PermissionButton type="primary" :uhas-permission="`${permission}:update`" key="add" @click="handleAddClick"
|
||||
<PermissionButton type="primary" :hasPermission="`${permission}:update`" key="add" @click="handleAddClick"
|
||||
:disabled="operateLimits('add', type)" :tooltip="{
|
||||
title: operateLimits('add', type) ? '当前的存储方式不支持新增' : '新增',
|
||||
}">
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<j-tooltip :title="instanceStore.detail?.independentMetadata && type === 'device'
|
||||
? '该设备已脱离产品物模型,修改产品物模型对该设备无影响'
|
||||
: '设备会默认继承产品的物模型,修改设备物模型后将脱离产品物模型'">
|
||||
<div class="ellipsis">
|
||||
<div class="ellipsis" style='color: #999;'>
|
||||
<AIcon type="InfoCircleOutlined" style="margin-right: 3px" />
|
||||
{{
|
||||
instanceStore.detail?.independentMetadata && type === 'device'
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<div class="box-item">
|
||||
<div class="label">设备数量</div>
|
||||
<div class="value">{{ deviceNum }}</div>
|
||||
<img src="/images/home/Group3793.png" alt="" />
|
||||
<img src="/images/home/top-1.png" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -469,7 +469,7 @@ export default {
|
|||
id: '1-4-9',
|
||||
parentId: '1-4',
|
||||
path: 'T4zX-A0TC-BFum',
|
||||
sortIndex: 9999,
|
||||
sortIndex: 9998,
|
||||
level: 1,
|
||||
name: '远程升级',
|
||||
code: 'device/Firmware',
|
||||
|
|
@ -493,6 +493,34 @@ export default {
|
|||
accessDescription: '此菜单不支持数据权限控制',
|
||||
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]: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1436,6 +1436,63 @@ export default [
|
|||
supportDataAccess: false,
|
||||
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 resp: any = await getInit();
|
||||
if (resp.status === 200 && resp.result.length) {
|
||||
window.location.href = '/';
|
||||
// window.location.href = '/';
|
||||
}
|
||||
};
|
||||
onBeforeMount(() => {
|
||||
// judgeInitSet();
|
||||
judgeInitSet();
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
|
|
|
|||
|
|
@ -287,14 +287,8 @@
|
|||
</j-row>
|
||||
</div>
|
||||
<div :class="current !== 2 ? 'steps-action' : 'steps-action-save'">
|
||||
<j-button
|
||||
v-if="[0, 1].includes(current)"
|
||||
type="primary"
|
||||
style="margin-right: 8px"
|
||||
@click="next"
|
||||
>
|
||||
下一步
|
||||
</j-button>
|
||||
|
||||
<j-button v-if="current > 0" @click="prev" style="margin-right: 8px"> 上一步 </j-button>
|
||||
<PermissionButton
|
||||
v-if="current === 2 && view === 'false'"
|
||||
type="primary"
|
||||
|
|
@ -306,7 +300,14 @@
|
|||
>
|
||||
保存
|
||||
</PermissionButton>
|
||||
<j-button v-if="current > 0" @click="prev"> 上一步 </j-button>
|
||||
<j-button
|
||||
v-if="[0, 1].includes(current)"
|
||||
type="primary"
|
||||
|
||||
@click="next"
|
||||
>
|
||||
下一步
|
||||
</j-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -372,14 +372,7 @@
|
|||
</j-row>
|
||||
</div>
|
||||
<div :class="current !== 2 ? 'steps-action' : 'steps-action-save'">
|
||||
<j-button
|
||||
v-if="[0, 1].includes(current)"
|
||||
type="primary"
|
||||
style="margin-right: 8px"
|
||||
@click="next"
|
||||
>
|
||||
下一步
|
||||
</j-button>
|
||||
<j-button v-if="current > 0" @click="prev" style="margin-right: 8px"> 上一步 </j-button>
|
||||
<PermissionButton
|
||||
style="margin-right: 8px"
|
||||
v-if="current === 2 && view === 'false'"
|
||||
|
|
@ -391,7 +384,15 @@
|
|||
>
|
||||
保存
|
||||
</PermissionButton>
|
||||
<j-button v-if="current > 0" @click="prev"> 上一步 </j-button>
|
||||
<j-button
|
||||
v-if="[0, 1].includes(current)"
|
||||
type="primary"
|
||||
|
||||
@click="next"
|
||||
>
|
||||
下一步
|
||||
</j-button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -178,13 +178,7 @@
|
|||
v-if="channel !== 'edge-child-device'"
|
||||
:class="current !== 1 ? 'steps-action' : 'steps-action-save'"
|
||||
>
|
||||
<j-button
|
||||
v-if="[0].includes(current)"
|
||||
style="margin-right: 8px"
|
||||
@click="next"
|
||||
>
|
||||
下一步
|
||||
</j-button>
|
||||
<j-button v-if="current > 0" @click="prev" style="margin-right: 8px"> 上一步 </j-button>
|
||||
<PermissionButton
|
||||
v-if="current === 1 && view === 'false'"
|
||||
type="primary"
|
||||
|
|
@ -196,7 +190,13 @@
|
|||
>
|
||||
保存
|
||||
</PermissionButton>
|
||||
<j-button v-if="current > 0" @click="prev"> 上一步 </j-button>
|
||||
<j-button
|
||||
v-if="[0].includes(current)"
|
||||
|
||||
@click="next"
|
||||
>
|
||||
下一步
|
||||
</j-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -279,12 +279,11 @@
|
|||
</div>
|
||||
<div class="steps-action">
|
||||
<j-button
|
||||
v-if="[0, 1].includes(current)"
|
||||
type="primary"
|
||||
style="margin-right: 8px"
|
||||
@click="next"
|
||||
v-if="type === 'child-device' ? current > 1 : current > 0"
|
||||
style="margin-right: 8px"
|
||||
@click="prev"
|
||||
>
|
||||
下一步
|
||||
上一步
|
||||
</j-button>
|
||||
<PermissionButton
|
||||
v-if="current === 2 && view === 'false'"
|
||||
|
|
@ -299,10 +298,11 @@
|
|||
保存
|
||||
</PermissionButton>
|
||||
<j-button
|
||||
v-if="type === 'child-device' ? current > 1 : current > 0"
|
||||
@click="prev"
|
||||
v-if="[0, 1].includes(current)"
|
||||
type="primary"
|
||||
@click="next"
|
||||
>
|
||||
上一步
|
||||
下一步
|
||||
</j-button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@
|
|||
{
|
||||
max: 64,
|
||||
message: '最多可输入64个字符',
|
||||
trigger: 'blur',
|
||||
},
|
||||
]"
|
||||
name='name'
|
||||
|
|
@ -135,13 +134,13 @@
|
|||
</div>
|
||||
<div class="steps-action">
|
||||
<j-button
|
||||
v-if="current === 0"
|
||||
type="primary"
|
||||
v-if="current > 0"
|
||||
@click="prev"
|
||||
style="margin-right: 8px"
|
||||
@click="next"
|
||||
>
|
||||
下一步
|
||||
上一步
|
||||
</j-button>
|
||||
|
||||
<PermissionButton
|
||||
v-if="current === 1 && view === 'false'"
|
||||
type="primary"
|
||||
|
|
@ -155,10 +154,12 @@
|
|||
保存
|
||||
</PermissionButton>
|
||||
<j-button
|
||||
v-if="current > 0"
|
||||
@click="prev"
|
||||
v-if="current === 0"
|
||||
type="primary"
|
||||
|
||||
@click="next"
|
||||
>
|
||||
上一步
|
||||
下一步
|
||||
</j-button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -325,10 +326,10 @@ const saveData = () => {
|
|||
loading.value = true
|
||||
const resp =
|
||||
paramsId === ':id'
|
||||
? await save(params)
|
||||
: await update({ ...params, id: paramsId });
|
||||
? await save(params).catch(() => { success: false})
|
||||
: await update({ ...params, id: paramsId }).catch(() => { success: false});
|
||||
loading.value = false
|
||||
if (resp.status === 200) {
|
||||
if (resp.success) {
|
||||
onlyMessage('操作成功', 'success');
|
||||
history.back();
|
||||
if ((window as any).onTabSaveSuccess) {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
name='version'
|
||||
:rules='[{ required: true, message: "请上传文件" }]'
|
||||
>
|
||||
<UploadFile v-model:modelValue='modelRef.version' @change='uploadChange' />
|
||||
<UploadFile v-model:modelValue='modelRef.version' @change='uploadChange' :fileName='data.filename' />
|
||||
</j-form-item>
|
||||
<div v-if='modelRef.version' class='file-detail'>
|
||||
<div>
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
import { ID_Rule, Max_Length_64, Max_Length_200, RequiredStringFn } from '@/components/Form/rules'
|
||||
import UploadFile from './UploadFile.vue'
|
||||
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 { TypeMap } from './util'
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ const fileType = ref(props.data.type)
|
|||
const loading = ref(false)
|
||||
|
||||
const vailId = async (_: any, value: string) => {
|
||||
if (!!props.data.id && value) { // 新增校验
|
||||
if (!props.data.id && value) { // 新增校验
|
||||
const resp = await vailIdFn(value)
|
||||
if (resp.success && resp.result) {
|
||||
return Promise.reject('ID重复');
|
||||
|
|
@ -135,7 +135,7 @@ const handleSave = async () => {
|
|||
const data = await formRef.value.validate()
|
||||
if (data) {
|
||||
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
|
||||
if (resp.success) {
|
||||
message.success('操作成功!');
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
:before-upload="beforeUpload"
|
||||
:disabled='loading'
|
||||
:maxCount='1'
|
||||
:fileList='list'
|
||||
@remove='remove'
|
||||
>
|
||||
<div>
|
||||
<j-button>上传文件</j-button>
|
||||
|
|
@ -39,6 +41,10 @@ const props = defineProps({
|
|||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
fileName: {
|
||||
type: String,
|
||||
default: undefined
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -47,9 +53,17 @@ const paths: string = useSystem().$state.configInfo.paths?.[
|
|||
] as string;
|
||||
|
||||
const value = ref(props.modelValue);
|
||||
const list = ref<any>(props.fileName ? [{ name: props.fileName}] : [])
|
||||
const loading = ref(false);
|
||||
|
||||
const remove = () => {
|
||||
list.value = []
|
||||
emit('update:modelValue', '');
|
||||
emit('change', {});
|
||||
}
|
||||
|
||||
const beforeUpload: UploadProps['beforeUpload'] = (file, fl) => {
|
||||
list.value = fl
|
||||
const arr = file.name.split('.');
|
||||
const isFile = ['jar', 'zip'].includes(arr[arr.length - 1]); // file.type === 'application/zip' || file.type === 'application/javj-archive'
|
||||
if (!isFile) {
|
||||
|
|
@ -62,7 +76,6 @@ const handleChange = async (info: UploadChangeParam) => {
|
|||
loading.value = true;
|
||||
if (info.file.status === 'done') {
|
||||
loading.value = false;
|
||||
console.log(info.file)
|
||||
const result = info.file.response?.result;
|
||||
const f = result.accessUrl;
|
||||
onlyMessage('上传成功!', 'success');
|
||||
|
|
|
|||
|
|
@ -76,8 +76,9 @@ const props = defineProps({
|
|||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
img {
|
||||
width: 100%;
|
||||
width: 100px;
|
||||
height: 100%;
|
||||
transform: translateY(2px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
<div class="box-item">
|
||||
<div class="label">通道数量</div>
|
||||
<div class="value">{{ channelCount }}</div>
|
||||
<img :src="getImage('/home/top-2.png')" alt="" />
|
||||
<img :src="getImage('/home/product.png')" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1219,8 +1219,13 @@ const handleSubmit = () => {
|
|||
delete formData.value.template.ttsmessage;
|
||||
}
|
||||
|
||||
if (formData.value.provider === 'dingTalkRobotWebHook' && formData.value.template?.messageType === 'text') {
|
||||
formData.value.template.text!.content = formData.value.template.message as string
|
||||
if (formData.value.provider === 'dingTalkRobotWebHook') {
|
||||
if (formData.value.template?.messageType === 'text') {
|
||||
formData.value.template.text!.content = formData.value.template.message as string
|
||||
}
|
||||
if (formData.value.template.messageType === 'markdown') {
|
||||
formData.value.template.markdown!.text = formData.value.template.message
|
||||
}
|
||||
}
|
||||
|
||||
formRef.value?.validate()
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ const columns = [
|
|||
dataIndex: 'handleTime',
|
||||
key: 'handleTime',
|
||||
scopedSlots: true,
|
||||
width: 180,
|
||||
search: {
|
||||
type: 'date',
|
||||
},
|
||||
|
|
@ -74,6 +75,7 @@ const columns = [
|
|||
title: '处理类型',
|
||||
key: 'handleType',
|
||||
scopedSlots: true,
|
||||
width: 120,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
|
|
@ -96,11 +98,13 @@ const columns = [
|
|||
search: {
|
||||
type: 'date',
|
||||
},
|
||||
width:180,
|
||||
},
|
||||
{
|
||||
title: '告警处理',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ const jumpDetail = (item:any) =>{
|
|||
}
|
||||
}
|
||||
.new-alarm-item-level {
|
||||
width: 52px;
|
||||
width: 70px;
|
||||
padding: 2px 8px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
|
|
|
|||
|
|
@ -78,8 +78,8 @@ const props = defineProps({
|
|||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
}
|
||||
.content-right-echart{
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { cloneDeep, isObject } from 'lodash-es'
|
||||
import ParamsDropdown from '../../../components/ParamsDropdown';
|
||||
import { handleParamsData } from './index';
|
||||
const props = defineProps({
|
||||
|
|
@ -172,6 +172,7 @@ const onChange = () => {
|
|||
};
|
||||
|
||||
const onValueChange = (val: any, label: string) => {
|
||||
const optionColumn = isObject(val) && (val as any).metadata ? [(val as any).column] : []
|
||||
const obj = {
|
||||
[`${propertyModelRef.properties}`]: {
|
||||
value: propertyModelRef?.propertiesValue,
|
||||
|
|
@ -179,7 +180,7 @@ const onValueChange = (val: any, label: string) => {
|
|||
},
|
||||
};
|
||||
emit('update:value', obj);
|
||||
emit('change', label || val)
|
||||
emit('change', label || val, optionColumn)
|
||||
};
|
||||
|
||||
watch(
|
||||
|
|
|
|||
|
|
@ -298,7 +298,7 @@ watch(
|
|||
{ immediate: true },
|
||||
);
|
||||
|
||||
const onWriteChange = (val: string) => {
|
||||
const onWriteChange = (val: string, optionColumn: string[]) => {
|
||||
modelRef.propertiesValue = val;
|
||||
emit('change', {
|
||||
propertiesName:
|
||||
|
|
@ -306,7 +306,7 @@ const onWriteChange = (val: string) => {
|
|||
? _function.value?.name
|
||||
: _property.value?.name,
|
||||
propertiesValue: modelRef.propertiesValue,
|
||||
});
|
||||
}, optionColumn);
|
||||
};
|
||||
|
||||
const onFormSave = () => {
|
||||
|
|
|
|||
|
|
@ -102,6 +102,10 @@ const props = defineProps({
|
|||
parallel: {
|
||||
type: Boolean,
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
const current = ref<number>(0);
|
||||
|
|
@ -125,6 +129,7 @@ const DeviceModel = reactive<DeviceModelType>({
|
|||
const DeviceOptions = ref<DeviceOptionType>({});
|
||||
|
||||
const emit = defineEmits<Emit>();
|
||||
const optionColumnCache = ref<string[]>(props.options?.otherColumn || [])
|
||||
|
||||
const onCancel = () => {
|
||||
emit('cancel');
|
||||
|
|
@ -150,6 +155,7 @@ const onSave = (_data: any) => {
|
|||
selector: DeviceModel.selector, //选择器标识
|
||||
triggerName: data.value.options?.trigger?.name || '触发设备',
|
||||
...DeviceOptions.value,
|
||||
otherColumns: []
|
||||
};
|
||||
const _type = _data.message.messageType;
|
||||
if (_type === 'INVOKE_FUNCTION') {
|
||||
|
|
@ -164,8 +170,9 @@ const onSave = (_data: any) => {
|
|||
(typeof _options?.propertiesValue === 'object'
|
||||
? JSON.stringify(_options?.propertiesValue)
|
||||
: _options?.propertiesValue)
|
||||
_options.otherColumns = optionColumnCache.value
|
||||
}
|
||||
console.log(item)
|
||||
|
||||
emit('save', item, JSON.parse(JSON.stringify(_options)));
|
||||
};
|
||||
|
||||
|
|
@ -195,7 +202,8 @@ const onDeviceSave = (_data: any, obj?: any) => {
|
|||
DeviceOptions.value = { ...unref(DeviceOptions), ...obj };
|
||||
};
|
||||
|
||||
const onActionsChange = (options?: any) => {
|
||||
const onActionsChange = (options?: any, optionColumn: string[]) => {
|
||||
optionColumnCache.value = optionColumn
|
||||
const obj = {
|
||||
...DeviceOptions.value,
|
||||
...options,
|
||||
|
|
|
|||
|
|
@ -368,6 +368,7 @@
|
|||
:branchGroup="thenName"
|
||||
:branchesName="branchesName"
|
||||
:data="data"
|
||||
:options='_data.branches[branchesName].then[thenName].actions[name].options'
|
||||
@cancel="onClose"
|
||||
@save="onSave"
|
||||
/>
|
||||
|
|
@ -377,6 +378,7 @@
|
|||
v-bind="props"
|
||||
v-if="!!actionType"
|
||||
:actionType="actionType"
|
||||
:options='_data.branches[branchesName].then[thenName].actions[name].options'
|
||||
@save="onPropsOk"
|
||||
@cancel="onPropsCancel"
|
||||
/>
|
||||
|
|
@ -523,12 +525,17 @@ const onType = (_type: string) => {
|
|||
const onSave = (data: ActionsType, options: any) => {
|
||||
const { key, terms } = _data.value.branches![props.branchesName].then?.[props.thenName].actions?.[props.name]
|
||||
console.log({...props.options, ...options})
|
||||
|
||||
const columns = new Set([...(props.options?.termsColumns || []), ...(options.otherColumns.filter((item?: string) => item))])
|
||||
|
||||
const actionItem: ActionsType = {
|
||||
...data,
|
||||
options: {...props.options, ...options},
|
||||
options: {...props.options, ...options, columns: [...columns.values()]},
|
||||
key,
|
||||
terms
|
||||
}
|
||||
|
||||
console.log(actionItem)
|
||||
_data.value.branches![props.branchesName].then[props.thenName].actions.splice(props.name, 1, actionItem)
|
||||
|
||||
visible.value = false;
|
||||
|
|
|
|||
|
|
@ -82,7 +82,10 @@ const onSave = (data: any, options?: any) => {
|
|||
const item: ActionsType = {
|
||||
...extra,
|
||||
key: data.key,
|
||||
options,
|
||||
options: {
|
||||
...options,
|
||||
columns: options.otherColumns.filter((item?: string) => item)
|
||||
},
|
||||
};
|
||||
emit('add', item)
|
||||
visible.value = false
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<template v-if="actionType === 'device'">
|
||||
<Device v-bind="props" :value="data?.device" @cancel="onCancel" @save="onPropsOk" />
|
||||
<Device v-bind="props" :value="data?.device" :options='options' @cancel="onCancel" @save="onPropsOk" />
|
||||
</template>
|
||||
<template v-else-if="actionType === 'notify'">
|
||||
<Notify :options="data?.options" :value="data?.notify" @cancel="onCancel" @save="onPropsOk" />
|
||||
|
|
@ -42,6 +42,10 @@ const props = defineProps({
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['cancel', 'save']);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
v-bind="props"
|
||||
v-if="!!actionType"
|
||||
:actionType="actionType"
|
||||
:options='actionOptions'
|
||||
@save="onPropsOk"
|
||||
@cancel="onPropsCancel"
|
||||
/>
|
||||
|
|
@ -64,6 +65,10 @@ const props = defineProps({
|
|||
parallel: {
|
||||
type: Boolean,
|
||||
},
|
||||
actionOptions: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['cancel', 'save']);
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@
|
|||
<j-form-item
|
||||
:name="`${item?.id}`"
|
||||
:label="item?.name"
|
||||
v-for="item in variableDefinitions"
|
||||
v-for="(item, index) in variableDefinitions"
|
||||
:key="item.id"
|
||||
:required="getType(item) !== 'file' ? true : false"
|
||||
:rules="[
|
||||
{
|
||||
validator: (_rule, value) => checkValue(_rule, value, item),
|
||||
trigger: ['change', 'blur'],
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
|
|
@ -22,19 +22,19 @@
|
|||
:notify="notify"
|
||||
v-if="getType(item) === 'user'"
|
||||
v-model:value="modelRef[item.id]"
|
||||
@change="(val) => onChange(val, 'user')"
|
||||
@change="(val) => onChange(val, 'user', index)"
|
||||
/>
|
||||
<Org
|
||||
:notify="notify"
|
||||
v-else-if="getType(item) === 'org'"
|
||||
v-model:value="modelRef[item.id]"
|
||||
@change="(val) => onChange(val, 'org')"
|
||||
@change="(val) => onChange(val, 'org', index)"
|
||||
/>
|
||||
<Tag
|
||||
:notify="notify"
|
||||
v-else-if="getType(item) === 'tag'"
|
||||
v-model:value="modelRef[item.id]"
|
||||
@change="(val) => onChange(val, 'tag')"
|
||||
@change="(val) => onChange(val, 'tag', index)"
|
||||
/>
|
||||
<InputFile
|
||||
v-else-if="getType(item) === 'file'"
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
v-else
|
||||
:item="item"
|
||||
v-model:value="modelRef[item.id]"
|
||||
@change="(val) => onChange(val, 'build-in')"
|
||||
@change="(val, _options) => onChange(val, 'build-in', index, _options)"
|
||||
/>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
|
|
@ -70,16 +70,20 @@ const props = defineProps({
|
|||
},
|
||||
value: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
default: () => ({}),
|
||||
},
|
||||
notify: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
default: () => ({}),
|
||||
},
|
||||
template: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
default: () => ({}),
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
|
|
@ -87,15 +91,14 @@ const emit = defineEmits(['update:value', 'change']);
|
|||
const formRef = ref();
|
||||
|
||||
const modelRef = reactive({});
|
||||
const otherColumns = ref<(string | undefined)[]>(props.options?.otherColumns || [])
|
||||
|
||||
watchEffect(() => {
|
||||
Object.assign(modelRef, props?.value);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if(props?.template?.template?.sendTo && props?.template?.template?.sendTo?.length){
|
||||
emit('change', { sendTo: props?.template?.template?.sendTo.join(' ') });
|
||||
}
|
||||
emit('change', { sendTo: props?.template?.template?.sendTo?.join(' ') });
|
||||
});
|
||||
|
||||
const getType = (item: any) => {
|
||||
|
|
@ -184,13 +187,21 @@ const checkValue = (_rule: any, value: any, item: any) => {
|
|||
return Promise.resolve();
|
||||
};
|
||||
|
||||
const onChange = (val: any, type: any) => {
|
||||
const onChange = (val: any, type: any, index: number, options?: string) => {
|
||||
if (type === 'build-in') {
|
||||
otherColumns.value[index] = options
|
||||
} else {
|
||||
otherColumns.value[index] = undefined
|
||||
}
|
||||
|
||||
if (type === 'org') {
|
||||
emit('change', { orgName: val.join(',') });
|
||||
emit('change', { orgName: val.join(','), otherColumns: [] });
|
||||
} else if (type === 'tag') {
|
||||
emit('change', { tagName: val });
|
||||
emit('change', { tagName: val, otherColumns: [] });
|
||||
} else if (type === 'user') {
|
||||
emit('change', { sendTo: val });
|
||||
emit('change', { sendTo: val, otherColumns: [] });
|
||||
} else {
|
||||
emit('change', { otherColumns: otherColumns.value });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@
|
|||
:value="formModel.variables"
|
||||
:notify="formModel"
|
||||
:template="template"
|
||||
:options='options'
|
||||
@change="(val) => onValChange(val, 'variables')"
|
||||
ref="variableRef"
|
||||
/>
|
||||
|
|
@ -82,7 +83,7 @@
|
|||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
<script lang="ts" setup name='NotifyIndex'>
|
||||
import NotifyWay from './NotifyWay.vue';
|
||||
import NotifyConfig from './NotifyConfig.vue';
|
||||
import NotifyTemplate from './NotifyTemplate.vue';
|
||||
|
|
@ -99,7 +100,7 @@ const props = defineProps({
|
|||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
default: () => ({}),
|
||||
},
|
||||
name: {
|
||||
type: Number,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
placeholder="请选择参数"
|
||||
style="width: calc(100% - 120px)"
|
||||
:fieldNames="{ label: 'name', value: 'id' }"
|
||||
@change="(val, label) => itemOnChange(undefined, val, label)"
|
||||
@change="(val, label, extra) => itemOnChange(undefined, val, label, extra)"
|
||||
>
|
||||
<template #title="{ fullName, description }">
|
||||
<j-space>
|
||||
|
|
@ -57,7 +57,7 @@
|
|||
</j-input-group>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
<script lang="ts" setup name='NotifyBuildIn'>
|
||||
import { queryBuiltInParams } from '@/api/rule-engine/scene';
|
||||
import { useSceneStore } from '@/store/scene';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
|
@ -103,15 +103,22 @@ const sourceChange = (val: any) => {
|
|||
});
|
||||
};
|
||||
|
||||
const itemOnChange = (val: any, _upperKey?: string, label?: any) => {
|
||||
const itemOnChange = (val: any, _upperKey?: string, label?: any, extra?: any) => {
|
||||
const item = extra?.triggerNode?.props
|
||||
let othersColumns = ''
|
||||
if (item && item.metadata) {
|
||||
othersColumns = item.column
|
||||
}
|
||||
|
||||
emit('update:value', {
|
||||
...props.value,
|
||||
value: val,
|
||||
upperKey: _upperKey,
|
||||
});
|
||||
|
||||
emit('change', {
|
||||
sendTo: label?.[0] || val,
|
||||
});
|
||||
}, othersColumns);
|
||||
};
|
||||
|
||||
const treeDataFilter = (arr: any[], type: string) => {
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@
|
|||
v-else
|
||||
style="width: calc(100% - 120px)"
|
||||
placeholder="请选择收信人"
|
||||
@select="
|
||||
(key, node) => onChange(source, key, node?.isRelation, node?.name)
|
||||
@change="
|
||||
(key, label) => onChange(source, key, undefined, label)
|
||||
"
|
||||
:tree-data="treeData"
|
||||
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
|
|
@ -209,6 +209,7 @@ const treeData = ref<any[]>([
|
|||
]);
|
||||
const mySource = ref<string>('relation');
|
||||
const labelMap = new Map();
|
||||
const treeDataMap = new Map()
|
||||
|
||||
const getRelationUsers = async (notifyType: string, notifierId: string) => {
|
||||
let resp = undefined;
|
||||
|
|
@ -250,6 +251,7 @@ const getUser = async (_source: string, triggerType: string) => {
|
|||
}
|
||||
if (platformResp.status === 200) {
|
||||
newTree[0].children = platformResp.result.map((item: any) => {
|
||||
treeDataMap.set(item.id, item)
|
||||
return {
|
||||
...item,
|
||||
value: item.id,
|
||||
|
|
@ -265,6 +267,7 @@ const getUser = async (_source: string, triggerType: string) => {
|
|||
key: 'p2',
|
||||
selectable: false,
|
||||
children: relationResp.result.map((item: any) => {
|
||||
treeDataMap.set(item.id, item)
|
||||
return {
|
||||
...item,
|
||||
value: item.id,
|
||||
|
|
@ -324,6 +327,7 @@ const onChange = (
|
|||
_name?: string,
|
||||
) => {
|
||||
let _values: any = undefined;
|
||||
|
||||
const _names: string[] = Array.isArray(_name) ? _name : [_name || ''];
|
||||
if (Array.isArray(_value)) {
|
||||
if (props?.notify?.notifyType === 'email') {
|
||||
|
|
@ -339,7 +343,9 @@ const onChange = (
|
|||
}
|
||||
}
|
||||
} else {
|
||||
_values = getObj(_source, _value, isRelation);
|
||||
const item = treeDataMap.get(_value)
|
||||
const _isRelation = item.isRelation
|
||||
_values = getObj(_source, _value, _isRelation);
|
||||
}
|
||||
emit('update:value', _values);
|
||||
emit('change', _names.filter((item) => !!item).join(','));
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
:getPopupContainer='getPopupContainer'
|
||||
popupClassName='manual-time-picker-popup'
|
||||
@change='change'
|
||||
@ok='change'
|
||||
/>
|
||||
<j-date-picker
|
||||
v-else
|
||||
|
|
@ -21,6 +22,7 @@
|
|||
:getPopupContainer='getPopupContainer'
|
||||
popupClassName='manual-time-picker-popup'
|
||||
@change='change'
|
||||
@ok='change'
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -58,6 +60,7 @@ const getPopupContainer = (trigger: HTMLElement) => {
|
|||
}
|
||||
|
||||
const change = (e: string) => {
|
||||
console.log('Time',e)
|
||||
myValue.value = e
|
||||
emit('update:value', e)
|
||||
emit('change', e)
|
||||
|
|
|
|||
|
|
@ -163,7 +163,6 @@ watchEffect(() => {
|
|||
const option = getOption(_options, props.value as string, props.valueName) // 回显label值
|
||||
myValue.value = props.value
|
||||
mySource.value = props.source
|
||||
console.log(_options, props.value, props.valueName, option)
|
||||
if (option) {
|
||||
label.value = option[props.labelName] || option.name
|
||||
treeOpenKeys.value = openKeysByTree(_options, props.value, props.valueName)
|
||||
|
|
@ -176,6 +175,8 @@ watchEffect(() => {
|
|||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
|
|
|
|||
|
|
@ -141,19 +141,13 @@ const addWhen = () => {
|
|||
type: 'and',
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: undefined,
|
||||
value: {
|
||||
source: 'fixed',
|
||||
value: undefined
|
||||
},
|
||||
termType: undefined,
|
||||
key: `params_${randomString()}`,
|
||||
type: 'and',
|
||||
}
|
||||
],
|
||||
key: `terms_2_${randomString()}`,
|
||||
column: undefined,
|
||||
value: {
|
||||
source: 'fixed',
|
||||
value: undefined
|
||||
},
|
||||
termType: undefined,
|
||||
key: `params_${randomString()}`,
|
||||
type: 'and',
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -276,16 +276,24 @@ const columnSelect = (option: any) => {
|
|||
} else if (termTypeChange) {
|
||||
const oldValue = isArray(paramsValue.value!.value) ? paramsValue.value!.value[0] : paramsValue.value!.value
|
||||
const value = arrayParamsKey.includes(paramsValue.termType as string) ? [ oldValue, undefined ] : oldValue
|
||||
paramsValue.value = {
|
||||
source: paramsValue.value?.source || tabsOptions.value[0].key,
|
||||
|
||||
const _source = paramsValue.value?.source || tabsOptions.value[0].key
|
||||
const newValue: any = {
|
||||
source: _source,
|
||||
value: value
|
||||
}
|
||||
|
||||
if (_source === 'metric') {
|
||||
newValue.metric = paramsValue.value?.metric
|
||||
}
|
||||
|
||||
paramsValue.value = newValue
|
||||
}
|
||||
handOptionByColumn(option)
|
||||
emit('update:value', { ...paramsValue })
|
||||
formItemContext.onFieldChange()
|
||||
formModel.value.options!.when[props.branchName].terms[props.whenName].terms[props.name][0] = option.name
|
||||
formModel.value.options!.when[props.branchName].terms[props.whenName].terms[props.name][1] = paramsValue.termType
|
||||
formModel.value.options!.when[props.branchName].terms[props.whenName].terms[props.termsName][0] = option.name
|
||||
formModel.value.options!.when[props.branchName].terms[props.whenName].terms[props.termsName][1] = paramsValue.termType
|
||||
}
|
||||
|
||||
const termsTypeSelect = (e: { key: string, name: string }) => {
|
||||
|
|
@ -304,35 +312,41 @@ const termsTypeSelect = (e: { key: string, name: string }) => {
|
|||
}
|
||||
}
|
||||
|
||||
paramsValue.value = {
|
||||
source: paramsValue.value?.source || tabsOptions.value[0].key,
|
||||
const _source = paramsValue.value?.source || tabsOptions.value[0].key
|
||||
const newValue: any = {
|
||||
source: _source,
|
||||
value: value
|
||||
}
|
||||
|
||||
if (_source === 'metric') {
|
||||
newValue.metric = paramsValue.value?.metric
|
||||
}
|
||||
paramsValue.value = newValue
|
||||
emit('update:value', { ...paramsValue })
|
||||
formItemContext.onFieldChange()
|
||||
formModel.value.options!.when[props.branchName].terms[props.whenName].terms[props.name][1] = e.name
|
||||
formModel.value.options!.when[props.branchName].terms[props.whenName].terms[props.termsName][1] = e.name
|
||||
|
||||
}
|
||||
|
||||
const valueSelect = (v: any, label: string, labelObj: Record<number, any>, option: any) => {
|
||||
if (paramsValue.value?.source === 'metric') {
|
||||
paramsValue.metric = option?.id
|
||||
paramsValue.value.metric = option?.id
|
||||
}
|
||||
|
||||
const newValues = { ...paramsValue }
|
||||
|
||||
if (paramsValue.value?.source !== 'metric') {
|
||||
delete newValues.metric
|
||||
delete newValues.value.metric
|
||||
}
|
||||
|
||||
emit('update:value', { ...newValues })
|
||||
formItemContext.onFieldChange()
|
||||
formModel.value.options!.when[props.branchName].terms[props.whenName].terms[props.name][2] = labelObj
|
||||
formModel.value.options!.when[props.branchName].terms[props.whenName].terms[props.termsName][2] = labelObj
|
||||
}
|
||||
|
||||
const typeSelect = (e: any) => {
|
||||
emit('update:value', { ...paramsValue })
|
||||
formModel.value.options!.when[props.branchName].terms[props.whenName].terms[props.name][3] = e.label
|
||||
formModel.value.options!.when[props.branchName].terms[props.whenName].terms[props.termsName][3] = e.label
|
||||
}
|
||||
|
||||
const termAdd = () => {
|
||||
|
|
@ -346,17 +360,17 @@ const termAdd = () => {
|
|||
type: 'and',
|
||||
key: `params_${new Date().getTime()}`
|
||||
}
|
||||
formModel.value.branches?.[props.branchName]?.when?.[props.whenName]?.terms?.[props.termsName]?.terms?.push(terms)
|
||||
formModel.value.branches?.[props.branchName]?.when?.[props.whenName]?.terms?.push(terms)
|
||||
formModel.value.options!.when[props.branchName].terms[props.whenName].terms[props.termsName].push(['', '', '', '并且'])
|
||||
}
|
||||
|
||||
const onDelete = () => {
|
||||
formModel.value.branches?.[props.branchName]?.when?.[props.whenName]?.terms?.[props.termsName]?.terms?.splice(props.name, 1)
|
||||
formModel.value.options!.when[props.branchName].terms[props.whenName].terms.splice(props.name, 1)
|
||||
formModel.value.branches?.[props.branchName]?.when?.[props.whenName]?.terms?.splice(props.termsName, 1)
|
||||
formModel.value.options!.when[props.branchName].terms[props.whenName].terms.splice(props.termsName, 1)
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
Object.assign(paramsValue, pick(props.value, ['column', 'options', 'termType', 'terms', 'type', 'value']))
|
||||
Object.assign(paramsValue, pick(props.value, ['column', 'options', 'termType', 'terms', 'type', 'value', 'metric', 'key']))
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<TitleComponent data='触发条件' style='font-size: 14px;' >
|
||||
<template #extra>
|
||||
<j-switch
|
||||
:checked='open'
|
||||
v-model:checked='open'
|
||||
@change='change'
|
||||
checkedChildren='开'
|
||||
unCheckedChildren='关'
|
||||
|
|
@ -60,17 +60,35 @@ import Action from '../../action/index.vue'
|
|||
|
||||
const sceneStore = useSceneStore()
|
||||
const { data } = storeToRefs(sceneStore)
|
||||
const open = ref(false)
|
||||
const open = ref<boolean>(false)
|
||||
const columnOptions = ref<any>([])
|
||||
|
||||
provide(ContextKey, columnOptions)
|
||||
|
||||
const change = (e: boolean) => {
|
||||
open.value = e
|
||||
if (!e) {
|
||||
data.value.branches!.length = 1
|
||||
data.value.branches![0].when = []
|
||||
} else {
|
||||
data.value.branches!.push(null as any)
|
||||
data.value.branches![0].when = [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: undefined,
|
||||
value: {
|
||||
source: 'fixed',
|
||||
value: undefined
|
||||
},
|
||||
termType: undefined,
|
||||
key: 'params_1',
|
||||
type: 'and',
|
||||
},
|
||||
],
|
||||
type: 'and',
|
||||
key: 'terms_1',
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -119,10 +137,15 @@ watchEffect(() => {
|
|||
})
|
||||
|
||||
watchEffect(() => {
|
||||
open.value = !(
|
||||
data.value.branches &&
|
||||
data.value.branches?.length === 1
|
||||
)
|
||||
if (data.value.branches?.filter(item => item).length) {
|
||||
open.value = !!data.value.branches[0].when.length
|
||||
} else {
|
||||
open.value = true
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
console.log('terms-onMounted')
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -13,19 +13,15 @@
|
|||
<AIcon type='CloseOutlined' />
|
||||
</div>
|
||||
</j-popconfirm>
|
||||
|
||||
<j-form-item
|
||||
v-for='(item, index) in termsData'
|
||||
:key='item.key'
|
||||
:name='["branches", branchName, "when", whenName, "terms", props.name, "terms", index]'
|
||||
:name='["branches", branchName, "when", whenName, "terms", props.name]'
|
||||
:rules='rules'
|
||||
>
|
||||
<ParamsItem
|
||||
v-model:value='formModel.branches[branchName].when[whenName].terms[props.name].terms[index]'
|
||||
:isFirst='index === 0'
|
||||
:isLast='index === termsData.length - 1'
|
||||
:showDeleteBtn='termsData.length !== 1'
|
||||
:name='index'
|
||||
v-model:value='formModel.branches[branchName].when[whenName].terms[props.name]'
|
||||
:isFirst='isFirst'
|
||||
:isLast='isLast'
|
||||
:showDeleteBtn='showDeleteBtn'
|
||||
:termsName='name'
|
||||
:whenName='whenName'
|
||||
:branchName='branchName'
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
:branchName='branchName'
|
||||
:whenName='props.name'
|
||||
:name='index'
|
||||
:showDeleteBtn='showDeleteBtn'
|
||||
:showDeleteBtn='termsData.length > 1'
|
||||
:isFirst='index === 0'
|
||||
:isLast='index === termsData.length -1'
|
||||
:data='item'
|
||||
|
|
@ -93,19 +93,13 @@ const addWhen = () => {
|
|||
type: 'and',
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: undefined,
|
||||
value: {
|
||||
source: 'manual',
|
||||
value: undefined
|
||||
},
|
||||
termType: undefined,
|
||||
key: `params_${randomString()}`,
|
||||
type: 'and',
|
||||
}
|
||||
],
|
||||
key: `terms_2_${randomString()}`,
|
||||
column: undefined,
|
||||
value: {
|
||||
source: 'manual',
|
||||
value: undefined
|
||||
},
|
||||
termType: undefined,
|
||||
key: `params_${randomString()}`,
|
||||
type: 'and',
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -389,6 +389,8 @@ const handleView = (id: string, triggerType: string) => {
|
|||
.subTitle {
|
||||
position: relative;
|
||||
margin-top: 18px;
|
||||
display: inline-block;
|
||||
height: 50px;
|
||||
|
||||
.subTitle-title {
|
||||
position: absolute;
|
||||
|
|
@ -400,6 +402,7 @@ const handleView = (id: string, triggerType: string) => {
|
|||
color: rgba(0, 0, 0, 0.65);
|
||||
font-size: 14px;
|
||||
text-indent: 38px;
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -3836,10 +3836,10 @@ jetlinks-ui-components@1.0.5:
|
|||
lodash-es "^4.17.21"
|
||||
monaco-editor "^0.35.0"
|
||||
|
||||
jetlinks-ui-components@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.8.tgz#bcbdbbdc6c0011207f15cbb51fadcc8389803f53"
|
||||
integrity sha512-FdXSS4Wdnq5cCUKP5f6Z/3FHu3XHFkRIzSAvkUQdneHbYO6iHkEjMJyHChttlP9cp4s6ydRpeqY2jjtoftYhtA==
|
||||
jetlinks-ui-components@^1.0.9:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.9.tgz#c71e593e65b9e8dd16c746cebf16d45339a0d340"
|
||||
integrity sha512-NRKA20IYMvaGabJTnt180ahjL6ERJz8rDohAMtaP4bWQeSAq89hBB5s6XMRJK4VexliEEo4+V3E/edK2iNsGWg==
|
||||
dependencies:
|
||||
"@vueuse/core" "^9.12.0"
|
||||
"@vueuse/router" "^9.13.0"
|
||||
|
|
|
|||