feat: 应用管理和物模型映射2.1
This commit is contained in:
parent
299f624658
commit
c8751bb8ad
|
@ -25,7 +25,7 @@
|
|||
"event-source-polyfill": "^1.0.31",
|
||||
"global": "^4.4.0",
|
||||
"jetlinks-store": "^0.0.3",
|
||||
"jetlinks-ui-components": "^1.0.22",
|
||||
"jetlinks-ui-components": "^1.0.23",
|
||||
"js-cookie": "^3.0.1",
|
||||
"less": "^4.1.3",
|
||||
"less-loader": "^11.1.0",
|
||||
|
|
|
@ -576,14 +576,16 @@ export const getDeviceNumber = (data?:any) => server.post<number>('/device-insta
|
|||
/**
|
||||
* 导入映射设备
|
||||
* @param productId
|
||||
* @param data
|
||||
* @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 metadateMapById = (type: 'device' | 'product', productId: string, data: any[]) => server.patch(`/device/metadata/mapping/${type}/${productId}`, data)
|
||||
|
||||
export const getMetadateMapById = (productId: string) => server.get(`/device/metadata/mapping/product/${productId}`)
|
||||
export const getMetadateMapById = (type: 'device' | 'product', productId: string) => server.get(`/device/metadata/mapping/${type}/${productId}`)
|
||||
|
||||
export const getInkingDevices = (data: string[]) => server.post('/plugin/mapping/device/_all', data)
|
||||
|
||||
export const getProtocolMetadata = (id: string, transport: string) => server.get(`/protocol/${id}/${transport}/metadata`)
|
||||
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ export const changeApplyStatus_api = (id: string, data: any) => server.put(`/app
|
|||
// 删除应用
|
||||
export const delApply_api = (id: string) => server.remove(`/application/${id}`)
|
||||
|
||||
|
||||
export const queryType = () => server.get(`/application/providers`)
|
||||
|
||||
// 获取组织列表
|
||||
export const getDepartmentList_api = (params: any) => server.get(`/organization/_all/tree`, params);
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import Index from './index.vue'
|
||||
|
||||
export default Index
|
|
@ -0,0 +1,240 @@
|
|||
<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'>
|
||||
<j-ellipsis>
|
||||
{{ text }} ({{ record.id }})
|
||||
</j-ellipsis>
|
||||
</span>
|
||||
</template>
|
||||
<template v-if='column.dataIndex === "plugin"'>
|
||||
<j-select
|
||||
v-model:value='record.plugin'
|
||||
style='width: 100%'
|
||||
allowClear
|
||||
@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'>
|
||||
<j-scrollbar>
|
||||
<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>
|
||||
</j-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts' name='MetadataMap'>
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { detail as queryPluginAccessDetail } from '@/api/link/accessConfig'
|
||||
// import { getPluginData, getProductByPluginId } from '@/api/link/plugin'
|
||||
import { getImage, onlyMessage } from '@/utils/comm'
|
||||
import { getMetadateMapById, metadateMapById, getProtocolMetadata } from '@/api/device/instance'
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
|
||||
const deviceStore = useInstanceStore()
|
||||
const { current: deviceDetail } = storeToRefs(deviceStore)
|
||||
const dataSourceCache = ref([])
|
||||
const dataSource = ref([])
|
||||
const pluginOptions = ref<any[]>([])
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '平台属性',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
title: '目标属性',
|
||||
dataIndex: 'plugin',
|
||||
width: 250,
|
||||
sorter: {
|
||||
compare: (a, b) => (a.plugin?.length || 0) - (b.plugin?.length || 0)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const selectedKeys = computed(() => {
|
||||
return dataSource.value?.filter((item: any) => !!item?.plugin).map((i: any) => i.id) || []
|
||||
})
|
||||
|
||||
const selectedPluginKeys = computed(() => {
|
||||
return dataSource.value?.filter((item: any) => !!item?.plugin).map((i: any) => i.plugin) || []
|
||||
})
|
||||
|
||||
const metadata = computed(() => {
|
||||
return JSON.parse(deviceDetail.value?.metadata || '{}')
|
||||
})
|
||||
|
||||
const _id = deviceDetail.value?.id
|
||||
|
||||
const getMetadataMapData = () => {
|
||||
return new Promise(resolve => {
|
||||
getMetadateMapById('device', _id).then((res: any) => {
|
||||
if (res.success) {
|
||||
resolve(res.result?.filter((i: any) => i.customMapping)?.map((item: any) => {
|
||||
return {
|
||||
id: item.metadataId,
|
||||
pluginId: item.originalId
|
||||
}
|
||||
}) || [])
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const search = (value: string) => {
|
||||
if (value) {
|
||||
dataSource.value = dataSourceCache.value.filter((item: any) => {
|
||||
return !!item.name?.includes(value)
|
||||
})
|
||||
} else {
|
||||
dataSource.value = dataSourceCache.value
|
||||
}
|
||||
}
|
||||
|
||||
const getDefaultMetadata = async () => {
|
||||
const properties = metadata.value?.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 => {
|
||||
const transport = deviceDetail.value?.transport
|
||||
getProtocolMetadata(deviceDetail.value?.protocol || '', transport).then((res: any) => {
|
||||
if(res.success){
|
||||
resolve(JSON.parse(res?.result || '{}'))
|
||||
}
|
||||
resolve({ properties: [] })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const pluginChange = async (value: any, id: string) => {
|
||||
const res = await metadateMapById('device', _id, [{
|
||||
metadataType: 'property',
|
||||
metadataId: value.id,
|
||||
originalId: id || null
|
||||
}])
|
||||
if (res.success) {
|
||||
onlyMessage('操作成功')
|
||||
}
|
||||
}
|
||||
|
||||
getDefaultMetadata()
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang='less'>
|
||||
.metadata-map {
|
||||
// position: relative;
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
|
||||
.left {
|
||||
// margin-right: 44px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.right {
|
||||
// position: absolute;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
min-height: 100%;
|
||||
min-width: 410px;
|
||||
width: 33.33333%;
|
||||
// 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>
|
|
@ -10,7 +10,7 @@
|
|||
/>
|
||||
<j-tabs
|
||||
tab-position="left"
|
||||
style="height: 600px"
|
||||
style="height: 500px"
|
||||
v-if="tabList.length"
|
||||
v-model:activeKey="activeKey"
|
||||
:tabBarStyle="{ width: '200px' }"
|
||||
|
@ -120,7 +120,10 @@ const tabChange = (key: string) => {
|
|||
min-height: 100%;
|
||||
.property-box-left {
|
||||
width: 200px;
|
||||
min-height: 100%;
|
||||
|
||||
// :deep(.jet-tabs) {
|
||||
// height: 100%;
|
||||
// }
|
||||
}
|
||||
.property-box-right {
|
||||
flex: 1;
|
||||
|
|
|
@ -114,6 +114,7 @@ import { useInstanceStore } from '@/store/instance';
|
|||
import Info from './Info/index.vue';
|
||||
import Running from './Running/index.vue';
|
||||
import Metadata from '../../components/Metadata/index.vue';
|
||||
import MetadataMap from './MetadataMap/index.vue';
|
||||
import ChildDevice from './ChildDevice/index.vue';
|
||||
import Diagnose from './Diagnose/index.vue';
|
||||
import Function from './Function/index.vue';
|
||||
|
@ -179,6 +180,7 @@ const tabs = {
|
|||
EdgeMap,
|
||||
Parsing,
|
||||
Log,
|
||||
MetadataMap
|
||||
};
|
||||
|
||||
const getStatus = (id: string) => {
|
||||
|
@ -254,6 +256,15 @@ const getDetail = () => {
|
|||
tab: '边缘端映射',
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
instanceStore.current?.features?.find(
|
||||
(item: any) => item?.id === 'diffMetadataSameProduct',
|
||||
) &&
|
||||
!keys.includes('MetadataMap')
|
||||
) {
|
||||
list.value.push({ key: 'MetadataMap', tab: '物模型映射'});
|
||||
}
|
||||
};
|
||||
|
||||
const initPage = async (newId: any) => {
|
||||
|
|
|
@ -118,7 +118,7 @@ const selectedPluginKeys = computed(() => {
|
|||
|
||||
const getMetadataMapData = () => {
|
||||
return new Promise(resolve => {
|
||||
getMetadateMapById(productDetail.value?.id).then(res => {
|
||||
getMetadateMapById('product', productDetail.value?.id).then(res => {
|
||||
if (res.success) {
|
||||
resolve(res.result?.filter(item => item.customMapping)?.map(item => {
|
||||
return {
|
||||
|
@ -168,7 +168,7 @@ const getPluginMetadata = (): Promise<{ properties: any[]}> => {
|
|||
queryPluginAccessDetail(productDetail.value?.accessId!).then(async res => {
|
||||
if (res.success) {
|
||||
const _channelId = (res.result as any)!.channelId
|
||||
const pluginRes = await getPluginData('product', productDetail.value?.accessId, productDetail.value?.id).catch(() => ({ success: false, result: {}}))
|
||||
const pluginRes = await getPluginData('product', productDetail.value?.accessId || '', 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)
|
||||
|
@ -182,7 +182,7 @@ const getPluginMetadata = (): Promise<{ properties: any[]}> => {
|
|||
}
|
||||
|
||||
const pluginChange = async (value: any, id: string) => {
|
||||
const res = await metadateMapById(productDetail.value?.id, [{
|
||||
const res = await metadateMapById('product', productDetail.value?.id, [{
|
||||
metadataType: 'property',
|
||||
metadataId: value.id,
|
||||
originalId: id
|
||||
|
|
|
@ -5,20 +5,21 @@
|
|||
width="700px"
|
||||
@ok="confirm"
|
||||
@cancel="emits('update:visible', false)"
|
||||
:filter-option="filterOption"
|
||||
:maskClosable="false"
|
||||
class="access-method-dialog-container"
|
||||
>
|
||||
<j-form :model="form" name="basic" autocomplete="off" layout="vertical">
|
||||
<j-form :model="form" ref="formRef" layout="vertical">
|
||||
<j-form-item
|
||||
label="产品"
|
||||
name="productId"
|
||||
:rules="[{ required: true, message: '该字段是必填字段' }]"
|
||||
:rules="[{ required: true, message: '请选择产品' }]"
|
||||
>
|
||||
<j-select
|
||||
v-model:value="form.productId"
|
||||
style="width: 100%"
|
||||
show-search
|
||||
:options="productList"
|
||||
placeholder="请选择产品"
|
||||
>
|
||||
</j-select>
|
||||
</j-form-item>
|
||||
|
@ -35,13 +36,10 @@ const props = defineProps<{
|
|||
visible: boolean;
|
||||
}>();
|
||||
|
||||
const confirm = () => {
|
||||
emits('confirm', form.value.productId);
|
||||
emits('update:visible', false);
|
||||
};
|
||||
const formRef = ref<any>()
|
||||
|
||||
const form = ref({
|
||||
productId: '',
|
||||
const form = reactive({
|
||||
productId: undefined,
|
||||
});
|
||||
|
||||
const productList = ref<[productItem] | []>([]);
|
||||
|
@ -56,10 +54,14 @@ const getOptions = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
getOptions();
|
||||
|
||||
const confirm = () => {
|
||||
formRef.value?.validate().then(() => {
|
||||
emits('confirm', form.productId);
|
||||
emits('update:visible', false);
|
||||
})
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -41,7 +41,17 @@
|
|||
<j-form-item name="basePath" v-bind="validateInfos.basePath">
|
||||
<template #label>
|
||||
<span>base-path</span>
|
||||
<j-tooltip title="系统后台访问的url">
|
||||
<j-tooltip>
|
||||
<template #title>
|
||||
<div style='word-break: break-all;'>
|
||||
<div>
|
||||
系统后台访问的url。
|
||||
</div>
|
||||
<div>
|
||||
格式:{http/https}: //{前端所在服务器IP地址}:{前端暴露的服务端口}/api
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<img
|
||||
class="img-style"
|
||||
:src="getImage('/init-home/mark.png')"
|
||||
|
@ -50,7 +60,7 @@
|
|||
</template>
|
||||
<j-input
|
||||
v-model:value="form.basePath"
|
||||
placeholder="请输入base-path"
|
||||
placeholder="格式:{http/https}: //{前端所在服务器IP地址}:{前端暴露的服务端口}/api"
|
||||
/>
|
||||
</j-form-item>
|
||||
<j-row :gutter="24" :span="24">
|
||||
|
@ -253,13 +263,12 @@
|
|||
</template>
|
||||
<script setup lang="ts">
|
||||
import { modalState, formState, logoState } from '../data/interface';
|
||||
import { getImage } from '@/utils/comm.ts';
|
||||
import { Form, message } from 'jetlinks-ui-components';
|
||||
import { FILE_UPLOAD } from '@/api/comm';
|
||||
import {
|
||||
save,
|
||||
} from '@/api/initHome';
|
||||
import { LocalStore } from '@/utils/comm';
|
||||
import { LocalStore, getImage } from '@/utils/comm';
|
||||
import { TOKEN_KEY } from '@/utils/variable';
|
||||
import { SystemConst } from '@/utils/consts';
|
||||
const formRef = ref();
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
@change="onChange"
|
||||
>
|
||||
<j-radio-button
|
||||
v-for="item in list"
|
||||
v-for="item in options"
|
||||
:value="item.value"
|
||||
:key="item.value"
|
||||
>
|
||||
<div class="radio-container-item" @click.stop>
|
||||
<div>
|
||||
<MUpload
|
||||
:defaultValue="item.imgUrl"
|
||||
:defaultValue="defaultImg[item.value]"
|
||||
:borderStyle="{
|
||||
width: '90px',
|
||||
height: '90px',
|
||||
|
@ -25,7 +25,7 @@
|
|||
@change="(_url) => onImgChange(_url, item.value)"
|
||||
/>
|
||||
</div>
|
||||
<span>{{ item.text }}</span>
|
||||
<span>{{ item.label }}</span>
|
||||
</div>
|
||||
</j-radio-button>
|
||||
</j-radio-group>
|
||||
|
@ -33,6 +33,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { PropType } from 'vue';
|
||||
import MUpload from './MUpload.vue';
|
||||
|
||||
const props = defineProps({
|
||||
|
@ -48,50 +49,24 @@ const props = defineProps({
|
|||
type: String,
|
||||
default: getImage('/apply/provider1.png'),
|
||||
},
|
||||
options: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:value', 'update:photoUrl']);
|
||||
const list = [
|
||||
{
|
||||
value: 'internal-standalone',
|
||||
text: '内部独立应用',
|
||||
imgUrl: getImage('/apply/provider1.png'),
|
||||
},
|
||||
{
|
||||
value: 'internal-integrated',
|
||||
text: '内部集成应用',
|
||||
imgUrl: getImage('/apply/provider2.png'),
|
||||
},
|
||||
{
|
||||
value: 'wechat-webapp',
|
||||
text: '微信网站应用',
|
||||
imgUrl: getImage('/apply/provider4.png'),
|
||||
},
|
||||
{
|
||||
value: 'dingtalk-ent-app',
|
||||
text: '钉钉企业内部应用',
|
||||
imgUrl: getImage('/apply/provider3.png'),
|
||||
},
|
||||
{
|
||||
value: 'third-party',
|
||||
text: '第三方应用',
|
||||
imgUrl: getImage('/apply/provider5.png'),
|
||||
},
|
||||
{
|
||||
value: 'wechat-miniapp',
|
||||
text: '小程序应用',
|
||||
imgUrl: getImage('/apply/provider1.png'),
|
||||
},
|
||||
];
|
||||
|
||||
const urlValue = ref({
|
||||
const defaultImg = {
|
||||
'internal-standalone': getImage('/apply/provider1.png'),
|
||||
'internal-integrated': getImage('/apply/provider2.png'),
|
||||
'wechat-webapp': getImage('/apply/provider4.png'),
|
||||
'dingtalk-ent-app': getImage('/apply/provider3.png'),
|
||||
'third-party': getImage('/apply/provider5.png'),
|
||||
'wechat-miniapp': getImage('/apply/provider1.png'),
|
||||
});
|
||||
}
|
||||
|
||||
const urlValue = ref<any>({...defaultImg});
|
||||
const _value = ref<string>('');
|
||||
|
||||
watchEffect(() => {
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
},
|
||||
]"
|
||||
>
|
||||
<ApplyList v-model:photoUrl="form.data.logoUrl" v-model:value="form.data.provider" :disabled="!!routeQuery.id" />
|
||||
<ApplyList :options="typeOptions" v-model:photoUrl="form.data.logoUrl" v-model:value="form.data.provider" :disabled="!!routeQuery.id" />
|
||||
</j-form-item>
|
||||
<j-form-item
|
||||
label="接入方式"
|
||||
|
@ -1380,17 +1380,15 @@
|
|||
<MenuDialog
|
||||
v-if="dialog.visible"
|
||||
v-model:visible="dialog.visible"
|
||||
:id="dialog.selectId"
|
||||
:provider="dialog.selectProvider"
|
||||
:data="dialog.current"
|
||||
:mode="routeQuery.id ? 'edit' : 'add'"
|
||||
@refresh="menuStory.jumpPage('system/Apply')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
|
||||
import { LocalStore, filterSelectNode, onlyMessage } from '@/utils/comm'
|
||||
import { testIP } from '@/utils/validate';
|
||||
|
||||
import {
|
||||
|
@ -1398,6 +1396,7 @@ import {
|
|||
addApp_api,
|
||||
updateApp_api,
|
||||
getAppInfo_api,
|
||||
queryType,
|
||||
} from '@/api/system/apply';
|
||||
import FormLabel from './FormLabel.vue';
|
||||
import RequestTable from './RequestTable.vue';
|
||||
|
@ -1405,11 +1404,6 @@ import MenuDialog from '../../componenets/MenuDialog.vue';
|
|||
import { getImage } from '@/utils/comm';
|
||||
import type { formType, dictType, optionsType } from '../typing';
|
||||
import { getRoleList_api } from '@/api/system/user';
|
||||
import {
|
||||
FormInstance,
|
||||
UploadChangeParam,
|
||||
UploadFile,
|
||||
} from 'ant-design-vue';
|
||||
import { message } from 'jetlinks-ui-components'
|
||||
import { randomString } from '@/utils/utils';
|
||||
import { cloneDeep, difference } from 'lodash';
|
||||
|
@ -1424,6 +1418,8 @@ const menuStory = useMenuStore();
|
|||
const deptPermission = 'system/Department';
|
||||
const rolePermission = 'system/Role';
|
||||
|
||||
const typeOptions = ref<any[]>([])
|
||||
|
||||
// 初始化表单
|
||||
const initForm: formType = {
|
||||
name: '',
|
||||
|
@ -1436,6 +1432,7 @@ const initForm: formType = {
|
|||
baseUrl: '',
|
||||
routeType: 'hash',
|
||||
parameters: [],
|
||||
configuration: {}
|
||||
},
|
||||
apiClient: {
|
||||
// API客户端
|
||||
|
@ -1509,7 +1506,7 @@ const initForm: formType = {
|
|||
defaultPasswd: '', // 默认密码
|
||||
},
|
||||
};
|
||||
const formRef = ref<FormInstance>();
|
||||
const formRef = ref<any>();
|
||||
const form = reactive({
|
||||
data: { ...initForm },
|
||||
// integrationModesISO: [] as string[], // 接入方式镜像 折叠面板使用
|
||||
|
@ -1541,77 +1538,32 @@ const paramsValidator = () => {
|
|||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
queryType().then((resp: any) => {
|
||||
if(resp.status === 200){
|
||||
const arr = resp.result.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.provider,
|
||||
integrationModes: item.integrationModes?.map((i: any) => {
|
||||
return {
|
||||
label: i.text,
|
||||
value: i.value
|
||||
}
|
||||
})
|
||||
}))
|
||||
typeOptions.value = arr
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// 接入方式的选项
|
||||
const joinOptions = computed(() => {
|
||||
if (form.data.provider === 'internal-standalone')
|
||||
return [
|
||||
{
|
||||
label: '页面集成',
|
||||
value: 'page',
|
||||
},
|
||||
{
|
||||
label: 'API客户端',
|
||||
value: 'apiClient',
|
||||
},
|
||||
{
|
||||
label: 'API服务',
|
||||
value: 'apiServer',
|
||||
},
|
||||
{
|
||||
label: '单点登录',
|
||||
value: 'ssoClient',
|
||||
},
|
||||
];
|
||||
else if (form.data.provider === 'internal-integrated')
|
||||
return [
|
||||
{
|
||||
label: '页面集成',
|
||||
value: 'page',
|
||||
},
|
||||
{
|
||||
label: 'API客户端',
|
||||
value: 'apiClient',
|
||||
},
|
||||
];
|
||||
else if (form.data.provider === 'wechat-webapp' || form.data.provider === 'wechat-miniapp')
|
||||
return [
|
||||
{
|
||||
label: '单点登录',
|
||||
value: 'ssoClient',
|
||||
},
|
||||
];
|
||||
else if (form.data.provider === 'dingtalk-ent-app')
|
||||
return [
|
||||
{
|
||||
label: '单点登录',
|
||||
value: 'ssoClient',
|
||||
},
|
||||
];
|
||||
else if (form.data.provider === 'third-party')
|
||||
return [
|
||||
{
|
||||
label: '页面集成',
|
||||
value: 'page',
|
||||
},
|
||||
{
|
||||
label: 'API客户端',
|
||||
value: 'apiClient',
|
||||
},
|
||||
{
|
||||
label: 'API服务',
|
||||
value: 'apiServer',
|
||||
},
|
||||
{
|
||||
label: '单点登录',
|
||||
value: 'ssoClient',
|
||||
},
|
||||
];
|
||||
return typeOptions.value.find(item => form.data?.provider === item.value)?.integrationModes || []
|
||||
});
|
||||
|
||||
const dialog = reactive({
|
||||
visible: false,
|
||||
selectId: '',
|
||||
selectProvider: '' as any,
|
||||
current: {}
|
||||
});
|
||||
|
||||
init();
|
||||
|
@ -1810,9 +1762,10 @@ function clickSave() {
|
|||
if (resp.status === 200) {
|
||||
const isPage = params.integrationModes.includes('page');
|
||||
if (isPage) {
|
||||
// form.data = params;
|
||||
dialog.selectId = routeQuery.id || resp.result.id;
|
||||
dialog.selectProvider = form.data.provider;
|
||||
dialog.current = {
|
||||
...params,
|
||||
id: routeQuery.id || resp.result.id
|
||||
}
|
||||
dialog.visible = true;
|
||||
} else {
|
||||
message.success('保存成功');
|
||||
|
|
|
@ -28,7 +28,8 @@ export type formType = {
|
|||
page: { // 页面集成
|
||||
baseUrl: string,
|
||||
routeType: 'hash' | 'history',
|
||||
parameters: optionsType
|
||||
parameters: optionsType,
|
||||
configuration: any
|
||||
},
|
||||
apiClient: { // API客户端
|
||||
baseUrl: string, // 接口地址
|
||||
|
|
|
@ -10,13 +10,14 @@
|
|||
>
|
||||
<j-select
|
||||
v-model:value="form.checkedSystem"
|
||||
@change="(value:string) => value && getTree(value)"
|
||||
@change="(value) => value && getTree(value)"
|
||||
style="width: 200px"
|
||||
placeholder="请选择集成系统"
|
||||
>
|
||||
<j-select-option
|
||||
v-for="item in form.systemList"
|
||||
:value="item.value"
|
||||
:key="item.value"
|
||||
>{{ item.label }}</j-select-option
|
||||
>
|
||||
</j-select>
|
||||
|
@ -29,8 +30,9 @@
|
|||
v-model:expandedKeys="form.expandedKeys"
|
||||
checkable
|
||||
:tree-data="form.menuTree"
|
||||
:fieldNames="{ key: 'id', title: 'name' }"
|
||||
:fieldNames="{ key: 'code', title: 'name' }"
|
||||
@check="treeCheck"
|
||||
:height="300"
|
||||
>
|
||||
<template #title="{ name }">
|
||||
<span>{{ name }}</span>
|
||||
|
@ -41,13 +43,13 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { optionItemType } from '@/views/system/DataSource/typing';
|
||||
import { applyType } from '../Save/typing';
|
||||
import {
|
||||
getOwner_api,
|
||||
getOwnerStandalone_api,
|
||||
getOwnerTree_api,
|
||||
getOwnerTreeStandalone_api,
|
||||
saveOwnerMenu_api,
|
||||
updateApp_api,
|
||||
} from '@/api/system/apply';
|
||||
import { CheckInfo } from 'ant-design-vue/lib/vc-tree/props';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
|
@ -55,31 +57,40 @@ import { message } from 'jetlinks-ui-components';
|
|||
import { getMenuTree_api } from '@/api/system/menu';
|
||||
|
||||
const menuStory = useMenuStore();
|
||||
const emits = defineEmits(['update:visible']);
|
||||
const emits = defineEmits(['update:visible', 'refresh']);
|
||||
const props = defineProps<{
|
||||
mode: 'add' | 'edit';
|
||||
visible: boolean;
|
||||
id: string;
|
||||
provider: applyType;
|
||||
data: any;
|
||||
}>();
|
||||
// 弹窗相关
|
||||
const loading = ref(false);
|
||||
const handleOk = () => {
|
||||
const handleOk = async () => {
|
||||
const items = filterTree(form.menuTree, [
|
||||
...form.checkedMenu,
|
||||
...form.half,
|
||||
]);
|
||||
console.log(items)
|
||||
if (form.checkedSystem) {
|
||||
if (items && items.length !== 0) {
|
||||
loading.value = true;
|
||||
saveOwnerMenu_api('iot', form.id, items)
|
||||
.then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功');
|
||||
emits('update:visible', false);
|
||||
const resp = await saveOwnerMenu_api('iot', form.id, items).finally(() => (loading.value = false));
|
||||
await updateApp_api(form.id as string, {
|
||||
...props.data,
|
||||
integrationModes: props.data?.integrationModes?.map((item: any) => item?.value || item),
|
||||
page: {
|
||||
...props.data?.page,
|
||||
configuration: {
|
||||
checkedSystem: form.checkedSystem
|
||||
}
|
||||
})
|
||||
.finally(() => (loading.value = false));
|
||||
}
|
||||
})
|
||||
if (resp.status === 200) {
|
||||
// 保存集成菜单
|
||||
message.success('操作成功');
|
||||
emits('update:visible', false);
|
||||
emits('refresh')
|
||||
}
|
||||
} else {
|
||||
message.warning('请勾选配置菜单');
|
||||
}
|
||||
|
@ -94,21 +105,16 @@ const cancel = () => {
|
|||
};
|
||||
|
||||
const form = reactive({
|
||||
id: props.id,
|
||||
id: props.data?.id,
|
||||
checkedSystem: undefined as undefined | string,
|
||||
checkedMenu: [] as string[],
|
||||
expandedKeys: [] as string[],
|
||||
half: [] as string[],
|
||||
|
||||
provider: props.provider,
|
||||
provider: props.data?.provider,
|
||||
systemList: [] as optionItemType[],
|
||||
menuTree: [] as any[],
|
||||
});
|
||||
|
||||
if (props.id) {
|
||||
getSystemList();
|
||||
getMenus();
|
||||
}
|
||||
/**
|
||||
* 与集成系统关联的菜单
|
||||
* @param params
|
||||
|
@ -120,26 +126,26 @@ function getTree(params: string) {
|
|||
: getOwnerTree_api(params);
|
||||
api.then((resp: any) => {
|
||||
form.menuTree = resp.result;
|
||||
form.expandedKeys = resp.result.map((item: any) => item.id);
|
||||
form.expandedKeys = resp.result.map((item: any) => item?.code);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 获取当前用户可访问菜单
|
||||
*/
|
||||
function getMenus() {
|
||||
function getMenus(id: string) {
|
||||
const params = {
|
||||
terms: [
|
||||
{
|
||||
column: 'appId',
|
||||
value: form.id,
|
||||
value: id,
|
||||
},
|
||||
],
|
||||
};
|
||||
getMenuTree_api(params).then((resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
form.menuTree = resp.result;
|
||||
const keys = resp.result.map((item: any) => item.id) as string[];
|
||||
form.expandedKeys = keys;
|
||||
// form.menuTree = resp.result;
|
||||
const keys = resp.result.map((item: any) => item?.code) as string[];
|
||||
// form.expandedKeys = keys;
|
||||
form.checkedMenu = keys;
|
||||
}
|
||||
});
|
||||
|
@ -147,10 +153,10 @@ function getMenus() {
|
|||
/**
|
||||
* 获取集成系统选项
|
||||
*/
|
||||
function getSystemList() {
|
||||
function getSystemList(id: string) {
|
||||
const api =
|
||||
form.provider === 'internal-standalone'
|
||||
? getOwnerStandalone_api(form.id, ['iot'])
|
||||
? getOwnerStandalone_api(id, ['iot'])
|
||||
: getOwner_api(['iot']);
|
||||
|
||||
api.then((resp: any) => {
|
||||
|
@ -162,9 +168,23 @@ function getSystemList() {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
watch(() => props.data, (newVal: any) => {
|
||||
form.checkedSystem = newVal?.page.configuration?.checkedSystem
|
||||
if(form.checkedSystem){
|
||||
getTree(form.checkedSystem)
|
||||
}
|
||||
if(newVal?.id) {
|
||||
getSystemList(newVal?.id);
|
||||
getMenus(newVal?.id);
|
||||
}
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
// 树选中事件
|
||||
function treeCheck(checkedKeys: any, e: CheckInfo) {
|
||||
form.checkedMenu = checkedKeys;
|
||||
form.checkedMenu = checkedKeys
|
||||
form.half = e.halfCheckedKeys as string[];
|
||||
}
|
||||
//过滤节点-默认带上父节点
|
||||
|
@ -174,7 +194,7 @@ function filterTree(nodes: any[], list: any[]) {
|
|||
}
|
||||
return nodes.filter((it) => {
|
||||
// 不符合条件的直接砍掉
|
||||
if (list.indexOf(it.id) <= -1) {
|
||||
if (list.indexOf(it.code) <= -1) {
|
||||
return false;
|
||||
}
|
||||
// 符合条件的保留,并且需要递归处理其子节点
|
||||
|
|
|
@ -102,15 +102,18 @@
|
|||
) in item.children"
|
||||
:key="i"
|
||||
>
|
||||
<j-button
|
||||
type="link"
|
||||
@click="o.onClick"
|
||||
>
|
||||
<AIcon :type="o.icon" />
|
||||
<span>{{
|
||||
o.text
|
||||
}}</span>
|
||||
</j-button>
|
||||
<j-tooltip :title="o?.tooltip?.title">
|
||||
<j-button
|
||||
type="link"
|
||||
@click="o.onClick"
|
||||
:disabled="o.disabled"
|
||||
>
|
||||
<AIcon :type="o.icon" />
|
||||
<span>{{
|
||||
o.text
|
||||
}}</span>
|
||||
</j-button>
|
||||
</j-tooltip>
|
||||
</j-menu-item>
|
||||
</j-menu>
|
||||
</template>
|
||||
|
@ -165,6 +168,7 @@
|
|||
)"
|
||||
:hasPermission="i.permission"
|
||||
type="link"
|
||||
:key="i.key"
|
||||
:tooltip="i.tooltip"
|
||||
:pop-confirm="i.popConfirm"
|
||||
@click="i.onClick"
|
||||
|
@ -181,9 +185,9 @@
|
|||
<MenuDialog
|
||||
v-if="dialogVisible"
|
||||
v-model:visible="dialogVisible"
|
||||
:id="selectId"
|
||||
:provider="selectProvider"
|
||||
mode="edit"
|
||||
:data="current"
|
||||
@refresh="table.refresh"
|
||||
/>
|
||||
</div>
|
||||
</page-container>
|
||||
|
@ -196,8 +200,8 @@ import {
|
|||
getApplyList_api,
|
||||
changeApplyStatus_api,
|
||||
delApply_api,
|
||||
queryType
|
||||
} from '@/api/system/apply';
|
||||
import { ActionsType } from '@/components/Table';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
import { message } from 'jetlinks-ui-components';
|
||||
|
@ -205,32 +209,46 @@ import BadgeStatus from '@/components/BadgeStatus/index.vue';
|
|||
|
||||
const menuStory = useMenuStore();
|
||||
const permission = 'system/Apply';
|
||||
const typeOptions = [
|
||||
{
|
||||
label: '内部独立应用',
|
||||
value: 'internal-standalone',
|
||||
},
|
||||
{
|
||||
label: '微信网站应用',
|
||||
value: 'wechat-webapp',
|
||||
},
|
||||
{
|
||||
label: '内部集成应用',
|
||||
value: 'internal-integrated',
|
||||
},
|
||||
{
|
||||
label: '钉钉企业内部应用',
|
||||
value: 'dingtalk-ent-app',
|
||||
},
|
||||
{
|
||||
label: '第三方应用',
|
||||
value: 'third-party',
|
||||
},
|
||||
{
|
||||
label: '小程序应用',
|
||||
value: 'wechat-miniapp',
|
||||
},
|
||||
];
|
||||
// const typeOptions = [
|
||||
// {
|
||||
// label: '内部独立应用',
|
||||
// value: 'internal-standalone',
|
||||
// },
|
||||
// {
|
||||
// label: '微信网站应用',
|
||||
// value: 'wechat-webapp',
|
||||
// },
|
||||
// {
|
||||
// label: '内部集成应用',
|
||||
// value: 'internal-integrated',
|
||||
// },
|
||||
// {
|
||||
// label: '钉钉企业内部应用',
|
||||
// value: 'dingtalk-ent-app',
|
||||
// },
|
||||
// {
|
||||
// label: '第三方应用',
|
||||
// value: 'third-party',
|
||||
// },
|
||||
// {
|
||||
// label: '小程序应用',
|
||||
// value: 'wechat-miniapp',
|
||||
// },
|
||||
// ];
|
||||
|
||||
const typeOptions = ref<any[]>([])
|
||||
|
||||
onMounted(() => {
|
||||
queryType().then((resp: any) => {
|
||||
if(resp.status === 200){
|
||||
const arr = resp.result.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.provider,
|
||||
}))
|
||||
typeOptions.value = arr
|
||||
}
|
||||
});
|
||||
})
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
|
@ -247,10 +265,20 @@ const columns = [
|
|||
dataIndex: 'provider',
|
||||
key: 'provider',
|
||||
ellipsis: true,
|
||||
fixed: 'left',
|
||||
search: {
|
||||
type: 'select',
|
||||
options: typeOptions,
|
||||
// options: () =>
|
||||
// new Promise((resolve) => {
|
||||
// queryType().then((resp: any) => {
|
||||
// resolve(
|
||||
// resp.result.map((item: any) => ({
|
||||
// label: item.name,
|
||||
// value: item.provider,
|
||||
// })),
|
||||
// );
|
||||
// });
|
||||
// }),
|
||||
},
|
||||
scopedSlots: true,
|
||||
},
|
||||
|
@ -296,6 +324,7 @@ const columns = [
|
|||
const queryParams = ref({});
|
||||
|
||||
const tableRef = ref();
|
||||
const current = ref<any>({})
|
||||
const table = {
|
||||
refresh: () => {
|
||||
tableRef.value.reload(queryParams.value);
|
||||
|
@ -383,12 +412,14 @@ const table = {
|
|||
key: 'page',
|
||||
text: '集成菜单',
|
||||
tooltip: {
|
||||
title: '集成菜单',
|
||||
title: !disabled ? '请先启用' : '集成菜单',
|
||||
},
|
||||
icon: 'MenuUnfoldOutlined',
|
||||
disabled: !disabled,
|
||||
onClick: () => {
|
||||
selectId.value = data.id;
|
||||
selectProvider.value = data.provider;
|
||||
current.value = data
|
||||
dialogVisible.value = true;
|
||||
},
|
||||
});
|
||||
|
@ -441,7 +472,7 @@ const table = {
|
|||
},
|
||||
getTypeLabel: (val: string) => {
|
||||
if (!val) return '';
|
||||
return typeOptions.find((item) => item.value === val)?.label;
|
||||
return typeOptions.value?.find((item) => item?.value === val)?.label;
|
||||
},
|
||||
};
|
||||
const dialogVisible = ref(false);
|
||||
|
|
|
@ -3823,10 +3823,10 @@ jetlinks-store@^0.0.3:
|
|||
resolved "https://registry.npmjs.org/jetlinks-store/-/jetlinks-store-0.0.3.tgz"
|
||||
integrity sha512-AZf/soh1hmmwjBZ00fr1emuMEydeReaI6IBTGByQYhTmK1Zd5pQAxC7WLek2snRAn/HHDgJfVz2hjditKThl6Q==
|
||||
|
||||
jetlinks-ui-components@^1.0.22:
|
||||
version "1.0.22"
|
||||
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.22.tgz#1b7c3d632b5c1b55a4ce59fb010c48edb402c657"
|
||||
integrity sha512-U19VTSmxCgz1ybNY3QhPRaTM9CVvq1nYJOMc+Y8rc2FnzQ9B7k1Xk/fmJZvdt7lqlcDL3xO0zXkIVdAdapG3GA==
|
||||
jetlinks-ui-components@^1.0.23:
|
||||
version "1.0.23"
|
||||
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.23.tgz#029f45a61316e3bf3b4c75959d41d7d76068fcbe"
|
||||
integrity sha512-6OGDn8/kAmjlHMoeIp5B4L6EeucbqKFxqnYys4MqmtEvco/d5Pr8rzmJEav6bZmGkN21YWNHpwkhX3yrQt2F+g==
|
||||
dependencies:
|
||||
"@vueuse/core" "^9.12.0"
|
||||
"@vueuse/router" "^9.13.0"
|
||||
|
|
Loading…
Reference in New Issue