feat: 场景联动新增过滤条件组件

This commit is contained in:
xieyonghong 2023-03-15 16:27:36 +08:00
parent a28f46e01c
commit 2cd4d7ddb1
5 changed files with 356 additions and 38 deletions

View File

@ -0,0 +1,254 @@
<template>
<div class='terms-params-item'>
<div v-if='!isFirst' class='term-type-warp'>
<DropdownButton
:options='[
{ label: "并且", value: "and" },
{ label: "或者", value: "or" },
]'
type='type'
v-model:value='paramsValue.type'
/>
</div>
<div
class='params-item_button'
@mouseover='mouseover'
@mouseout='mouseout'
>
<DropdownButton
:options='columnOptions'
icon='icon-zhihangdongzuoxie-1'
type='column'
value-name='column'
label-name='fullName'
placeholder='请选择参数'
v-model:value='paramsValue.column'
component='treeSelect'
@select='columnSelect'
/>
<DropdownButton
:options='termTypeOptions'
type="termType"
value-name='id'
label-name='name'
placeholder="操作符"
v-model:value='paramsValue.termType'
@select='termsTypeSelect'
/>
<DoubleParamsDropdown
v-if='showDouble'
icon='icon-canshu'
placeholder='参数值'
:options='valueOptions'
:metricOptions='metricOption'
:tabsOptions='tabsOptions'
v-model:value='paramsValue.value.value'
v-model:source='paramsValue.value.source'
/>
<ParamsDropdown
v-else
icon='icon-canshu'
placeholder='参数值'
:options='valueOptions'
:metricOptions='metricOption'
:tabsOptions='tabsOptions'
v-model:value='paramsValue.value.value'
v-model:source='paramsValue.value.source'
/>
<j-popconfirm title='确认删除?' @confirm='onDelete'>
<div v-show='showDelete' class='button-delete'> <AIcon type='CloseOutlined' /></div>
</j-popconfirm>
</div>
<div class='term-add' @click.stop='termAdd' v-if='isLast'>
<div class='terms-content'>
<AIcon type='PlusOutlined' style='font-size: 12px' />
</div>
</div>
</div>
</template>
<script setup lang='ts' name='FilterCondition'>
import type { PropType } from 'vue'
import type { TermsType } from '@/views/rule-engine/Scene/typings'
import DropdownButton from '../../components/DropdownButton'
import { getOption } from '../../components/DropdownButton/util'
import ParamsDropdown, { DoubleParamsDropdown } from '../../components/ParamsDropdown'
import { inject } from 'vue'
import { ContextKey } from '../../components/Terms/util'
import { useSceneStore } from 'store/scene'
import { storeToRefs } from 'pinia';
const sceneStore = useSceneStore()
const { data: formModel } = storeToRefs(sceneStore)
type Emit = {
(e: 'update:value', data: TermsType): void
}
type TabsOption = {
label: string;
key: string;
component: string
}
const props = defineProps({
isFirst: {
type: Boolean,
default: true
},
isLast: {
type: Boolean,
default: true
},
showDeleteBtn: {
type: Boolean,
default: true
},
name: {
type: Number,
default: 0
},
termsName: {
type: Number,
default: 0
},
branchName: {
type: Number,
default: 0
},
thenName: {
type: Number,
default: 0
},
value: {
type: Object as PropType<TermsType>,
default: () => ({
column: '',
type: '',
termType: 'eq',
value: {
source: 'fixed',
value: undefined
}
})
}
})
const emit = defineEmits<Emit>()
const paramsValue = reactive<TermsType>({
column: props.value.column,
type: props.value.type,
termType: props.value.termType,
value: props.value.value
})
const showDelete = ref(false)
const columnOptions: any = inject(ContextKey) //
const termTypeOptions = ref<Array<{ id: string, name: string}>>([]) //
const valueOptions = ref<any[]>([]) //
const metricOption = ref<any[]>([]) //
const tabsOptions = ref<Array<TabsOption>>([{ label: '内置参数', key: 'upper', component: 'tree' }])
// { label: '', key: 'fixed', component: 'string' },
const handOptionByColumn = (option: any) => {
if (option) {
termTypeOptions.value = option.termTypes || []
metricOption.value = option.metrics || []
tabsOptions.value.length = 1
tabsOptions.value[0].component = option.dataType
if (option.metrics && option.metrics.length) {
tabsOptions.value.push(
{ label: '指标值', key: 'metric', component: 'select' }
)
}
if (option.dataType === 'boolean') {
valueOptions.value = [
{ label: '是', value: true },
{ label: '否', value: false },
]
} else if(option.dataType === 'enum') {
valueOptions.value = option.options?.map((item: any) => ({ ...item, label: item.name, value: item.id})) || []
} else{
valueOptions.value = option.options || []
}
} else {
termTypeOptions.value = []
metricOption.value = []
valueOptions.value = []
}
}
watchEffect(() => {
const option = getOption(columnOptions.value, paramsValue.column, 'column')
handOptionByColumn(option)
})
const showDouble = computed(() => {
const isRange = paramsValue.termType ? ['nbtw', 'btw', 'in', 'nin'].includes(paramsValue.termType) : false
if (metricOption.value.length) {
metricOption.value = metricOption.value.filter(item => isRange ? item.range : !item.range)
} else {
metricOption.value = []
}
return isRange
})
const mouseover = () => {
if (props.showDeleteBtn){
showDelete.value = true
}
}
const mouseout = () => {
if (props.showDeleteBtn){
showDelete.value = false
}
}
const columnSelect = () => {
paramsValue.termType = 'eq'
paramsValue.value = {
source: tabsOptions.value[0].key,
value: undefined
}
emit('update:value', { ...paramsValue })
}
const termsTypeSelect = () => {
paramsValue.value = {
source: tabsOptions.value[0].key,
value: undefined
}
emit('update:value', { ...paramsValue })
}
const termAdd = () => {
const terms = {
column: undefined,
value: {
source: 'fixed',
value: undefined
},
termType: undefined,
type: 'and',
key: `params_${new Date().getTime()}`
}
formModel.value.branches?.[props.branchName]?.then?.[props.thenName]?.actions?.[props.name].terms?.push(terms)
}
const onDelete = () => {
formModel.value.branches?.[props.branchName]?.then?.[props.thenName]?.actions?.[props.name].terms?.splice(props.name, 1)
}
nextTick(() => {
Object.assign(paramsValue, props.value)
})
</script>
<style scoped>
</style>

