fix: 修复设备接入更换接入方式,参数名称未发生变化问题

This commit is contained in:
XieYongHong 2023-05-18 16:20:20 +08:00 committed by GitHub
parent 285d108bbc
commit 73e527f12c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 735 additions and 160 deletions

View File

@ -25,7 +25,7 @@
"event-source-polyfill": "^1.0.31", "event-source-polyfill": "^1.0.31",
"global": "^4.4.0", "global": "^4.4.0",
"jetlinks-store": "^0.0.3", "jetlinks-store": "^0.0.3",
"jetlinks-ui-components": "^1.0.13", "jetlinks-ui-components": "^1.0.16",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"less": "^4.1.3", "less": "^4.1.3",
"less-loader": "^11.1.0", "less-loader": "^11.1.0",

Binary file not shown.

View File

@ -2,6 +2,15 @@
@DarkMenuItemColor: #808491 !important; @DarkMenuItemColor: #808491 !important;
@font-face {
font-family: AliRegular;
src: url("/fonts/AlibabaPuHuiTi-2-55-Regular.ttf");
}
body {
font-family: 'AliRegular' !important;
}
.ant-form-item-required:before { .ant-form-item-required:before {
position: absolute; position: absolute;
right: -12px; right: -12px;

View File

@ -1,3 +1,4 @@
@import "ant-design-vue/es/style/themes/index.less";
@import 'jetlinks-ui-components/es/style/variable.less'; @import 'jetlinks-ui-components/es/style/variable.less';
.ellipsisFn(@num: 1, @width: 100%) { .ellipsisFn(@num: 1, @width: 100%) {

View File

@ -1,6 +1,6 @@
<template> <template>
<pro-search <pro-search
class="device-search" class="device-running-search"
type="simple" type="simple"
:columns="columns" :columns="columns"
target="device-instance-running-events" target="device-instance-running-events"
@ -45,23 +45,25 @@ const events = defineProps({
}); });
const instanceStore = useInstanceStore(); const instanceStore = useInstanceStore();
const columns = ref<Record<string, any>>([ const defaultColumns = [
{ {
title: '时间', title: '时间',
dataIndex: 'timestamp', dataIndex: 'timestamp',
key: 'timestamp', key: 'timestamp',
scopedSlots: true, scopedSlots: true,
search: { search: {
type: 'date', type: 'date',
},
}, },
{ },
title: '操作', {
dataIndex: 'action', title: '操作',
key: 'action', dataIndex: 'action',
scopedSlots: true, key: 'action',
}, scopedSlots: true,
]); }
]
const columns = ref<Array<Record<string, any>>>([...defaultColumns]);
const params = ref<Record<string, any>>({}); const params = ref<Record<string, any>>({});
const visible = ref<boolean>(false); const visible = ref<boolean>(false);
const info = ref<Record<string, any>>({}); const info = ref<Record<string, any>>({});
@ -70,6 +72,7 @@ const _getEventList = (_params: any) =>
getEventList(instanceStore.current.id || '', events.data.id || '', _params); getEventList(instanceStore.current.id || '', events.data.id || '', _params);
watchEffect(() => { watchEffect(() => {
columns.value = [...defaultColumns]
if (events.data?.valueType?.type === 'object') { if (events.data?.valueType?.type === 'object') {
(events.data.valueType?.properties || []).reverse().map((i: any) => { (events.data.valueType?.properties || []).reverse().map((i: any) => {
columns.value.splice(0, 0, { columns.value.splice(0, 0, {
@ -109,8 +112,10 @@ const detail = (_info: any) => {
</script> </script>
<style lang="less"> <style lang="less">
.device-search { .device-running-search {
margin: 0 0 24px 0; margin: 0 0 24px 0;
padding-top: 0 !important;
padding-bottom: 0 !important;
} }
.device-running-event-modal { .device-running-event-modal {

View File

@ -61,7 +61,7 @@
<div class="card-item-content-text"> <div class="card-item-content-text">
设备类型 设备类型
</div> </div>
<div>直连设备</div> <div>{{ slotProps?.deviceType?.text }}</div>
</j-col> </j-col>
</j-row> </j-row>
</template> </template>

View File

@ -257,12 +257,15 @@ const findProvidersByProvider = (provider: string) => {
*/ */
const submitData = async () => { const submitData = async () => {
if (selectedRowKeys.value.length) { if (selectedRowKeys.value.length) {
if (checkData.value.channel === 'plugin') { if (checkData.value.channel === 'plugin') {
const resp = await getProductByPluginId(checkData.value.channelId).catch(() => ({ success: false, result: []})) const resp = await getProductByPluginId(checkData.value.channelId).catch(() => ({ success: false, result: []}))
const metadataResp = await getAccessConfig(props.productId!, checkData.value.id).catch(() => ({ success: false, result: {}}))
emit('submit', { emit('submit', {
access: {...checkData.value}, access: {...checkData.value},
productTypes: resp.result productTypes: resp.result,
metadata: metadataResp.result
}) })
} else { } else {
loading.value= true loading.value= true

View File

@ -615,11 +615,13 @@ const checkAccess = async (data: any) => {
productTypes.value = [] productTypes.value = []
productData.id = undefined productData.id = undefined
productData.metadata = {} productData.metadata = {}
metadata.value = data.metadata?.[0] || {
properties: []
}
if (data.access.channel === 'plugin') { // if (data.access.channel === 'plugin') { //
markdownToHtml.value = '' markdownToHtml.value = ''
productTypes.value = data.productTypes.map(item => ({ ...item, label: item.name, value: item.id})) productTypes.value = data.productTypes.map(item => ({ ...item, label: item.name, value: item.id}))
} else { } else {
metadata.value = data.metadata[0]
handleColumns() handleColumns()
markdownToHtml.value = config.value?.document ? marked(config.value.document) : ''; markdownToHtml.value = config.value?.document ? marked(config.value.document) : '';
getGuide(!!data.metadata.length); // getGuide(!!data.metadata.length); //

View File

@ -20,12 +20,17 @@
> >
<template #bodyCell="{ column, text, record, index }"> <template #bodyCell="{ column, text, record, index }">
<template v-if='column.dataIndex === "name"'> <template v-if='column.dataIndex === "name"'>
<span class='metadata-title'>{{ text }} ({{ record.id }})</span> <span class='metadata-title'>
<j-ellipsis>
{{ text }} ({{ record.id }})
</j-ellipsis>
</span>
</template> </template>
<template v-if='column.dataIndex === "plugin"'> <template v-if='column.dataIndex === "plugin"'>
<j-select <j-select
v-model:value='record.plugin' v-model:value='record.plugin'
style='width: 100%' style='width: 100%'
allowClear
@change='(id) => pluginChange(record, id)' @change='(id) => pluginChange(record, id)'
> >
<j-select-option <j-select-option
@ -40,26 +45,28 @@
</j-table> </j-table>
</div> </div>
<div class='right'> <div class='right'>
<div class='title'> <j-scrollbar>
功能说明 <div class='title'>
</div> 功能说明
<p> </div>
该功能用于将插件中的 <p>
<b>物模型属性标识</b> 该功能用于将插件中的
<b>平台物模型属性标识</b>进行映射,当两方属性标识不一致时可在当前页面直接修改映射管理系统将以映射后的物模型属性进行数据处理 <b>物模型属性标识</b>
</p> <b>平台物模型属性标识</b>进行映射,当两方属性标识不一致时可在当前页面直接修改映射管理系统将以映射后的物模型属性进行数据处理
<p> </p>
未完成映射的属性标识目标属性列数据为空代表该属性值来源以在平台配置的来源为准 <p>
</p> 未完成映射的属性标识目标属性列数据为空代表该属性值来源以在平台配置的来源为准
<p> </p>
数据条背景亮起代表<b>标识一致</b><b>已完成映射</b>的属性 <p>
</p> 数据条背景亮起代表<b>标识一致</b><b>已完成映射</b>的属性
<div class='title'> </p>
功能图示 <div class='title'>
</div> 功能图示
<div> </div>
<img :src='getImage("/device/matadataMap.png")' /> <div>
</div> <img :src='getImage("/device/matadataMap.png")' />
</div>
</j-scrollbar>
</div> </div>
</div> </div>
</template> </template>
@ -87,7 +94,7 @@ const columns = [
{ {
title: '序号', title: '序号',
dataIndex: 'index', dataIndex: 'index',
width: 120 width: 100
}, },
{ {
title: '平台属性', title: '平台属性',
@ -96,6 +103,7 @@ const columns = [
{ {
title: '目标属性', title: '目标属性',
dataIndex: 'plugin', dataIndex: 'plugin',
width: 250,
sorter: tableFilter sorter: tableFilter
} }
] ]

View File

@ -470,8 +470,8 @@ const query = reactive({
}, },
{ {
title: '接入方式', title: '接入方式',
key: 'accessName', key: 'accessId',
dataIndex: 'accessName', dataIndex: 'accessId',
search: { search: {
type: 'select', type: 'select',
options: async () => { options: async () => {
@ -482,7 +482,7 @@ const query = reactive({
typeList.value = []; typeList.value = [];
typeList.value = resp.result.map((item: any) => ({ typeList.value = resp.result.map((item: any) => ({
label: item.name, label: item.name,
value: item.name, value: item.id,
})); }));
res(typeList.value); res(typeList.value);
}); });

View File

@ -20,7 +20,8 @@ watch(
deviceId.value = newId as string; deviceId.value = newId as string;
_control(newId).then((resp: any) => { _control(newId).then((resp: any) => {
if (resp.status === 200) { if (resp.status === 200) {
const item = `http://${resp.result?.url}/#/login?token=${resp.result.token}`; const protocol = location.protocol
const item = `${protocol}//${resp.result?.url}/#/login?token=${resp.result.token}`;
url.value = item; url.value = item;
} }
}); });

View File

@ -16,11 +16,13 @@
<template v-if="showType === 'network'"> <template v-if="showType === 'network'">
<Network <Network
v-if="provider.id !== 'plugin_gateway'" v-if="provider.id !== 'plugin_gateway'"
:bindProduct='bindProduct'
:provider="provider" :provider="provider"
:data="data" :data="data"
/> />
<Plugin <Plugin
v-else v-else
:bindProduct='bindProduct'
:provider="provider" :provider="provider"
:data="data" :data="data"
/> />
@ -28,21 +30,25 @@
<Media <Media
v-if="showType === 'media'" v-if="showType === 'media'"
:bindProduct='bindProduct'
:provider="provider" :provider="provider"
:data="data" :data="data"
/> />
<Channel <Channel
v-if="showType === 'channel'" v-if="showType === 'channel'"
:bindProduct='bindProduct'
:provider="provider" :provider="provider"
:data="data" :data="data"
/> />
<Edge <Edge
v-if="showType === 'edge'" v-if="showType === 'edge'"
:bindProduct='bindProduct'
:provider="provider" :provider="provider"
:data="data" :data="data"
/> />
<Cloud <Cloud
v-if="showType === 'cloud'" v-if="showType === 'cloud'"
:bindProduct='bindProduct'
:provider="provider" :provider="provider"
:data="data" :data="data"
/> />
@ -63,6 +69,7 @@ import Cloud from '../components/Cloud/index.vue';
import Plugin from '../components/Plugin/index.vue' import Plugin from '../components/Plugin/index.vue'
import { getProviders, detail } from '@/api/link/accessConfig'; import { getProviders, detail } from '@/api/link/accessConfig';
import { accessConfigTypeFilter } from '@/utils/setting'; import { accessConfigTypeFilter } from '@/utils/setting';
import { queryProductList } from '@/api/device/product';
const route = useRoute(); const route = useRoute();
const id = route.params.id as string; const id = route.params.id as string;
@ -73,6 +80,7 @@ const loading = ref(true);
const provider = ref({}); const provider = ref({});
const data = ref({}); const data = ref({});
const showType: any = ref(''); const showType: any = ref('');
const bindProduct = ref(false)
const goProviders = (param: any) => { const goProviders = (param: any) => {
showType.value = param.type; showType.value = param.type;
@ -188,8 +196,27 @@ const queryProviders = async () => {
} }
}; };
/**
* 检查是否被产品使用
*/
const checkBindProduct = async (_id: string) => {
const resp = await queryProductList({
paging: false,
terms: [{
column: 'accessId',
termType: 'eq',
value: _id
}]
})
console.log(resp.success && resp.result?.total)
if (resp.success && resp.result?.total) {
bindProduct.value = true
}
}
const getProvidersData = async () => { const getProvidersData = async () => {
if (id !== ':id') { if (id !== ':id') {
checkBindProduct(id)
getProviders().then((response: any) => { getProviders().then((response: any) => {
if (response.status === 200) { if (response.status === 200) {
const _data = response.result || []; const _data = response.result || [];

View File

@ -22,13 +22,12 @@
}}</j-tooltip> }}</j-tooltip>
</div> </div>
<div class="checked-icon"> <div class="checked-icon">
<div><CheckOutlined /></div> <div><a-icon type='CheckOutlined' /></div>
</div> </div>
</j-card> </j-card>
</template> </template>
<script lang="ts" setup name="AccessCard"> <script lang="ts" setup name="AccessCard">
import { CheckOutlined } from '@ant-design/icons-vue';
const emit = defineEmits(['checkedChange']); const emit = defineEmits(['checkedChange']);
@ -48,7 +47,9 @@ const props = defineProps({
}); });
const checkedChange = (id: string) => { const checkedChange = (id: string) => {
if (!props.disabled) {
emit('checkedChange', id); emit('checkedChange', id);
}
}; };
</script> </script>

View File

@ -177,6 +177,7 @@
@search="procotolSearch" @search="procotolSearch"
/> />
<PermissionButton <PermissionButton
v-if='showAddBtn'
type="primary" type="primary"
@click="addProcotol" @click="addProcotol"
hasPermission="link/Protocol:add" hasPermission="link/Protocol:add"
@ -199,6 +200,7 @@
<AccessCard <AccessCard
@checkedChange="procotolChange" @checkedChange="procotolChange"
:checked="procotolCurrent" :checked="procotolCurrent"
:disabled='!showAddBtn'
:data="{ ...item, type: 'protocol' }" :data="{ ...item, type: 'protocol' }"
> >
</AccessCard> </AccessCard>
@ -352,6 +354,10 @@ const props = defineProps({
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
bindProduct: {
type: Boolean,
default: false
}
}); });
const formRef1 = ref<FormInstance>(); const formRef1 = ref<FormInstance>();
@ -368,6 +374,10 @@ const formData = ref<Form>({
description: '', description: '',
}); });
const showAddBtn = computed(() => {
return route.query.view === 'false' && !props.bindProduct
})
const current = ref(0); const current = ref(0);
const stepCurrent = ref(0); const stepCurrent = ref(0);
const steps = ref(['接入配置', '消息协议', '完成']); const steps = ref(['接入配置', '消息协议', '完成']);

View File

@ -260,6 +260,7 @@
@search="procotolSearch" @search="procotolSearch"
/> />
<PermissionButton <PermissionButton
v-if='showAddBtn'
type="primary" type="primary"
@click="addProcotol" @click="addProcotol"
hasPermission="link/Protocol:add" hasPermission="link/Protocol:add"
@ -282,6 +283,7 @@
<AccessCard <AccessCard
@checkedChange="procotolChange" @checkedChange="procotolChange"
:checked="procotolCurrent" :checked="procotolCurrent"
:disabled='!showAddBtn'
:data="{ ...item, type: 'protocol' }" :data="{ ...item, type: 'protocol' }"
> >
</AccessCard> </AccessCard>
@ -434,6 +436,10 @@ const props = defineProps({
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
bindProduct: {
type: Boolean,
default: false
}
}); });
const route = useRoute(); const route = useRoute();
@ -462,6 +468,10 @@ const procotolList: any = ref([]);
const allProcotolList = ref([]); const allProcotolList = ref([]);
const procotolCurrent: any = ref(''); const procotolCurrent: any = ref('');
const showAddBtn = computed(() => {
return route.query.view === 'false' && !props.bindProduct
})
const procotolChange = (id: string) => { const procotolChange = (id: string) => {
procotolCurrent.value = id; procotolCurrent.value = id;
}; };

View File

@ -3,11 +3,13 @@
<Ctwing <Ctwing
v-if="channel === 'Ctwing'" v-if="channel === 'Ctwing'"
:provider="props.provider" :provider="props.provider"
:bindProduct='bindProduct'
:data="props.data" :data="props.data"
/> />
<OneNet <OneNet
v-if="channel === 'OneNet'" v-if="channel === 'OneNet'"
:provider="props.provider" :provider="props.provider"
:bindProduct='bindProduct'
:data="props.data" :data="props.data"
/> />
</div> </div>
@ -26,6 +28,10 @@ const props = defineProps({
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
bindProduct: {
type: Boolean,
default: false
}
}); });
const channel = props.provider.channel; const channel = props.provider.channel;

View File

@ -530,7 +530,7 @@ const props = defineProps({
data: { data: {
type: Object, type: Object,
default: () => {}, default: () => {},
}, }
}); });
const route = useRoute(); const route = useRoute();

View File

@ -17,6 +17,7 @@
@search="networkSearch" @search="networkSearch"
/> />
<PermissionButton <PermissionButton
type="primary" type="primary"
@click="addNetwork" @click="addNetwork"
hasPermission="link/Type:add" hasPermission="link/Type:add"
@ -112,6 +113,7 @@
@search="procotolSearch" @search="procotolSearch"
/> />
<PermissionButton <PermissionButton
v-if='showAddBtn'
type="primary" type="primary"
@click="addProcotol" @click="addProcotol"
hasPermission="link/Protocol:add" hasPermission="link/Protocol:add"
@ -134,7 +136,7 @@
<AccessCard <AccessCard
@checkedChange="procotolChange" @checkedChange="procotolChange"
:checked="procotolCurrent" :checked="procotolCurrent"
:disabled='id !== ":id"' :disabled='!showAddBtn'
:data="{ ...item, type: 'protocol' }" :data="{ ...item, type: 'protocol' }"
> >
</AccessCard> </AccessCard>
@ -352,6 +354,10 @@ const props = defineProps({
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
bindProduct: {
type: Boolean,
default: false
}
}); });
const clientHeight = document.body.clientHeight; const clientHeight = document.body.clientHeight;
@ -396,6 +402,10 @@ const { resetFields, validate, validateInfos } = useForm(
}), }),
); );
const showAddBtn = computed(() => {
return route.query.view === 'false' && !props.bindProduct
})
const queryNetworkList = async (id: string, include: string, data = {}) => { const queryNetworkList = async (id: string, include: string, data = {}) => {
const resp = await getNetworkList( const resp = await getNetworkList(
NetworkTypeMapping.get(id), NetworkTypeMapping.get(id),

View File

@ -14,6 +14,7 @@
@search="pluginSearch" @search="pluginSearch"
/> />
<PermissionButton <PermissionButton
v-if='showAddBtn'
type="primary" type="primary"
@click="addPlugin" @click="addPlugin"
hasPermission="link/plugin:add" hasPermission="link/plugin:add"
@ -36,7 +37,7 @@
<AccessCard <AccessCard
@checkedChange="AccessChange" @checkedChange="AccessChange"
:checked="AccessCurrent" :checked="AccessCurrent"
:disabled='paramsId !== ":id"' :disabled='!showAddBtn'
:data="{ ...item, type: 'plugin' }" :data="{ ...item, type: 'plugin' }"
> >
<template #other> <template #other>
@ -185,6 +186,10 @@ const props = defineProps({
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
bindProduct: {
type: Boolean,
default: false
}
}); });
const route = useRoute(); const route = useRoute();
@ -220,6 +225,10 @@ const queryPlugin = (params = {}) => {
}) })
} }
const showAddBtn = computed(() => {
return route.query.view === 'false' && !props.bindProduct
})
const getRules = (item: any) => { const getRules = (item: any) => {
let typeName = '输入' let typeName = '输入'
let rules: any[] = [] let rules: any[] = []

View File

@ -157,7 +157,15 @@ watch(
.dash-board-item { .dash-board-item {
flex: 1; flex: 1;
//margin: 24px 12px; //margin: 24px 12px;
min-width: 250px; min-width: calc(25% - 24px);
} }
} }
@media (max-width: 1400px) {
.dash-board {
.dash-board-item {
min-width: calc(50% - 24px);
}
}
}
</style> </style>

View File

@ -43,19 +43,27 @@ export default {
data() { data() {
return { return {
options: {}, options: {},
myChart: undefined
}; };
}, },
beforeDestroy() {
window.removeEventListener('resize', this.resize)
},
methods: { methods: {
createChart(val) { createChart(val) {
const chart = this.$refs.chartRef; const chart = this.$refs.chartRef;
if (chart && Object.keys(val).length > 0) { if (chart && Object.keys(val).length > 0 && !this.myChart) {
const myChart = echarts.init(chart); console.log('createChart')
myChart.setOption(val); this.myChart = echarts.init(chart);
window.addEventListener('resize', function () { this.myChart.setOption(val);
myChart.resize(); window.addEventListener('resize', this.resize);
}); } else if (this.myChart) {
this.myChart.setOption(val);
} }
}, },
resize() {
this.myChart?.resize();
},
getOptions(max, formatter, val) { getOptions(max, formatter, val) {
let formatterCount = 0; let formatterCount = 0;
this.options = { this.options = {

View File

@ -110,17 +110,20 @@ export const Validator = {
), ),
regIPv6: new RegExp(/^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/), regIPv6: new RegExp(/^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/),
regDomain: new RegExp( regDomain: new RegExp(
/^https?:\/\/(([a-zA-Z0-9_-])+(\.)?)*(:\d+)?(\/((\.)?(\?)?=?&?[a-zA-Z0-9_-](\?)?)*)*$/i, // /^https?:\/\/(([a-zA-Z0-9_-])+(\.)?)*(:\d+)?(\/((\.)?(\?)?=?&?[a-zA-Z0-9_-](\?)?)*)*$/i,
/^[a-zA-Z0-9]+([\-\.]{1}[a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$/
), ),
regOnlyNumber: new RegExp(/^\d+$/), regOnlyNumber: new RegExp(/^\d+$/),
}; };
const validateAddress = (_rule: any, value: string): Promise<any> => { const validateAddress = (_rule: any, value: string): Promise<any> => {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const _domainStr = value
const _domain = _domainStr.replace(/^(https?|ftp):\/\/(www\.)?/i, '')
if ( if (
Validator.regIpv4.test(value) || Validator.regIpv4.test(value) ||
Validator.regIPv6.test(value) || Validator.regIPv6.test(value) ||
Validator.regDomain.test(value) Validator.regDomain.test(_domain)
) { ) {
return resolve(''); return resolve('');
} else { } else {

View File

@ -1,7 +1,7 @@
<template> <template>
<j-upload <j-upload
name="file" name="file"
accept=".jar" accept=".jar,.zip"
:action="uploadFile" :action="uploadFile"
:headers="{ :headers="{
[TOKEN_KEY]: LocalStore.get(TOKEN_KEY), [TOKEN_KEY]: LocalStore.get(TOKEN_KEY),
@ -16,7 +16,7 @@
> >
<div> <div>
<j-button>上传文件</j-button> <j-button>上传文件</j-button>
<span class='upload-tip'>格式要求{文件名}.jar/{文件名}.zip</span> <span class='upload-tip'>格式要求.jar .zip</span>
</div> </div>
</j-upload> </j-upload>

View File

@ -207,9 +207,11 @@ watch(
* 部门点击 * 部门点击
*/ */
const onTreeSelect = (keys: any) => { const onTreeSelect = (keys: any) => {
if (keys.length) {
deptId.value = keys[0]; deptId.value = keys[0];
pageSize.value = 10; pageSize.value = 10;
current.value = 1; current.value = 1;
}
}; };
// //

View File

@ -271,11 +271,11 @@ const columns = [
}, },
{ {
title: '关联场景联动', title: '关联场景联动',
dataIndex: 'sceneId', dataIndex: 'scene',
wdith: 250,
scopedSlots: true, scopedSlots: true,
search: { search: {
type: 'select', type: 'select',
// defaultTermType: 'rule-bind-alarm',
options: async () => { options: async () => {
const res = await getScene( const res = await getScene(
encodeQuery({ encodeQuery({
@ -338,7 +338,23 @@ const map = {
other: '其他', other: '其他',
}; };
const handleSearch = (e: any) => { const handleSearch = (e: any) => {
params.value = e; const _terms = (e?.terms || []).map((item: any) => {
item.terms = item.terms.map((i: any) => {
if(i.column === 'scene'){
return {
...i,
termType: 'rule-bind-alarm',
column: 'id'
}
}
return i
})
return item
})
params.value = {
...e,
terms: _terms
}
}; };
const queryDefaultLevel = () => { const queryDefaultLevel = () => {
queryLevel().then((res) => { queryLevel().then((res) => {

View File

@ -0,0 +1,127 @@
<template>
<slot></slot>
</template>
<script setup lang='ts' name='CheckItem'>
import { storeToRefs } from 'pinia';
import { useSceneStore } from '@/store/scene'
import { Form } from 'jetlinks-ui-components'
import { queryProductList } from '@/api/device/product'
import { query as deviceQuery } from '@/api/device/instance'
import { getTreeData_api } from '@/api/system/department'
const sceneStore = useSceneStore()
const { data } = storeToRefs(sceneStore)
const formItemContext = Form.useInjectFormItemContext()
const formTouchOff = () => {
formItemContext.onFieldChange()
}
const check = async (): Promise<boolean> => {
const deviceTrigger = data.value.trigger!.device!
const productId = deviceTrigger.productId
//
const proResp = await queryProductList({ terms: [{ terms: [{ column: 'id', termType: 'eq', value: productId }]}]})
if (proResp.success && (proResp.result as any)?.total === 0) {
data.value.trigger!.device!.productId = ''
return false
}
const productDetail = proResp?.result?.data?.[0]
const selectorValues = deviceTrigger.selectorValues?.map(item => item.value)
let metadata = JSON.parse(productDetail?.metadata || '{}') //
//
if (deviceTrigger.selector === 'fixed') { //
const deviceResp = await deviceQuery({ terms: [{ column: 'id', termType: 'in', value: selectorValues?.toString() }]})
if (deviceResp.success && (deviceResp.result as any)?.total !== (selectorValues!.length)) {
data.value.trigger!.device!.selectorValues = undefined
return false
}
if (selectorValues!.length === 1) {
const deviceDetail = deviceResp?.result?.data?.[0]
metadata = JSON.parse(deviceDetail?.metadata || '{}') //
}
} else if (deviceTrigger.selector === 'org') { //
const orgResp = await getTreeData_api({
paging: false,
terms: [{ column: 'id', termType: 'eq', value: selectorValues![0] }]
})
if (orgResp.success && (orgResp.result as any[]).length !== selectorValues!.length) {
data.value.trigger!.device!.selectorValues = undefined
return false
}
}
//
if (['readProperty', 'writeProperty'].includes(deviceTrigger.operation?.operator!)) {
let hasProperties = false
if (metadata.properties.length) {
if (deviceTrigger.operation?.readProperties && deviceTrigger.operation?.readProperties.length) {
hasProperties = metadata.properties.every((item: any) => deviceTrigger.operation!.readProperties!.includes(item.id))
} else if (deviceTrigger.operation?.writeProperties && Object.keys(deviceTrigger.operation?.writeProperties).length) {
const key = Object.keys(deviceTrigger.operation?.writeProperties)[0]
hasProperties = metadata.properties.some((item: any) => key ===item.id)
}
}
if (!hasProperties) {
if (deviceTrigger.operation?.operator === 'readProperty') {
deviceTrigger.operation!.readProperties = []
} else {
deviceTrigger.operation!.writeProperties = {}
}
return false
}
}
if (deviceTrigger.operation?.operator === 'invokeFunction') {
let hasProperties = false
if (metadata.functions.length) {
const functionId = deviceTrigger.operation?.functionId
hasProperties = metadata.functions.some((item: any) => functionId ===item.id)
}
if (!hasProperties) {
deviceTrigger.operation.functionId = undefined
deviceTrigger.operation.functionParameters = []
return false
}
}
if (deviceTrigger.operation?.operator === 'reportEvent') {
let hasProperties = false
if (metadata.events.length) {
const eventId = deviceTrigger.operation.eventId
hasProperties = metadata.events.some((item: any) => eventId ===item.id)
}
if (!hasProperties) {
deviceTrigger.operation.eventId = undefined
return false
}
}
return true
}
const checkInit = async () => {
if (data.value.trigger?.device) {
const checkStatus = await check()
if (!checkStatus) {
formTouchOff()
}
}
}
checkInit()
</script>
<style scoped>
</style>

View File

@ -7,13 +7,15 @@
<template #label> <template #label>
<TitleComponent data='触发规则' style='font-size: 14px;' /> <TitleComponent data='触发规则' style='font-size: 14px;' />
</template> </template>
<AddButton
style='width: 100%' <AddButton
@click='visible = true' style='width: 100%'
> @click='visible = true'
<Title :options='data.options.trigger' /> >
</AddButton> <Title :options='data.options.trigger' />
<AddModel v-if='visible' @cancel='visible = false' @save='save' :value='data.trigger.device' :options='data.options.trigger' /> </AddButton>
<AddModel v-if='visible' @cancel='visible = false' @save='save' :value='data.trigger.device' :options='data.options.trigger' />
<CheckItem />
</j-form-item> </j-form-item>
<Terms /> <Terms />
</div> </div>
@ -28,6 +30,7 @@ import Title from '../components/Title.vue'
import Terms from '../components/Terms' import Terms from '../components/Terms'
import type { TriggerDevice } from '@/views/rule-engine/Scene/typings' import type { TriggerDevice } from '@/views/rule-engine/Scene/typings'
import { EventEmitter, DeviceEmitterKey } from '@/views/rule-engine/Scene/Save/util' import { EventEmitter, DeviceEmitterKey } from '@/views/rule-engine/Scene/Save/util'
import CheckItem from './CheckItem.vue'
const sceneStore = useSceneStore() const sceneStore = useSceneStore()
const { data } = storeToRefs(sceneStore) const { data } = storeToRefs(sceneStore)
@ -38,6 +41,21 @@ const rules = [{
validator(_: any, v: any) { validator(_: any, v: any) {
if (!v) { if (!v) {
return Promise.reject(new Error('请配置设备触发规则')); return Promise.reject(new Error('请配置设备触发规则'));
} else {
console.log('device-validator', v)
if (
!v.productId ||
(['fixed', 'org'].includes(v.selector) && !v.selectorValues) ||
(v.operation?.operator === 'readProperty' && !v.operation!.readProperties.length) ||
(v.operation?.operator === 'writeProperty' && !Object.keys(v.operation!.writeProperties).length) ||
(v.operation?.operator === 'invokeFunction' && !v.operation.functionId) ||
(v.operation?.operator === 'reportEvent' && !v.operation.eventId)
) {
return Promise.reject(new Error('该数据已发生变更,请重新配置'));
}
//
//
//
} }
return Promise.resolve(); return Promise.resolve();
}, },

View File

@ -3,7 +3,7 @@
</template> </template>
<script setup lang='ts' name='ActionCheckItem'> <script setup lang='ts' name='ActionCheckItem'>
import { ActionsType } from '@/views/rule-engine/Scene/typings' import { ActionsType, FormModelType, PlatformRelation } from '@/views/rule-engine/Scene/typings'
import { useSceneStore } from '@/store/scene'; import { useSceneStore } from '@/store/scene';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { queryProductList } from '@/api/device/product' import { queryProductList } from '@/api/device/product'
@ -11,7 +11,11 @@ import { query as deviceQuery } from '@/api/device/instance'
import noticeConfig from '@/api/notice/config' import noticeConfig from '@/api/notice/config'
import noticeTemplate from '@/api/notice/template' import noticeTemplate from '@/api/notice/template'
import { Form } from 'jetlinks-ui-components' import { Form } from 'jetlinks-ui-components'
import { EventEmitter, EventSubscribeKeys } from '@/views/rule-engine/Scene/Save/util' import { EventEmitter, EventSubscribeKeys, getParams } from '@/views/rule-engine/Scene/Save/util'
import { getOption } from '@/views/rule-engine/Scene/Save/components/DropdownButton/util'
import { getBuildInData, getNotifyVariablesUser } from './util'
import { defineExpose } from 'vue'
const sceneStore = useSceneStore(); const sceneStore = useSceneStore();
const { data: _data } = storeToRefs(sceneStore); const { data: _data } = storeToRefs(sceneStore);
@ -34,21 +38,6 @@ const props = defineProps({
const sub = ref() const sub = ref()
const rules = [{
validator(_: any, v?: ActionsType) {
if (v?.executor === 'device') {
if(
!v.device?.productId || //
!v.device?.selectorValues || //
(v.device.source === 'upper' && !v.device?.upperKey)
) {
return Promise.reject(new Error('该数据已发生变更,请重新配置'))
}
}
return Promise.resolve()
}
}]
const formTouchOff = () => { const formTouchOff = () => {
formItemContext.onFieldChange() formItemContext.onFieldChange()
} }
@ -64,17 +53,29 @@ const checkDeviceDelete = async () => {
formTouchOff() formTouchOff()
return return
} }
const productDetail = proResp?.result?.data?.[0]
let metadata = JSON.parse(productDetail?.metadata || '{}')
if (item?.selector === 'fixed') { if (item?.selector === 'fixed') {
const deviceList = item!.selectorValues?.map(item => item.value) || [] let hasDevice = false
const deviceResp = await deviceQuery({ terms: [{ terms: [{ column: 'id', termType: 'in', value: deviceList.toString() }]}]}) if (item!.selectorValues) {
if (deviceResp.success && (deviceResp.result as any)?.total < (item!.selectorValues?.length || 0)) { // const deviceList = item!.selectorValues?.map(item => item.value) || []
const deviceResp = await deviceQuery({ terms: [{ terms: [{ column: 'id', termType: 'in', value: deviceList.toString() }]}]})
hasDevice = deviceResp.success && (deviceResp.result as any)?.total === (item!.selectorValues?.length || 0)
if (item!.selectorValues!.length === 1 && hasDevice) {
const deviceDetail = deviceResp?.result?.data?.[0]
metadata = JSON.parse(deviceDetail?.metadata || '{}') //
}
}
if (!hasDevice) { //
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.selectorValues = undefined _data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.selectorValues = undefined
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.changeData = true
formTouchOff() formTouchOff()
return return
} }
}
console.log(item!.source, props.name) } else if (item!.selector === 'context') { // id
if (item!.source === 'upper') { // id
if (props.name === 0) { if (props.name === 0) {
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.upperKey = undefined _data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.upperKey = undefined
formTouchOff() formTouchOff()
@ -83,10 +84,99 @@ const checkDeviceDelete = async () => {
const prevItem = _data.value.branches![props.branchesName].then[props.thenName].actions[props.name - 1].device const prevItem = _data.value.branches![props.branchesName].then[props.thenName].actions[props.name - 1].device
if (prevItem?.productId !== item?.productId) { if (prevItem?.productId !== item?.productId) {
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.upperKey = undefined _data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.upperKey = undefined
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.changeData = true
formTouchOff() formTouchOff()
return return
} else {
const _upperKey = item!.upperKey
const _params = {
branch: props.thenName,
branchGroup: props.branchesName,
action: props.name - 1,
};
const upperData = await getParams(_params, unref(_data.value));
const option = getOption(upperData, _upperKey, 'id')
if (!option) {
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.upperKey = undefined
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.changeData = true
formTouchOff()
return
}
} }
} }
} else if (item!.selector === 'relation') {
const _relationValue = (item!.selectorValues?.[0]?.value as any)?.relation
const relationData = await noticeConfig.getRelationUsers({
paging: false,
sorts: [{ name: 'createTime', order: 'desc' }],
terms: [
{ termType: 'eq', column: 'objectTypeName', value: '设备' },
{ termType: 'eq', column: 'relation', value: _relationValue }
],
}).catch(() => ({ success: false, result: []}))
if (!relationData.result?.length ) {
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.selectorValues = undefined
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.changeData = true
formTouchOff()
return
}
} else if (item!.selector === 'tag') {
let hasAllTags = false
if (item!.selectorValues && metadata?.tags?.length) {
const values = (item!.selectorValues?.[0]?.value as any).map((item: any) => item.column)
const tagKeys = new Set(values)
hasAllTags = metadata?.tags?.every((item: any) => tagKeys.has(item.id))
}
if (!hasAllTags) {
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.selectorValues = undefined
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.changeData = true
formTouchOff()
return
}
}
if (item!.message!.messageType === 'READ_PROPERTY') {
let hasProperties = false
if (item!.message!.properties && metadata.properties.length) {
const propertiesKey = item!.message!.properties?.[0]
hasProperties = metadata.properties?.some((item: any) => item.id === propertiesKey)
}
if (!hasProperties) {
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.message!.properties = []
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.changeData = true
formTouchOff()
return
}
}
if (item!.message!.messageType === 'WRITE_PROPERTY') {
let hasProperties = false
if (item!.message!.properties && metadata.properties.length) {
const propertiesKey = Object.keys(item!.message!.properties!)?.[0]
hasProperties = metadata.properties?.some((item: any) => item.id === propertiesKey)
}
if (!hasProperties) {
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.message!.properties = undefined
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.changeData = true
formTouchOff()
return
}
}
if (item!.message!.messageType === 'INVOKE_FUNCTION') {
const functionId = item!.message!.functionId
let hasFunctions = false
if (functionId && metadata.functions.length) {
hasFunctions = metadata.functions?.some((item: any) => item.id === functionId)
}
if (!hasFunctions) {
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.message!.functionId = undefined
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.changeData = true
formTouchOff()
return
}
} }
} }
@ -95,18 +185,114 @@ const checkDeviceDelete = async () => {
*/ */
const checkNoticeDelete = async () => { const checkNoticeDelete = async () => {
const item = _data.value.branches![props.branchesName].then[props.thenName].actions[props.name].notify const item = _data.value.branches![props.branchesName].then[props.thenName].actions[props.name].notify
const _triggerType = _data.value.triggerType
const configResp = await noticeConfig.list({ terms: [{ terms: [{ column: 'id', termType: 'eq', value: item!.notifierId }]}]}) const configResp = await noticeConfig.list({ terms: [{ terms: [{ column: 'id', termType: 'eq', value: item!.notifierId }]}]})
if (configResp.success && (configResp.result as any)?.total === 0) { if (configResp.success && (configResp.result as any)?.total === 0) { //
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].notify!.notifierId = '' _data.value.branches![props.branchesName].then[props.thenName].actions[props.name].notify!.notifierId = ''
formTouchOff() formTouchOff()
return return
} }
const templateResp = await noticeTemplate.list({ terms: [{ terms: [{ column: 'id', termType: 'eq', value: item!.templateId }]}]}) const templateResp = await noticeTemplate.list({ terms: [{ terms: [{ column: 'id', termType: 'eq', value: item!.templateId }]}]})
if (templateResp.success && (templateResp.result as any)?.total === 0) { if (templateResp.success && (templateResp.result as any)?.total === 0) { //
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].notify!.templateId = '' _data.value.branches![props.branchesName].then[props.thenName].actions[props.name].notify!.templateId = ''
formTouchOff() formTouchOff()
return return
} }
const templateDetailResp = await noticeTemplate.getTemplateDetail(item!.templateId)
if (templateDetailResp.success) {
const variableDefinitionsMap = new Map()
const itemVariableKeys = Object.keys(item!.variables)
const notifyType = item!.notifyType
templateDetailResp.result.variableDefinitions.map((dItem: any) => {
variableDefinitionsMap.set(dItem.id, dItem.expands?.businessType)
})
//
let BuildInData: any = null
const hasAll = itemVariableKeys.every(async (variableKey: any) => {
if (variableDefinitionsMap.has(variableKey)) {
const itemData = item!.variables[variableKey]
if (itemData.source === 'upper') {
if (!BuildInData) {
const _params = {
branch: props.thenName,
branchGroup: props.branchesName,
action: props.name - 1,
};
BuildInData = await getBuildInData(_params, _data.value)
}
const option = BuildInData?.(itemData.upperKey!, 'id')
return !!option
} else if (itemData.source === 'fixed') {
const itemType = variableDefinitionsMap.get(variableKey)
let hasUser = false
if (itemType === 'user') { //
let resp = undefined;
if (notifyType === 'dingTalk') {
resp = await noticeConfig.queryDingTalkUsers(item!.notifierId);
} else {
resp = await noticeConfig.queryWechatUsers(item!.notifierId);
}
if (resp && resp.success) {
hasUser = resp.result.some((uItem: any) => uItem.id === itemData.value)
}
return hasUser
}
if (itemType === 'tag') { //
const resp = await noticeTemplate.getTags(item!.notifierId)
if (resp && resp.success) {
hasUser = resp.result.some((tag: any) => tag.id === itemData.value)
}
return hasUser
}
if (itemType === 'org') { //
let resp = undefined;
if (notifyType === 'dingTalk') {
resp = await noticeConfig.dingTalkDept(item!.notifierId)
} else {
resp = await noticeConfig.weChatDept(item!.notifierId)
}
if (resp && resp.success) {
hasUser = resp.result.some((org: any) => org.id === itemData.value)
}
return hasUser
}
} else if (itemData.source == 'relation') {
//
const userData = await getNotifyVariablesUser(_triggerType === 'device')
let hasUser = false
if (!hasUser && userData.platform.length) { //
hasUser = userData.platform.some(uItem => {
return uItem.id === (itemData.relation as PlatformRelation)?.objectId
})
}
if (!hasUser && userData.relation.length) { //
hasUser = userData.relation.some(uItem => {
return uItem.id === (itemData.relation as PlatformRelation)?.objectId
})
}
return hasUser
}
} else {
return false
}
return true
})
BuildInData = null
if (!hasAll) {
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].notify!.changeData! = true
formTouchOff()
return
}
}
} }
const check = () => { const check = () => {
@ -133,6 +319,10 @@ subscribe()
check() check()
defineExpose({
formTouchOff
})
</script> </script>
<style scoped> <style scoped>

View File

@ -5,7 +5,7 @@
:rules='rules' :rules='rules'
> >
<div class="actions-item"> <div class="actions-item">
<CheckItem v-bind='props'> <CheckItem v-bind='props' ref='checkItemRef'>
<div class="item-options-warp"> <div class="item-options-warp">
<div class="item-options-type" @click="onAdd"> <div class="item-options-type" @click="onAdd">
<img <img
@ -330,7 +330,7 @@
</j-popconfirm> </j-popconfirm>
</CheckItem> </CheckItem>
</div> </div>
</j-form-item> </j-form-item>
<template v-if="!isLast && type === 'serial'"> <template v-if="!isLast && type === 'serial'">
<div <div
:class="[ :class="[
@ -361,7 +361,7 @@
</div> </div>
</div> </div>
</template> </template>
<!-- 编辑 -->
<template v-if="visible"> <template v-if="visible">
<Modal <Modal
:name="name" :name="name"
@ -373,6 +373,7 @@
@save="onSave" @save="onSave"
/> />
</template> </template>
<!-- 编辑 -->
<template> <template>
<ActionTypeComponent <ActionTypeComponent
v-bind="props" v-bind="props"
@ -405,7 +406,6 @@ import FilterGroup from './FilterGroup.vue';
import { randomString } from '@/utils/utils' import { randomString } from '@/utils/utils'
import { EventEmitter, EventEmitterKeys } from '@/views/rule-engine/Scene/Save/util' import { EventEmitter, EventEmitterKeys } from '@/views/rule-engine/Scene/Save/util'
import CheckItem from './CheckItem.vue' import CheckItem from './CheckItem.vue'
import { Form } from 'jetlinks-ui-components'
const sceneStore = useSceneStore(); const sceneStore = useSceneStore();
const { data: _data } = storeToRefs(sceneStore); const { data: _data } = storeToRefs(sceneStore);
@ -441,7 +441,7 @@ const props = defineProps({
}); });
const emit = defineEmits(['delete', 'update']); const emit = defineEmits(['delete', 'update']);
const checkItemRef = ref()
const visible = ref<boolean>(false); const visible = ref<boolean>(false);
const triggerVisible = ref<boolean>(false); const triggerVisible = ref<boolean>(false);
const actionType = ref(''); const actionType = ref('');
@ -450,7 +450,6 @@ const eventEmitterKey = ref(EventEmitterKeys({
branchGroup: props.thenName, branchGroup: props.thenName,
action: props.name action: props.name
})) }))
const formItemContext = Form.useInjectFormItemContext()
const termsOptions = computed(() => { const termsOptions = computed(() => {
if (!props.parallel) { if (!props.parallel) {
// //
@ -535,9 +534,8 @@ const onSave = (data: ActionsType, options: any) => {
} }
_data.value.branches![props.branchesName].then[props.thenName].actions.splice(props.name, 1, actionItem) _data.value.branches![props.branchesName].then[props.thenName].actions.splice(props.name, 1, actionItem)
checkItemRef.value?.formTouchOff?.()
visible.value = false; visible.value = false;
EventEmitter.emit(eventEmitterKey.value, data) // EventEmitter.emit(eventEmitterKey.value, data) //
}; };
@ -559,7 +557,22 @@ const rules = [{
validator(_: any, v?: ActionsType) { validator(_: any, v?: ActionsType) {
console.log('validator-action-item',v) console.log('validator-action-item',v)
if (v?.executor === 'device') { if (v?.executor === 'device') {
if(v?.device?.source === 'fixed' && (!v.device?.productId || !v.device?.selectorValues)) { const _device = v.device!
if (
(_device?.selector === 'fixed' && (!_device?.productId || !_device?.selectorValues?.length )) ||
(_device?.selector === 'context' && !_device?.upperKey) ||
(_device?.selector === 'relation' && !_device?.selectorValues?.length) ||
_device?.changeData === true
) {
return Promise.reject(new Error('该数据已发生变更,请重新配置'))
}
} else if (v?.executor === 'notify') {
const _notify = v.notify
if (
!_notify?.notifierId ||
!_notify?.templateId ||
_notify?.changeData === true
) {
return Promise.reject(new Error('该数据已发生变更,请重新配置')) return Promise.reject(new Error('该数据已发生变更,请重新配置'))
} }
} }

View File

@ -1,4 +1,7 @@
import { getImage } from '@/utils/comm' import { getImage } from '@/utils/comm'
import NoticeApi from '@/api/notice/config'
import { getParams } from '@/views/rule-engine/Scene/Save/util'
import { getOption } from '@/views/rule-engine/Scene/Save/components/DropdownButton/util'
export const iconMap = new Map(); export const iconMap = new Map();
iconMap.set('trigger', getImage('/scene/action-bind-icon.png')); iconMap.set('trigger', getImage('/scene/action-bind-icon.png'));
@ -25,4 +28,33 @@ export const typeIconMap = {
READ_PROPERTY: 'icon-zhihangdongzuodu', READ_PROPERTY: 'icon-zhihangdongzuodu',
INVOKE_FUNCTION: 'icon-zhihangdongzuoxie-1', INVOKE_FUNCTION: 'icon-zhihangdongzuoxie-1',
WRITE_PROPERTY: 'icon-zhihangdongzuoxie', WRITE_PROPERTY: 'icon-zhihangdongzuoxie',
}; };
export const getBuildInData = async (params: any, data: any) => {
const buildInData = await getParams(params, unref(data));
return function(upperKey: string, key: string ) {
return getOption(buildInData, upperKey, key)
}
}
export const getNotifyVariablesUser = (isRelationUser: boolean = false): Promise<{ platform: any[], relation: any[] }> => {
return new Promise(async (resolve) => {
let relationResp = undefined;
const platformResp = await NoticeApi.getPlatformUsers({
paging: false,
sorts: [{ name: 'name', order: 'asc' }],
});
if (isRelationUser) {
relationResp = await NoticeApi.getRelationUsers({
paging: false,
sorts: [{ name: 'name', order: 'asc' }],
});
}
resolve({
platform: platformResp.result || [],
relation: relationResp?.result || []
})
})
}

View File

@ -32,7 +32,7 @@
</j-modal> </j-modal>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup name='UpdateActionItemModal'>
import { getImage } from '@/utils/comm'; import { getImage } from '@/utils/comm';
import Delay from '../Delay/index.vue'; import Delay from '../Delay/index.vue';
import Notify from '../Notify/index.vue'; import Notify from '../Notify/index.vue';

View File

@ -32,6 +32,7 @@
<j-date-picker <j-date-picker
:value="value.value" :value="value.value"
allowClear allowClear
valueFormat='YYYY-MM-DD HH:mm:ss'
format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss"
style="width: calc(100% - 120px)" style="width: calc(100% - 120px)"
v-if="item.type === 'date'" v-if="item.type === 'date'"

View File

@ -38,7 +38,7 @@
v-if="['email'].includes(notifyType)" v-if="['email'].includes(notifyType)"
style="width: calc(100% - 120px)" style="width: calc(100% - 120px)"
placeholder="请选择收信人" placeholder="请选择收信人"
@change="(key, label) => onChange(source, key, false, label)" @change="(key, label) => onChange(source, key, label)"
:tree-data="treeData" :tree-data="treeData"
:multiple="true" :multiple="true"
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
@ -66,7 +66,7 @@
style="width: calc(100% - 120px)" style="width: calc(100% - 120px)"
placeholder="请选择收信人" placeholder="请选择收信人"
@change=" @change="
(key, label) => onChange(source, key, undefined, label) (key, label) => onChange(source, key, label)
" "
:tree-data="treeData" :tree-data="treeData"
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }" :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
@ -102,7 +102,6 @@
onChange( onChange(
source, source,
val, val,
false,
option?.label || option?.name, option?.label || option?.name,
) )
" "
@ -120,7 +119,6 @@
onChange( onChange(
source, source,
val, val,
false,
Array.isArray(val) ? val.join(',') : val, Array.isArray(val) ? val.join(',') : val,
) )
" "
@ -132,7 +130,7 @@
:value="value?.value" :value="value?.value"
@change=" @change="
(e) => (e) =>
onChange(source, e.target.value, false, e.target.value) onChange(source, e.target.value, e.target.value)
" "
></j-input> ></j-input>
</template> </template>
@ -183,7 +181,13 @@ const triggerType = computed(() => {
const relationData = computed(() => { const relationData = computed(() => {
const item = props.value; const item = props.value;
if (item?.source === 'relation') { if(notifyType.value === 'email'){
if(item && Array.isArray(item) && item.length){
if(item[0].source === 'relation'){
return item.map(i => i?.relation?.objectId)
}
}
} else if (item?.source === 'relation') {
const relation = item?.relation; const relation = item?.relation;
if (relation) { if (relation) {
if (relation.objectId) { if (relation.objectId) {
@ -268,14 +272,15 @@ const getUser = async (_source: string, _triggerType: string) => {
key: 'p2', key: 'p2',
selectable: false, selectable: false,
children: relationResp.result.map((item: any) => { children: relationResp.result.map((item: any) => {
treeDataMap.set(item.id, item) const obj = {
return {
...item, ...item,
value: item.id, value: item.id,
key: item.id, key: item.id,
title: item.name, title: item.name,
isRelation: true, isRelation: true,
}; }
treeDataMap.set(item.id, obj)
return obj
}), }),
}); });
} }
@ -302,7 +307,7 @@ const getObj = (
objectType: 'device', objectType: 'device',
objectSource: { objectSource: {
source: 'upper', source: 'upper',
upperKey: 'deviceId', upperKey: 'scene.deviceId',
}, },
related: { related: {
objectType: 'user', objectType: 'user',
@ -324,7 +329,7 @@ const getObj = (
const onChange = ( const onChange = (
_source: string = 'fixed', _source: string = 'fixed',
_value?: string | string[], _value?: string | string[],
isRelation?: boolean, // isRelation?: boolean,
_name?: string, _name?: string,
) => { ) => {
let _values: any = undefined; let _values: any = undefined;
@ -332,16 +337,15 @@ const onChange = (
const _names: string[] = Array.isArray(_name) ? _name : [_name || '']; const _names: string[] = Array.isArray(_name) ? _name : [_name || ''];
if (Array.isArray(_value)) { if (Array.isArray(_value)) {
if (props?.notify?.notifyType === 'email') { if (props?.notify?.notifyType === 'email') {
if (isRelation) { _values = _value.map((item) => {
const arr = _value.map((item) => { return {
const _item = labelMap.get(item); source: "relation",
_names.push(_item?.name || ''); relation:{
return getObj('relation', item, _item?.relation); objectType: "user",
}); objectId: item
_values = arr; }
} else { }
_values = getObj(_source, _value, false); });
}
} }
} else { } else {
const item = treeDataMap.get(_value) const item = treeDataMap.get(_value)

View File

@ -20,7 +20,7 @@ export const getParams = (params: Params, sceneModel: FormModelType): Promise<an
if (resp.success) { if (resp.success) {
res(resp.result as any[]) res(resp.result as any[])
} }
}) }).catch(() => res([]))
}) })
} }

View File

@ -42,6 +42,7 @@ export enum ActionDeviceSelector {
'fixed' = 'fixed', 'fixed' = 'fixed',
'tag' = 'tag', 'tag' = 'tag',
'relation' = 'relation', 'relation' = 'relation',
'context' = 'context',
} }
export enum ActionDeviceSource { export enum ActionDeviceSource {
@ -236,6 +237,7 @@ export interface NotifyProps {
templateId: string; templateId: string;
variables: Record<string, NotifyVariablesType>; variables: Record<string, NotifyVariablesType>;
options?: Record<string, any>; options?: Record<string, any>;
changeData?: Boolean
} }
export type SelectorValuesType = export type SelectorValuesType =
@ -264,6 +266,7 @@ export interface ActionsDeviceProps {
upperKey?: string; upperKey?: string;
/** 来源为relation时不能为空 */ /** 来源为relation时不能为空 */
relation?: any; relation?: any;
changeData?: boolean
} }
export interface BranchesThen { export interface BranchesThen {
@ -321,6 +324,7 @@ export interface FormModelType {
* , * ,
*/ */
options?: Record<string, any>; options?: Record<string, any>;
triggerType?: 'device' | 'manual' | 'timer'
description?: string; description?: string;
} }

View File

@ -1913,6 +1913,7 @@ function changeBackUpload(info: UploadChangeParam<UploadFile<any>>) {
message.error('logo上传失败请稍后再试'); message.error('logo上传失败请稍后再试');
} }
} }
function clearNullProp(obj: object) { function clearNullProp(obj: object) {
if (typeof obj !== 'object') return; if (typeof obj !== 'object') return;
for (const prop in obj) { for (const prop in obj) {
@ -1930,15 +1931,19 @@ function clearNullProp(obj: object) {
* @param value * @param value
*/ */
const validateIP = (_rule: Rule, value: string) => { const validateIP = (_rule: Rule, value: string) => {
const ipList = value?.split(/[\n,]/g).filter((i: string) => i && i.trim()); if (value) {
const errorIPList = ipList.filter( const ipList = value?.split(/[\n,]/g).filter((i: string) => i && i.trim());
(f: string) => !testIP(f.replace(/\s*/g, '')), const errorIPList = ipList?.filter(
); (f: string) => !testIP(f.replace(/\s*/g, '')),
return new Promise((resolve, reject) => { );
!errorIPList.length return new Promise((resolve, reject) => {
? resolve('') !errorIPList?.length
: reject(`[${errorIPList}]不是正确的IP地址`); ? resolve('')
}); : reject(`[${errorIPList}]不是正确的IP地址`);
});
} else {
return Promise.resolve()
}
}; };
</script> </script>

View File

@ -48,7 +48,18 @@
<j-form-item name="base-path"> <j-form-item name="base-path">
<template #label> <template #label>
<span>base-path</span> <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 <img
class="img-style" class="img-style"
:src=" :src="
@ -59,7 +70,7 @@
</template> </template>
<j-input <j-input
v-model:value="formValue['base-path']" v-model:value="formValue['base-path']"
placeholder="输入base-path" placeholder="{http/https}: //{前端所在服务器IP地址}:{前端暴露的服务端口}/api"
/> />
</j-form-item> </j-form-item>
<j-row :gutter="24" :span="24"> <j-row :gutter="24" :span="24">

View File

@ -2,7 +2,7 @@
<div class="product-container"> <div class="product-container">
<pro-search <pro-search
:columns="columns" :columns="columns"
target="category" target="category-device"
@search="(params:any)=>queryParams = {...params}" @search="(params:any)=>queryParams = {...params}"
style='margin-bottom: 0;' style='margin-bottom: 0;'
/> />

View File

@ -2,7 +2,7 @@
<div class="product-container"> <div class="product-container">
<pro-search <pro-search
:columns="columns" :columns="columns"
target="category" target="category-product"
@search="(params:any)=>queryParams = {...params}" @search="(params:any)=>queryParams = {...params}"
style='margin-bottom: 0;' style='margin-bottom: 0;'
/> />

View File

@ -2,7 +2,7 @@
<div> <div>
<pro-search <pro-search
:columns="columns" :columns="columns"
target="category" target="category-user"
@search="handleParams" @search="handleParams"
style='margin-bottom: 0;' style='margin-bottom: 0;'
/> />

View File

@ -149,7 +149,7 @@
:model="form.data" :model="form.data"
class="basic-form permiss-form" class="basic-form permiss-form"
> >
<j-form-item name="accessSupport" required> <j-form-item name="accessSupport" required v-if="isNoCommunity">
<template #label> <template #label>
<span style="margin-right: 3px">数据权限控制</span> <span style="margin-right: 3px">数据权限控制</span>
<j-tooltip title="此菜单页面数据所对应的资产类型"> <j-tooltip title="此菜单页面数据所对应的资产类型">
@ -263,7 +263,6 @@ import { FormInstance } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components'; import { message } from 'jetlinks-ui-components';
import ChooseIconDialog from '../components/ChooseIconDialog.vue'; import ChooseIconDialog from '../components/ChooseIconDialog.vue';
import PermissChoose from '../components/PermissChoose.vue'; import PermissChoose from '../components/PermissChoose.vue';
import { import {
getMenuTree_api, getMenuTree_api,
getAssetsType_api, getAssetsType_api,
@ -273,6 +272,7 @@ import {
validCode_api, validCode_api,
} from '@/api/system/menu'; } from '@/api/system/menu';
import { Rule } from 'ant-design-vue/lib/form'; import { Rule } from 'ant-design-vue/lib/form';
import { isNoCommunity } from '@/utils/utils';
const permission = 'system/Menu'; const permission = 'system/Menu';
// //

View File

@ -38,6 +38,7 @@ import {
import { message } from 'jetlinks-ui-components'; import { message } from 'jetlinks-ui-components';
import { modeType } from '../typing'; import { modeType } from '../typing';
import { useDepartmentStore } from '@/store/department'; import { useDepartmentStore } from '@/store/department';
import { onlyMessage } from '@/utils/comm';
const department = useDepartmentStore(); const department = useDepartmentStore();
const emits = defineEmits([ const emits = defineEmits([
@ -136,11 +137,16 @@ const save = async () => {
// emits('refresh'); // emits('refresh');
// }); // });
// fix: bug#10829 // fix: bug#10829
removeKeys.length && (await delOperations_api(removeKeys)); if(addKeys.length || removeKeys.length) {
const res = await addOperations_api(addKeys); removeKeys.length && (await delOperations_api(removeKeys));
if (res.success) { const res = await addOperations_api(addKeys);
message.success('操作成功'); if (res.success) {
emits('refresh'); message.success('操作成功');
emits('refresh');
}
} else {
onlyMessage('请选择API接口','error')
return
} }
} else if (props.mode === 'appManger') { } else if (props.mode === 'appManger') {
const removeItems = removeKeys.map((key) => ({ const removeItems = removeKeys.map((key) => ({

View File

@ -109,6 +109,7 @@ import {
USER_CENTER_MENU_CODE, USER_CENTER_MENU_CODE,
MESSAGE_SUBSCRIBE_MENU_CODE MESSAGE_SUBSCRIBE_MENU_CODE
} from '@/utils/consts' } from '@/utils/consts'
import { isNoCommunity } from '@/utils/utils'
const emits = defineEmits(['update:selectItems']); const emits = defineEmits(['update:selectItems']);
const route = useRoute(); const route = useRoute();
@ -117,6 +118,7 @@ const props = defineProps({
}); });
const treeRef = ref(); const treeRef = ref();
let { ctx: that, proxy } = getCurrentInstance(); let { ctx: that, proxy } = getCurrentInstance();
const columns = [ const columns = [
{ {
title: '菜单权限', title: '菜单权限',
@ -130,13 +132,16 @@ const columns = [
key: 'action', key: 'action',
width: '260px', width: '260px',
}, },
{ ];
if(isNoCommunity){
columns.push({
title: '数据权限', title: '数据权限',
dataIndex: 'data', dataIndex: 'data',
key: 'data', key: 'data',
width: '50%', width: '50%',
}, })
]; }
const tableData = ref<tableItemType[]>([]); const tableData = ref<tableItemType[]>([]);
// - // -

View File

@ -270,6 +270,7 @@ type modalType = '' | 'add' | 'edit' | 'reset';
const handleParams = (params: any) => { const handleParams = (params: any) => {
const newParams = (params?.terms as any[])?.map((item1) => { const newParams = (params?.terms as any[])?.map((item1) => {
let arr: any[] = []
item1.terms = item1.terms.map((item2: any) => { item1.terms = item1.terms.map((item2: any) => {
if (['telephone', 'email'].includes(item2.column)) { if (['telephone', 'email'].includes(item2.column)) {
return { return {
@ -277,8 +278,27 @@ const handleParams = (params: any) => {
value: [item2], value: [item2],
}; };
} }
if (['type'].includes(item2.column) && item2.value === 'other') {
arr = [
{
...item2,
type: 'or',
termType: 'isnull',
value: 1,
},
{
...item2,
type: 'or',
termType: 'empty',
value: 1,
}
]
}
return item2; return item2;
}); });
if(arr.length){
item1.terms = [...item1.terms, ...arr]
}
return item1; return item1;
}); });
queryParams.value = { terms: newParams || [] }; queryParams.value = { terms: newParams || [] };

View File

@ -3823,10 +3823,10 @@ jetlinks-store@^0.0.3:
resolved "https://registry.npmjs.org/jetlinks-store/-/jetlinks-store-0.0.3.tgz" resolved "https://registry.npmjs.org/jetlinks-store/-/jetlinks-store-0.0.3.tgz"
integrity sha512-AZf/soh1hmmwjBZ00fr1emuMEydeReaI6IBTGByQYhTmK1Zd5pQAxC7WLek2snRAn/HHDgJfVz2hjditKThl6Q== integrity sha512-AZf/soh1hmmwjBZ00fr1emuMEydeReaI6IBTGByQYhTmK1Zd5pQAxC7WLek2snRAn/HHDgJfVz2hjditKThl6Q==
jetlinks-ui-components@^1.0.13: jetlinks-ui-components@^1.0.16:
version "1.0.13" version "1.0.16"
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.13.tgz#9f33fe41d9c453b4db01a3d5e0a86412c13cf652" resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.16.tgz#bdb65385a30a121065322e5156c13080c8328080"
integrity sha512-50QFseYmoN1OnvwbMI735q3MlUWW8tASGScfxUKDCD4c7EBr/tgU9exdW8i++ggbjvTfa8d3Lhg+L2gggLtnUQ== integrity sha512-R3oE8tpXW4oaNSCeGXRK++paNJiHYDO89Id3YqzIVX6/bWMItOWrEU6JT4iPA9uYkPTfsYHxnG5qZRloLnpiZw==
dependencies: dependencies:
"@vueuse/core" "^9.12.0" "@vueuse/core" "^9.12.0"
"@vueuse/router" "^9.13.0" "@vueuse/router" "^9.13.0"