update: 优化场景联动-执行动作、过滤条件校验

This commit is contained in:
xieyonghong 2023-03-20 17:31:26 +08:00
parent a094b9b093
commit faccbc53f7
14 changed files with 196 additions and 127 deletions

View File

@ -33,7 +33,10 @@ export const defaultBranches = [
terms: [ terms: [
{ {
column: undefined, column: undefined,
value: undefined, value: {
source: 'fixed',
value: undefined
},
termType: undefined, termType: undefined,
key: 'params_1', key: 'params_1',
type: 'and', type: 'and',
@ -89,6 +92,8 @@ export const useSceneStore = defineStore('scene', () => {
branches = cloneDeep(defaultBranches) branches = cloneDeep(defaultBranches)
if (triggerType === 'device') { if (triggerType === 'device') {
branches.push(null) branches.push(null)
} else {
branches[0].when.length = []
} }
} else { } else {
const branchesLength = branches.length; const branchesLength = branches.length;

View File

@ -59,10 +59,12 @@ import { detail as deviceDetail } from '@/api/device/instance'
import Product from './Product.vue' import Product from './Product.vue'
import DeviceSelect from './DeviceSelect.vue' import DeviceSelect from './DeviceSelect.vue'
import Type from './Type.vue' import Type from './Type.vue'
import {continuousValue, handleTimerOptions, timeUnitEnum} from '@/views/rule-engine/Scene/Save/components/Timer/util' import { handleTimerOptions } from '@/views/rule-engine/Scene/Save/components/Timer/util'
import { Form } from 'jetlinks-ui-components'
type Emit = { type Emit = {
(e: 'cancel'): void (e: 'cancel'): void
(e: 'change', data: TriggerDevice): void
(e: 'save', data: TriggerDevice, options: Record<string, any>): void (e: 'save', data: TriggerDevice, options: Record<string, any>): void
} }
@ -76,6 +78,7 @@ interface AddModelType extends Omit<TriggerDevice, 'selectorValues'> {
metadata: metadataType, metadata: metadataType,
operator: TriggerDeviceOptions operator: TriggerDeviceOptions
} }
const formItemContext = Form.useInjectFormItemContext();
const emit = defineEmits<Emit>() const emit = defineEmits<Emit>()
const typeRef = ref() const typeRef = ref()
@ -165,26 +168,6 @@ const handleOptions = (data: TriggerDeviceOptions) => {
_options.when = when; _options.when = when;
_options.time = time; _options.time = time;
_options.extraTime = extraTime; _options.extraTime = extraTime;
// if (_timer.trigger === 'cron') {
// _options.time = _timer.cron;
// } else {
// // console.log('continuousValue', continuousValue(_timer.when! || [], _timer!.trigger))
// let whenStr = '';
// if (_timer.when!.length) {
// whenStr = _timer!.trigger === 'week' ? '' : '';
// const whenStrArr = continuousValue(_timer.when! || [], _timer!.trigger);
// const whenStrArr3 = whenStrArr.splice(0, 3);
// whenStr += whenStrArr3.join('');
// whenStr += `${_timer.when!.length}`;
// }
// _options.when = whenStr;
// if (_timer.once) {
// _options.time = _timer.once.time + ' 1';
// } else if (_timer.period) {
// _options.time = _timer.period.from + '-' + _timer.period.to;
// _options.extraTime = `${_timer.period.every}${timeUnitEnum[_timer.period.unit]}1`;
// }
// }
} }
if (data.operator === 'online') { if (data.operator === 'online') {
@ -263,6 +246,7 @@ const save = async (step?: number) => {
productId: addModel.productId productId: addModel.productId
} }
emit('save', data, _options) emit('save', data, _options)
formItemContext.onFieldChange()
} }
} }
} }

View File