View File

@ -0,0 +1,13 @@
<template>
</template>
<script setup lang='ts' name='FilterGroup'>
export default {
name: 'FilterGroup'
}
</script>
<style scoped>
</style>

View File

@ -318,16 +318,26 @@
</j-popconfirm> </j-popconfirm>
</div> </div>
<template v-if="!isLast && type === 'serial'"> <template v-if="!isLast && type === 'serial'">
<div class="actions-item-filter-warp"> <div :class='["actions-item-filter-warp", termsOptions.length ? "filter-border" : ""]'>
<!-- filter-border --> <template v-if='termsOptions.length'>
满足此条件后执行后续动作 <div class='actions-item-filter-warp-tip'>
满足此条件后执行后续动作
</div>
<div class='actions-item-filter-overflow'>
</div>
</template>
<div v-else class='filter-add-button'>
<AIcon type='PlusOutlined' style='padding-right: 4px;' />
<span>添加过滤条件</span>
</div>
</div> </div>
</template> </template>
<!-- 编辑 --> <!-- 编辑 -->
<template v-if="visible"> <template v-if="visible">
<Modal <Modal
:name="name" :name="name"
:branchGroup="branchGroup" :branchGroup="thenName"
:branchesName="branchesName" :branchesName="branchesName"
:data="data" :data="data"
@cancel="onClose" @cancel="onClose"
@ -352,7 +362,6 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { getImage } from '@/utils/comm';
import { isBoolean } from 'lodash-es'; import { isBoolean } from 'lodash-es';
import { PropType } from 'vue'; import { PropType } from 'vue';
import { ActionsType, ParallelType } from '../../../typings'; import { ActionsType, ParallelType } from '../../../typings';
@ -361,6 +370,7 @@ import ActionTypeComponent from '../Modal/ActionTypeComponent.vue';
import TriggerAlarm from '../TriggerAlarm/index.vue'; import TriggerAlarm from '../TriggerAlarm/index.vue';
import { useSceneStore } from '@/store/scene'; import { useSceneStore } from '@/store/scene';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { iconMap, itemNotifyIconMap, typeIconMap } from './util'
const sceneStore = useSceneStore(); const sceneStore = useSceneStore();
const { data: _data } = storeToRefs(sceneStore); const { data: _data } = storeToRefs(sceneStore);
@ -370,9 +380,9 @@ const props = defineProps({
type: Number, type: Number,
default: 0, default: 0,
}, },
branchGroup: { thenName: {
type: Number, type: Number,
default: 0, default: 0,
}, },
name: { name: {
type: Number, type: Number,
@ -397,37 +407,17 @@ const props = defineProps({
const emit = defineEmits(['delete', 'update']); const emit = defineEmits(['delete', 'update']);
const iconMap = new Map();
iconMap.set('trigger', getImage('/scene/action-bind-icon.png'));
iconMap.set('notify', getImage('/scene/action-notify-icon.png'));
iconMap.set('device', getImage('/scene/action-device-icon.png'));
iconMap.set('relieve', getImage('/scene/action-unbind-icon.png'));
iconMap.set('delay', getImage('/scene/action-delay-icon.png'));
const itemNotifyIconMap = new Map();
itemNotifyIconMap.set(
'dingTalk',
getImage('/scene/notify-item-img/dingtalk.png'),
);
itemNotifyIconMap.set('weixin', getImage('/scene/notify-item-img/weixin.png'));
itemNotifyIconMap.set('email', getImage('/scene/notify-item-img/email.png'));
itemNotifyIconMap.set('voice', getImage('/scene/notify-item-img/voice.png'));
itemNotifyIconMap.set('sms', getImage('/scene/notify-item-img/sms.png'));
itemNotifyIconMap.set(
'webhook',
getImage('/scene/notify-item-img/webhook.png'),
);
const typeIconMap = {
READ_PROPERTY: 'icon-zhihangdongzuodu',
INVOKE_FUNCTION: 'icon-zhihangdongzuoxie-1',
WRITE_PROPERTY: 'icon-zhihangdongzuoxie',
};
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('');
const termsOptions = computed(() => {
if (!props.parallel) { //
return _data.value.branches![props.branchesName].then?.[props.thenName].actions?.[props.name].terms || []
}
return []
})
const onDelete = () => { const onDelete = () => {
emit('delete'); emit('delete');
}; };
@ -463,6 +453,7 @@ const onPropsOk = (data: ActionsType, options?: any) => {
const onPropsCancel = () => { const onPropsCancel = () => {
actionType.value = ''; actionType.value = '';
}; };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@ -582,6 +573,19 @@ const onPropsCancel = () => {
border-radius: 2px; border-radius: 2px;
} }
.actions-item-filter-warp-tip {
position: absolute;
top: 0;
left: 16px;
z-index: 2;
color: rgba(0, 0, 0, 0.55);
font-weight: 800;
font-size: 14px;
line-height: 1;
background-color: #fff;
transform: translateY(-50%);
}
.actions-item-filter-overflow { .actions-item-filter-overflow {
display: flex; display: flex;
padding-top: 4px; padding-top: 4px;
@ -590,6 +594,13 @@ const onPropsCancel = () => {
row-gap: 16px; row-gap: 16px;
} }
.filter-add-button{
width: 100%;
color: rgba(0, 0, 0, 0.3);
text-align: center;
cursor: pointer;
}
.terms-params { .terms-params {
// display: inline-block; // display: inline-block;
display: flex; display: flex;

View File

@ -5,7 +5,7 @@
:parallel="parallel" :parallel="parallel"
:data="item" :data="item"
:branchesName="branchesName" :branchesName="branchesName"
:branchGroup="parallel ? 1 : 0" :thenName="thenName"
:name="index" :name="index"
:type="type" :type="type"
:isLast="index === actions.length - 1" :isLast="index === actions.length - 1"
@ -25,7 +25,7 @@
@cancel="onCancel" @cancel="onCancel"
:parallel="parallel" :parallel="parallel"
:name="actions.length" :name="actions.length"
:branchGroup="parallel ? 1 : 0" :branchGroup="thenName"
@save="onSave" @save="onSave"
:branchesName="branchesName" :branchesName="branchesName"
/> />
@ -38,6 +38,11 @@ import { ActionsType, ParallelType } from '../../../typings';
import Modal from '../Modal/index.vue'; import Modal from '../Modal/index.vue';
import Item from './Item.vue'; import Item from './Item.vue';
import { pick } from 'lodash'; import { pick } from 'lodash';
import { useSceneStore } from '@/store/scene';
import { storeToRefs } from 'pinia';
const sceneStore = useSceneStore();
const { data: _data } = storeToRefs(sceneStore);
interface ListProps { interface ListProps {
branchesName: number; branchesName: number;
@ -47,7 +52,10 @@ interface ListProps {
} }
const props = defineProps({ const props = defineProps({
branchesName: Number, branchesName: {
type: Number,
default: 0
},
type: { type: {
type: String as PropType<ListProps['type']>, type: String as PropType<ListProps['type']>,
default: 'serial', default: 'serial',
@ -63,6 +71,10 @@ const emit = defineEmits(['delete', 'add']);
const visible = ref<boolean>(false); const visible = ref<boolean>(false);
const thenName = computed(() => {
return _data.value.branches![props.branchesName].then.findIndex(item => item.parallel === props.parallel)
})
const onAdd = () => { const onAdd = () => {
visible.value = true; visible.value = true;
}; };

View File

@ -0,0 +1,28 @@
import { getImage } from '@/utils/comm'
export const iconMap = new Map();
iconMap.set('trigger', getImage('/scene/action-bind-icon.png'));
iconMap.set('notify', getImage('/scene/action-notify-icon.png'));
iconMap.set('device', getImage('/scene/action-device-icon.png'));
iconMap.set('relieve', getImage('/scene/action-unbind-icon.png'));
iconMap.set('delay', getImage('/scene/action-delay-icon.png'));
export const itemNotifyIconMap = new Map();
itemNotifyIconMap.set(
'dingTalk',
getImage('/scene/notify-item-img/dingtalk.png'),
);
itemNotifyIconMap.set('weixin', getImage('/scene/notify-item-img/weixin.png'));
itemNotifyIconMap.set('email', getImage('/scene/notify-item-img/email.png'));
itemNotifyIconMap.set('voice', getImage('/scene/notify-item-img/voice.png'));
itemNotifyIconMap.set('sms', getImage('/scene/notify-item-img/sms.png'));
itemNotifyIconMap.set(
'webhook',
getImage('/scene/notify-item-img/webhook.png'),
);
export const typeIconMap = {
READ_PROPERTY: 'icon-zhihangdongzuodu',
INVOKE_FUNCTION: 'icon-zhihangdongzuoxie-1',
WRITE_PROPERTY: 'icon-zhihangdongzuoxie',
};