feat: 优化场景联动-执行动作-数据发生变化时触发校验

This commit is contained in:
xieyonghong 2023-05-16 18:06:39 +08:00
parent 7b4fb484bf
commit 92530b6b88
9 changed files with 277 additions and 42 deletions

View File

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

View File

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

View File

@ -3,7 +3,7 @@
</template>
<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 { storeToRefs } from 'pinia';
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 noticeTemplate from '@/api/notice/template'
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 { data: _data } = storeToRefs(sceneStore);
@ -34,21 +38,6 @@ const props = defineProps({
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 = () => {
formItemContext.onFieldChange()
}
@ -59,22 +48,29 @@ const formTouchOff = () => {
const checkDeviceDelete = async () => {
const item = _data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device
const proResp = await queryProductList({ terms: [{ terms: [{ column: 'id', termType: 'eq', value: item!.productId }]}]})
const productDetail = proResp?.result?.data?.[0]
if (proResp.success && (proResp.result as any)?.total === 0 && item && item.productId) { //
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.productId = undefined
formTouchOff()
return
}
const metadata = JSON.parse(productDetail?.metadata || '{}')
if (item?.selector === 'fixed') {
const deviceList = item!.selectorValues?.map(item => item.value) || []
const deviceResp = await deviceQuery({ terms: [{ terms: [{ column: 'id', termType: 'in', value: deviceList.toString() }]}]})
if (deviceResp.success && (deviceResp.result as any)?.total < (item!.selectorValues?.length || 0)) { //
let hasDevice = false
if (item!.selectorValues) {
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)
}
console.log('hasDevice', item!.selectorValues)
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!.changeData = true
formTouchOff()
return
}
}
console.log(item!.source, props.name)
if (item!.source === 'upper') { // id
} else if (item!.selector === 'context') { // id
if (props.name === 0) {
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].device!.upperKey = undefined
formTouchOff()
@ -83,10 +79,99 @@ const checkDeviceDelete = async () => {
const prevItem = _data.value.branches![props.branchesName].then[props.thenName].actions[props.name - 1].device
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!.changeData = true
formTouchOff()
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 +180,114 @@ const checkDeviceDelete = async () => {
*/
const checkNoticeDelete = async () => {
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 }]}]})
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 = ''
formTouchOff()
return
}
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 = ''
formTouchOff()
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 = () => {
@ -133,6 +314,10 @@ subscribe()
check()
defineExpose({
formTouchOff
})
</script>
<style scoped>

View File

@ -5,7 +5,7 @@
:rules='rules'
>
<div class="actions-item">
<CheckItem v-bind='props'>
<CheckItem v-bind='props' ref='checkItemRef'>
<div class="item-options-warp">
<div class="item-options-type" @click="onAdd">
<img
@ -330,7 +330,7 @@
</j-popconfirm>
</CheckItem>
</div>
</j-form-item>
</j-form-item>
<template v-if="!isLast && type === 'serial'">
<div
:class="[
@ -361,7 +361,7 @@
</div>
</div>
</template>
<!-- 编辑 -->
<template v-if="visible">
<Modal
:name="name"
@ -373,6 +373,7 @@
@save="onSave"
/>
</template>
<!-- 编辑 -->
<template>
<ActionTypeComponent
v-bind="props"
@ -405,7 +406,6 @@ import FilterGroup from './FilterGroup.vue';
import { randomString } from '@/utils/utils'
import { EventEmitter, EventEmitterKeys } from '@/views/rule-engine/Scene/Save/util'
import CheckItem from './CheckItem.vue'
import { Form } from 'jetlinks-ui-components'
const sceneStore = useSceneStore();
const { data: _data } = storeToRefs(sceneStore);
@ -441,7 +441,7 @@ const props = defineProps({
});
const emit = defineEmits(['delete', 'update']);
const checkItemRef = ref()
const visible = ref<boolean>(false);
const triggerVisible = ref<boolean>(false);
const actionType = ref('');
@ -450,7 +450,6 @@ const eventEmitterKey = ref(EventEmitterKeys({
branchGroup: props.thenName,
action: props.name
}))
const formItemContext = Form.useInjectFormItemContext()
const termsOptions = computed(() => {
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)
checkItemRef.value?.formTouchOff?.()
visible.value = false;
EventEmitter.emit(eventEmitterKey.value, data) //
};
@ -559,7 +557,22 @@ const rules = [{
validator(_: any, v?: ActionsType) {
console.log('validator-action-item',v)
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('该数据已发生变更,请重新配置'))
}
}

View File

@ -1,4 +1,7 @@
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();
iconMap.set('trigger', getImage('/scene/action-bind-icon.png'));
@ -25,4 +28,33 @@ export const typeIconMap = {
READ_PROPERTY: 'icon-zhihangdongzuodu',
INVOKE_FUNCTION: 'icon-zhihangdongzuoxie-1',
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>
</template>
<script lang="ts" setup>
<script lang="ts" setup name='UpdateActionItemModal'>
import { getImage } from '@/utils/comm';
import Delay from '../Delay/index.vue';
import Notify from '../Notify/index.vue';

View File

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

View File

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

View File

@ -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.13:
version "1.0.13"
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.13.tgz#9f33fe41d9c453b4db01a3d5e0a86412c13cf652"
integrity sha512-50QFseYmoN1OnvwbMI735q3MlUWW8tASGScfxUKDCD4c7EBr/tgU9exdW8i++ggbjvTfa8d3Lhg+L2gggLtnUQ==
jetlinks-ui-components@^1.0.15:
version "1.0.15"
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.15.tgz#fa007e685ead81ed0eb81250c5ecd3909f8b223d"
integrity sha512-ydgWz4Ee80V57yWnudRuGJASnuQULpbLAczY0NRybsVgLNf88N4FGqAug3XKBtsLn8xkPwNDlSLBCEs9baLv5Q==
dependencies:
"@vueuse/core" "^9.12.0"
"@vueuse/router" "^9.13.0"