@ -75,7 +75,6 @@ type Emit = {
} }
const params = ref({}) const params = ref({})
const context = inject('SceneDeviceAddModel')
const props = defineProps({ const props = defineProps({
rowKeys: { rowKeys: {
type: Array as PropType<SelectorValuesItem[]>, type: Array as PropType<SelectorValuesItem[]>,

View File

@ -2,7 +2,7 @@
<div class='device'> <div class='device'>
<j-form-item <j-form-item
:rules='rules' :rules='rules'
name='device' :name='["trigger", "device"]'
> >
<template #label> <template #label>
<TitleComponent data='触发规则' style='font-size: 14px;' /> <TitleComponent data='触发规则' style='font-size: 14px;' />
@ -13,9 +13,9 @@
> >
<Title :options='data.options.trigger' /> <Title :options='data.options.trigger' />
</AddButton> </AddButton>
<AddModel v-if='visible' @cancel='visible = false' @save='save' :value='data.trigger.device' :options='data.options.trigger' />
</j-form-item> </j-form-item>
<Terms /> <Terms />
<AddModel v-if='visible' @cancel='visible = false' @save='save' :value='data.trigger.device' :options='data.options.trigger' />
</div> </div>
</template> </template>
@ -28,6 +28,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'
const sceneStore = useSceneStore() const sceneStore = useSceneStore()
const { data } = storeToRefs(sceneStore) const { data } = storeToRefs(sceneStore)
@ -45,6 +46,7 @@ const rules = [{
const save = (device: TriggerDevice, options: Record<string, any>) => { const save = (device: TriggerDevice, options: Record<string, any>) => {
data.value.trigger!.device = device data.value.trigger!.device = device
data.value.options!.trigger = options data.value.options!.trigger = options
visible.value = false
} }
</script> </script>

View File

@ -79,6 +79,7 @@ const onActionUpdate = (_data: any, type: boolean) => {
const save = (_data: OperationTimer, options: Record<string, any>) => { const save = (_data: OperationTimer, options: Record<string, any>) => {
data.value.trigger!.timer = _data data.value.trigger!.timer = _data
data.value.options!.trigger = options data.value.options!.trigger = options
visible.value = false
} }
</script> </script>

View File

@ -172,7 +172,6 @@ const onDelete = () => {
const rules = [ const rules = [
{ {
validator(_: any, v?: Record<string, any>) { validator(_: any, v?: Record<string, any>) {
console.log('-----v',v)
if (v !== undefined) { if (v !== undefined) {
if (!Object.keys(v).length) { if (!Object.keys(v).length) {
return Promise.reject(new Error('该数据已发生变更,请重新配置')) return Promise.reject(new Error('该数据已发生变更,请重新配置'))

View File

@ -349,7 +349,7 @@
/> />
</div> </div>
</template> </template>
<div v-else class="filter-add-button"> <div v-else class="filter-add-button" @click='addFilterParams'>
<AIcon type="PlusOutlined" style="padding-right: 4px" /> <AIcon type="PlusOutlined" style="padding-right: 4px" />
<span>添加过滤条件</span> <span>添加过滤条件</span>
</div> </div>
@ -394,6 +394,7 @@ import { useSceneStore } from '@/store/scene';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { iconMap, itemNotifyIconMap, typeIconMap } from './util'; import { iconMap, itemNotifyIconMap, typeIconMap } from './util';
import FilterGroup from './FilterGroup.vue'; import FilterGroup from './FilterGroup.vue';
import { randomString } from '@/utils/utils'
const sceneStore = useSceneStore(); const sceneStore = useSceneStore();
const { data: _data } = storeToRefs(sceneStore); const { data: _data } = storeToRefs(sceneStore);
@ -471,6 +472,30 @@ const onSave = (data: ActionsType, options: any) => {
visible.value = false; visible.value = false;
}; };
const addFilterParams = () => {
const item: any = {
type: 'and',
key: randomString(),
terms: [
{
column: undefined,
value: {
type: 'fixed',
value: undefined
},
termType: undefined,
type: 'and',
key: randomString()
}
]
}
if (_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].terms) {
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].terms!.push(item)
} else {
_data.value.branches![props.branchesName].then[props.thenName].actions[props.name].terms = [item]
}
}
const onAdd = () => { const onAdd = () => {
visible.value = true; visible.value = true;
}; };

View File

@ -1,57 +1,57 @@
<template> <template>
<div class="actions"> <div class='actions'>
<div class="actions-title"> <div class='actions-title'>
<span>执行</span> <span>执行</span>
<ShakeLimit <ShakeLimit
v-if="props.openShakeLimit" v-if='props.openShakeLimit'
v-model:value="FormModel.branches[name].shakeLimit" v-model:value='FormModel.branches[name].shakeLimit'
/> />
</div> </div>
<div class="actions-warp"> <div class='actions-warp'>
<j-collapse v-model:activeKey="activeKeys"> <j-collapse v-model:activeKey='activeKeys'>
<j-collapse-panel key="1"> <j-collapse-panel key='1'>
<template #header> <template #header>
<span> <span>
串行 串行
<span class="panel-tip"> <span class='panel-tip'>
按顺序依次执行动作适用于基于动作输出参数判断是否执行后续动作的场景 按顺序依次执行动作适用于基于动作输出参数判断是否执行后续动作的场景
</span> </span>
</span> </span>
</template> </template>
<div class="actions-list"> <div class='actions-list'>
<List <List
type="serial" type='serial'
:branchesName="name" :branchesName='name'
:parallel="false" :parallel='false'
:actions=" :actions='
serialArray.length ? serialArray[0].actions : [] serialArray.length ? serialArray[0].actions : []
" '
@add="(_item) => onAdd(_item, false)" @add='(_item) => onAdd(_item, false)'
@delete="(_key) => onDelete(_key, false)" @delete='(_key) => onDelete(_key, false)'
/> />
</div> </div>
</j-collapse-panel> </j-collapse-panel>
<j-collapse-panel key="2"> <j-collapse-panel key='2'>
<template #header> <template #header>
<span> <span>
并行 并行
<span class="panel-tip"> <span class='panel-tip'>
同时执行所有动作适用于不需要关注执行动作先后顺序和结果的场景 同时执行所有动作适用于不需要关注执行动作先后顺序和结果的场景
</span> </span>
</span> </span>
</template> </template>
<div class="actions-list"> <div class='actions-list'>
<List <List
type="parallel" type='parallel'
:branchesName="name" :branchesName='name'
:parallel="true" :parallel='true'
:actions=" :actions='
parallelArray.length parallelArray.length
? parallelArray[0].actions ? parallelArray[0].actions
: [] : []
" '
@add="(_item) => onAdd(_item, true)" @add='(_item) => onAdd(_item, true)'
@delete="(_key) => onDelete(_key, true)" @delete='(_key) => onDelete(_key, true)'
/> />
</div> </div>
</j-collapse-panel> </j-collapse-panel>
@ -60,64 +60,66 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang='ts' setup>
import ShakeLimit from '../components/ShakeLimit/index.vue'; import ShakeLimit from '../components/ShakeLimit/index.vue'
import { List } from './ListItem'; import { List } from './ListItem'
import { BranchesThen } from '../../typings' import { BranchesThen } from '../../typings'
import { PropType } from 'vue'; import { PropType } from 'vue'
import { randomString } from '@/utils/utils'; import { randomString } from '@/utils/utils'
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia'
import { useSceneStore } from 'store/scene' import { useSceneStore } from 'store/scene'
import { Form } from 'jetlinks-ui-components'
const sceneStore = useSceneStore() const sceneStore = useSceneStore()
const { data: FormModel } = storeToRefs(sceneStore) const { data: FormModel } = storeToRefs(sceneStore)
const formItemContext = Form.useInjectFormItemContext();
const props = defineProps({ const props = defineProps({
name: { name: {
type: Number, type: Number,
default: 0 default: 0
}, },
thenOptions: { thenOptions: {
type: Array as PropType<BranchesThen[]>, type: Array as PropType<BranchesThen[]>,
default: () => [], default: () => []
}, },
openShakeLimit: { openShakeLimit: {
type: Boolean, type: Boolean,
default: false default: false
}, }
}); })
const emit = defineEmits(['update', 'add']); const emit = defineEmits(['update', 'add'])
const activeKeys = ref<string[]>(['1']); const activeKeys = ref<string[]>(['1'])
const parallelArray = ref<BranchesThen[]>([]); const parallelArray = ref<BranchesThen[]>([])
const serialArray = ref<BranchesThen[]>([]); const serialArray = ref<BranchesThen[]>([])
const lock = ref<boolean>(false); const lock = ref<boolean>(false)
watch( watch(
() => props.thenOptions, () => props.thenOptions,
(newVal) => { (newVal) => {
parallelArray.value = newVal.filter((item) => item.parallel); parallelArray.value = newVal.filter((item) => item.parallel)
serialArray.value = newVal.filter((item) => !item.parallel); serialArray.value = newVal.filter((item) => !item.parallel)
const isSerialActions = serialArray.value.some((item) => { const isSerialActions = serialArray.value.some((item) => {
return !!item.actions.length; return !!item.actions.length
}); })
if ( if (
!lock.value && !lock.value &&
parallelArray.value.length && parallelArray.value.length &&
(!serialArray.value.length || !isSerialActions) (!serialArray.value.length || !isSerialActions)
) { ) {
activeKeys.value = ['2']; activeKeys.value = ['2']
lock.value = true; lock.value = true
} }
}, },
{ {
deep: true, deep: true,
immediate: true, immediate: true
}, }
); )
const onDelete = (_key: string, _parallel: boolean) => { const onDelete = (_key: string, _parallel: boolean) => {
const thenName = props.thenOptions.findIndex(item => item.parallel === _parallel) const thenName = props.thenOptions.findIndex(item => item.parallel === _parallel)
@ -125,7 +127,8 @@ const onDelete = (_key: string, _parallel: boolean) => {
if (actionIndex !== -1) { if (actionIndex !== -1) {
FormModel.value.branches?.[props.name].then?.[thenName].actions.splice(actionIndex!, 1) FormModel.value.branches?.[props.name].then?.[thenName].actions.splice(actionIndex!, 1)
} }
}; formItemContext.onFieldChange()
}
const onAdd = (actionItem: any, _parallel: boolean) => { const onAdd = (actionItem: any, _parallel: boolean) => {
const thenName = props.thenOptions.findIndex(item => item.parallel === _parallel) const thenName = props.thenOptions.findIndex(item => item.parallel === _parallel)
@ -146,30 +149,31 @@ const onAdd = (actionItem: any, _parallel: boolean) => {
} }
FormModel.value.branches?.[props.name].then.push(newThenItem) FormModel.value.branches?.[props.name].then.push(newThenItem)
} }
}; formItemContext.onFieldChange()
}
</script> </script>
<style lang="less" scoped> <style lang='less' scoped>
.actions { .actions {
.actions-title { .actions-title {
display: flex; display: flex;
gap: 16px; gap: 16px;
align-items: center; align-items: center;
margin-bottom: 16px; margin-bottom: 16px;
font-weight: 800; font-weight: 800;
font-size: 14px; font-size: 14px;
line-height: 32px; line-height: 32px;
} }
.actions-warp { .actions-warp {
.actions-list { .actions-list {
width: 100%; width: 100%;
}
} }
}
.panel-tip { .panel-tip {
padding-left: 8px; padding-left: 8px;
color: rgba(#000, 0.45); color: rgba(#000, 0.45);
} }
} }
</style> </style>

View File

@ -56,7 +56,7 @@
<Action <Action
:name='name' :name='name'
:openShakeLimit="true" :openShakeLimit="true"
:thenOptions='FormModel.branches[name].then' :thenOptions='FormModel.branches[name]?.then'
/> />
</j-form-item> </j-form-item>
</div> </div>
@ -64,7 +64,7 @@
</div> </div>
</template> </template>
<script lang='ts' setup name='Branchs'> <script lang='ts' setup name='Branches'>
import type { PropType } from 'vue' import type { PropType } from 'vue'
import type { ActionBranchesProps } from '@/views/rule-engine/Scene/typings' import type { ActionBranchesProps } from '@/views/rule-engine/Scene/typings'
import TermsItem from './TermsItem.vue' import TermsItem from './TermsItem.vue'

View File

@ -44,6 +44,7 @@
:tabsOptions='tabsOptions' :tabsOptions='tabsOptions'
v-model:value='paramsValue.value.value' v-model:value='paramsValue.value.value'
v-model:source='paramsValue.value.source' v-model:source='paramsValue.value.source'
@select='valueSelect'
/> />
<ParamsDropdown <ParamsDropdown
v-else v-else
@ -54,6 +55,7 @@
:tabsOptions='tabsOptions' :tabsOptions='tabsOptions'
v-model:value='paramsValue.value.value' v-model:value='paramsValue.value.value'
v-model:source='paramsValue.value.source' v-model:source='paramsValue.value.source'
@select='valueSelect'
/> />
<j-popconfirm title='确认删除?' @confirm='onDelete'> <j-popconfirm title='确认删除?' @confirm='onDelete'>
<div v-show='showDelete' class='button-delete'> <AIcon type='CloseOutlined' /></div> <div v-show='showDelete' class='button-delete'> <AIcon type='CloseOutlined' /></div>
@ -77,9 +79,11 @@ import { inject } from 'vue'
import { ContextKey } from './util' import { ContextKey } from './util'
import { useSceneStore } from 'store/scene' import { useSceneStore } from 'store/scene'
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { Form } from 'jetlinks-ui-components'
const sceneStore = useSceneStore() const sceneStore = useSceneStore()
const { data: formModel } = storeToRefs(sceneStore) const { data: formModel } = storeToRefs(sceneStore)
const formItemContext = Form.useInjectFormItemContext();
type Emit = { type Emit = {
(e: 'update:value', data: TermsType): void (e: 'update:value', data: TermsType): void
@ -216,6 +220,7 @@ const columnSelect = () => {
value: undefined value: undefined
} }
emit('update:value', { ...paramsValue }) emit('update:value', { ...paramsValue })
formItemContext.onFieldChange()
} }
const termsTypeSelect = (e: { key: string }) => { const termsTypeSelect = (e: { key: string }) => {
@ -225,6 +230,11 @@ const termsTypeSelect = (e: { key: string }) => {
value: value value: value
} }
emit('update:value', { ...paramsValue }) emit('update:value', { ...paramsValue })
formItemContext.onFieldChange()
}
const valueSelect = () => {
formItemContext.onFieldChange()
} }
const termAdd = () => { const termAdd = () => {
@ -247,6 +257,7 @@ const onDelete = () => {
nextTick(() => { nextTick(() => {
Object.assign(paramsValue, props.value) Object.assign(paramsValue, props.value)
formItemContext.onFieldChange()
}) })
</script> </script>

View File

@ -23,7 +23,7 @@
@deleteAll='branchesDeleteAll' @deleteAll='branchesDeleteAll'
/> />
<div v-else class='actions-terms-warp' :style='{ marginTop: data.branches.length === 2 ? 0 : 24 }'> <div v-else class='actions-terms-warp' :style='{ marginTop: data.branches.length === 2 ? 0 : 24 }'>
<div class='actions-terms-title' style='padding: 0'> <div class='actions-terms-title' style='padding: 0;margin-bottom: 24px;'>
否则 否则
</div> </div>
<div class='actions-terms-options no-when'> <div class='actions-terms-options no-when'>
@ -43,11 +43,10 @@ import { provide } from 'vue'
import { ContextKey, handleParamsData } from './util' import { ContextKey, handleParamsData } from './util'
import { getParseTerm } from '@/api/rule-engine/scene' import { getParseTerm } from '@/api/rule-engine/scene'
import type { FormModelType } from '@/views/rule-engine/Scene/typings' import type { FormModelType } from '@/views/rule-engine/Scene/typings'
import Branches from './Branchs.vue' import Branches from './Branches.vue'
const sceneStore = useSceneStore() const sceneStore = useSceneStore()
const { data } = storeToRefs(sceneStore) const { data } = storeToRefs(sceneStore)
const open = ref(false) const open = ref(false)
const columnOptions = ref<any>([]) const columnOptions = ref<any>([])
@ -96,7 +95,6 @@ const branchesDeleteAll = () => {
} }
watchEffect(() => { watchEffect(() => {
console.log(data.value.trigger, data.value.trigger?.device)
if (data.value.trigger?.device) { if (data.value.trigger?.device) {
queryColumn(data.value) queryColumn(data.value)
} }

View File

@ -29,6 +29,7 @@
v-for='(item, index) in termsData' v-for='(item, index) in termsData'
:key='item.key' :key='item.key'
:name='["branches", branchName, "when", whenName, "terms", index]' :name='["branches", branchName, "when", whenName, "terms", index]'
:rules='rules'
> >
<ParamsItem <ParamsItem
v-model:value='formModel.branches[branchName].when[whenName].terms[index]' v-model:value='formModel.branches[branchName].when[whenName].terms[index]'
@ -60,6 +61,7 @@ import DropdownButton from '../DropdownButton'
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useSceneStore } from 'store/scene' import { useSceneStore } from 'store/scene'
import ParamsItem from './ParamsItem.vue' import ParamsItem from './ParamsItem.vue'
import { isArray } from 'lodash-es'
const sceneStore = useSceneStore() const sceneStore = useSceneStore()
const { data: formModel } = storeToRefs(sceneStore) const { data: formModel } = storeToRefs(sceneStore)
@ -103,6 +105,41 @@ const props = defineProps({
} }
}) })
const rules = [
{
validator(_: any, v: any) {
if (v !== undefined) {
if (!Object.keys(v).length) {
return Promise.reject(new Error('该数据已发生变更,请重新配置'));
}
if (!v.column) {
return Promise.reject(new Error('请选择参数'));
}
if (!v.termType) {
return Promise.reject(new Error('请选择操作符'));
}
if (v.value === undefined) {
return Promise.reject(new Error('请选择或输入参数值'));
} else {
if (
isArray(v.value.value) &&
v.value.value.some((_v: any) => _v === undefined)
) {
return Promise.reject(new Error('请选择或输入参数值'));
} else if (v.value.value === undefined) {
return Promise.reject(new Error('请选择或输入参数值'));
}
}
} else {
return Promise.reject(new Error('请选择参数'));
}
return Promise.resolve();
},
}
]
const showDelete = ref(false) const showDelete = ref(false)
const termsData = computed(() => { const termsData = computed(() => {

View File

@ -131,11 +131,15 @@
.ant-form-item { .ant-form-item {
margin-bottom: 8px; margin-bottom: 8px;
&:not(:first-child) { &:not(:nth-child(2)) {
.ant-form-item-explain-error { .ant-form-item-explain-error {
padding-left: 80px !important; padding-left: 80px !important;
} }
} }
&.ant-form-item-has-error {
margin-bottom: 0;
}
} }
} }

View File

@ -93,10 +93,10 @@ export default defineConfig(({ mode}) => {
// target: 'http://192.168.33.22:8800', // target: 'http://192.168.33.22:8800',
// target: 'http://192.168.32.244:8881', // target: 'http://192.168.32.244:8881',
// target: 'http://47.112.135.104:5096', // opcua // target: 'http://47.112.135.104:5096', // opcua
target: 'http://120.77.179.54:8844', // 120测试 target: 'http://192.168.33.46:8844', // 120测试
// target: 'http://47.108.63.174:8845', // 测试 // target: 'http://47.108.63.174:8845', // 测试
// target: 'http://120.77.179.54:8844', // target: 'http://120.77.179.54:8844',
ws: 'ws://120.77.179.54:8844', ws: 'ws://192.168.33.46:8844',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '') rewrite: (path) => path.replace(/^\/api/, '')
} }