Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev

This commit is contained in:
easy 2023-03-15 17:41:45 +08:00
commit 46df7d35c5
25 changed files with 601 additions and 270 deletions

View File

@ -22,12 +22,8 @@
><TopCard ><TopCard
title="当前在线" title="当前在线"
:footer="onlineFooter" :footer="onlineFooter"
:value="onlineToday" :value="deviceOnline"
> >
<!-- <BarChart
:chartXData="barChartXData"
:chartYData="barChartYData"
></BarChart> -->
<Charts :options="onlineOptions"></Charts> </TopCard <Charts :options="onlineOptions"></Charts> </TopCard
></j-col> ></j-col>
<j-col :span="6" <j-col :span="6"
@ -140,6 +136,9 @@ const quickBtnList = [
{ label: '近一月', value: 'month' }, { label: '近一月', value: 'month' },
{ label: '近一年', value: 'year' }, { label: '近一年', value: 'year' },
]; ];
/**
* 获取产品数量
*/
const getProductData = () => { const getProductData = () => {
if (menuStore.hasMenu('device/Product')) { if (menuStore.hasMenu('device/Product')) {
productCount().then((res) => { productCount().then((res) => {
@ -174,6 +173,9 @@ const getProductData = () => {
} }
}; };
getProductData(); getProductData();
/**
* 获取设备数量
*/
const getDeviceData = () => { const getDeviceData = () => {
if (menuStore.hasMenu('device/Instance')) { if (menuStore.hasMenu('device/Instance')) {
deviceCount().then((res) => { deviceCount().then((res) => {
@ -197,6 +199,9 @@ const getDeviceData = () => {
} }
}; };
getDeviceData(); getDeviceData();
/**
* 获取在线数量
*/
const getOnline = () => { const getOnline = () => {
dashboard([ dashboard([
{ {
@ -222,7 +227,8 @@ const getOnline = () => {
const onlineYdata = y; const onlineYdata = y;
onlineYdata.reverse(); onlineYdata.reverse();
setOnlineChartOpition(x, onlineYdata); setOnlineChartOpition(x, onlineYdata);
deviceFooter.value[0].value = y?.[1]; onlineFooter.value[0].value = y?.[1];
console.log(res.result);
} }
}); });
}; };

View File

@ -23,7 +23,7 @@
<j-row type="flex"> <j-row type="flex">
<j-col flex="180px"> <j-col flex="180px">
<j-form-item name="photoUrl"> <j-form-item name="photoUrl">
<JUpload v-model="form.photoUrl" /> <Upload v-model="form.photoUrl" />
</j-form-item> </j-form-item>
<!-- <j-form-item> <!-- <j-form-item>
<div class="upload-image-warp-logo"> <div class="upload-image-warp-logo">

View File

@ -566,31 +566,31 @@ const route = useRoute();
// //
const formData = ref({ const formData = ref({
id: route.query.id || undefined, id: route.query.id || undefined,
// name: '', // name: undefined,
cascadeName: '', cascadeName: undefined,
proxyStream: false, proxyStream: false,
// , sipConfigs[{}] // , sipConfigs[{}]
clusterNodeId: '', clusterNodeId: undefined,
name: '', name: undefined,
sipId: '', sipId: undefined,
domain: '', domain: undefined,
remoteAddress: '', remoteAddress: undefined,
remotePort: undefined, remotePort: undefined,
localSipId: '', localSipId: undefined,
host: '', host: undefined,
port: undefined, port: undefined,
// remotePublic: { // remotePublic: {
// host: '', // host: undefined,
// port: undefined, // port: undefined,
// }, // },
publicHost: '', publicHost: undefined,
publicPort: undefined, publicPort: undefined,
transport: 'UDP', transport: 'UDP',
user: '', user: undefined,
password: '', password: undefined,
manufacturer: '', manufacturer: undefined,
model: '', model: undefined,
firmware: '', firmware: undefined,
keepaliveInterval: '60', keepaliveInterval: '60',
registerInterval: '3600', registerInterval: '3600',
}); });

View File

@ -13,19 +13,22 @@
import templateApi from '@/api/notice/template'; import templateApi from '@/api/notice/template';
type Emits = { type Emits = {
(e: 'update:toParty', data: string): void; (e: 'update:toParty', data: string | undefined): void;
}; };
type Props = {
toParty: string | undefined;
type: string | undefined;
configId: string | undefined;
}
const emit = defineEmits<Emits>(); const emit = defineEmits<Emits>();
const props = defineProps({ const props = defineProps<Props>();
toParty: { type: String, default: '' },
type: { type: String, default: '' },
configId: { type: String, default: '' },
});
const _value = computed({ const _value = computed({
get: () => props.toParty, get: () => props.toParty,
set: (val: string) => emit('update:toParty', val), set: (val: string | undefined) => emit('update:toParty', val),
}); });
const typeObj = { const typeObj = {
weixin: 'wechat', weixin: 'wechat',

View File

@ -13,19 +13,21 @@
import templateApi from '@/api/notice/template'; import templateApi from '@/api/notice/template';
type Emits = { type Emits = {
(e: 'update:toTag', data: string): void; (e: 'update:toTag', data: string | undefined): void;
}; };
type Props = {
toTag: string | undefined;
type: string | undefined;
configId: string | undefined;
}
const emit = defineEmits<Emits>(); const emit = defineEmits<Emits>();
const props = defineProps({ const props = defineProps<Props>();
toTag: { type: String, default: '' },
type: { type: String, default: '' },
configId: { type: String, default: '' },
});
const _value = computed({ const _value = computed({
get: () => props.toTag, get: () => props.toTag,
set: (val: string) => emit('update:toTag', val), set: (val: string | undefined) => emit('update:toTag', val),
}); });
const options = ref([]); const options = ref([]);

View File

@ -13,19 +13,21 @@
import templateApi from '@/api/notice/template'; import templateApi from '@/api/notice/template';
type Emits = { type Emits = {
(e: 'update:toUser', data: string): void; (e: 'update:toUser', data: string | undefined): void;
}; };
type Props = {
toUser: string | undefined;
type: string | undefined;
configId: string | undefined;
}
const emit = defineEmits<Emits>(); const emit = defineEmits<Emits>();
const props = defineProps({ const props = defineProps<Props>();
toUser: { type: String, default: '' },
type: { type: String, default: '' },
configId: { type: String, default: '' },
});
const _value = computed({ const _value = computed({
get: () => props.toUser, get: () => props.toUser,
set: (val: string) => emit('update:toUser', val), set: (val: string | undefined) => emit('update:toUser', val),
}); });
const typeObj = { const typeObj = {

View File

@ -820,49 +820,49 @@ const resetPublicFiles = () => {
formData.value.template = {}; formData.value.template = {};
switch (formData.value.provider) { switch (formData.value.provider) {
case 'dingTalkMessage': case 'dingTalkMessage':
formData.value.template.agentId = ''; formData.value.template.agentId = undefined;
formData.value.template.message = ''; formData.value.template.message = undefined;
formData.value.template.departmentIdList = ''; formData.value.template.departmentIdList = undefined;
formData.value.template.userIdList = ''; formData.value.template.userIdList = undefined;
break; break;
case 'dingTalkRobotWebHook': case 'dingTalkRobotWebHook':
formData.value.template.message = ''; formData.value.template.message = undefined;
formData.value.template.messageType = 'markdown'; formData.value.template.messageType = 'markdown';
formData.value.template.markdown = { text: '', title: '' }; formData.value.template.markdown = { text: undefined, title: undefined };
break; break;
case 'corpMessage': case 'corpMessage':
formData.value.template.agentId = ''; formData.value.template.agentId = undefined;
formData.value.template.message = ''; formData.value.template.message = undefined;
formData.value.template.toParty = ''; formData.value.template.toParty = undefined;
formData.value.template.toUser = ''; formData.value.template.toUser = undefined;
formData.value.template.toTag = ''; formData.value.template.toTag = undefined;
break; break;
case 'embedded': case 'embedded':
formData.value.template.subject = ''; formData.value.template.subject = undefined;
formData.value.template.message = ''; formData.value.template.message = undefined;
formData.value.template.text = ''; formData.value.template.text = undefined;
formData.value.template.sendTo = []; formData.value.template.sendTo = [];
formData.value.template.attachments = []; formData.value.template.attachments = [];
break; break;
case 'aliyun': case 'aliyun':
formData.value.template.templateType = 'tts'; formData.value.template.templateType = 'tts';
formData.value.template.templateCode = ''; formData.value.template.templateCode = undefined;
formData.value.template.ttsCode = ''; formData.value.template.ttsCode = undefined;
// formData.value.template.message = ''; // formData.value.template.message = undefined;
formData.value.template.ttsmessage = ''; formData.value.template.ttsmessage = undefined;
formData.value.template.playTimes = 1; formData.value.template.playTimes = 1;
formData.value.template.calledShowNumbers = ''; formData.value.template.calledShowNumbers = undefined;
formData.value.template.calledNumber = ''; formData.value.template.calledNumber = undefined;
break; break;
case 'aliyunSms': case 'aliyunSms':
formData.value.template.code = ''; formData.value.template.code = undefined;
formData.value.template.message = ''; formData.value.template.message = undefined;
formData.value.template.phoneNumber = ''; formData.value.template.phoneNumber = undefined;
formData.value.template.signName = ''; formData.value.template.signName = undefined;
break; break;
case 'http': case 'http':
formData.value.template.contextAsBody = true; formData.value.template.contextAsBody = true;
formData.value.template.body = ''; formData.value.template.body = undefined;
break; break;
} }

View File

@ -94,42 +94,42 @@ export const MSG_TYPE = {
export const CONFIG_FIELD_MAP = { export const CONFIG_FIELD_MAP = {
dingTalk: { dingTalk: {
dingTalkMessage: { dingTalkMessage: {
appKey: '', appKey: undefined,
appSecret: '', appSecret: undefined,
}, },
dingTalkRobotWebHook: { dingTalkRobotWebHook: {
url: '', url: undefined,
} }
}, },
weixin: { weixin: {
corpMessage: { corpMessage: {
corpId: '', corpId: undefined,
corpSecret: '', corpSecret: undefined,
}, },
// officialMessage: {}, // officialMessage: {},
}, },
email: { email: {
embedded: { embedded: {
host: '', host: undefined,
port: 25, port: 25,
ssl: false, ssl: false,
sender: '', sender: undefined,
username: '', username: undefined,
password: '', password: undefined,
} }
}, },
voice: { voice: {
aliyun: { aliyun: {
regionId: '', regionId: undefined,
accessKeyId: '', accessKeyId: undefined,
secret: '', secret: undefined,
} }
}, },
sms: { sms: {
aliyunSms: { aliyunSms: {
regionId: '', regionId: undefined,
accessKeyId: '', accessKeyId: undefined,
secret: '', secret: undefined,
} }
}, },
webhook: { webhook: {
@ -145,69 +145,69 @@ export const CONFIG_FIELD_MAP = {
export const TEMPLATE_FIELD_MAP = { export const TEMPLATE_FIELD_MAP = {
dingTalk: { dingTalk: {
dingTalkMessage: { dingTalkMessage: {
agentId: '', agentId: undefined,
message: '', message: undefined,
departmentIdList: '', departmentIdList: undefined,
userIdList: '' userIdList: undefined
}, },
dingTalkRobotWebHook: { dingTalkRobotWebHook: {
message: '', message: undefined,
messageType: 'markdown', messageType: 'markdown',
markdown: { markdown: {
text: '', text: undefined,
title: '', title: undefined,
}, },
link: { link: {
title: '', title: undefined,
picUrl: '', picUrl: undefined,
messageUrl: '', messageUrl: undefined,
text: '', text: undefined,
}, },
} }
}, },
weixin: { weixin: {
corpMessage: { corpMessage: {
agentId: '', agentId: undefined,
message: '', message: undefined,
toParty: '', toParty: undefined,
toUser: '', toUser: undefined,
toTag: '', toTag: undefined,
}, },
officialMessage: {}, officialMessage: {},
}, },
email: { email: {
embedded: { embedded: {
subject: '', subject: undefined,
sendTo: [], sendTo: [],
attachments: [], attachments: [],
message: '', message: undefined,
text: '', text: undefined,
} }
}, },
voice: { voice: {
aliyun: { aliyun: {
templateType: 'tts', templateType: 'tts',
templateCode: '', templateCode: undefined,
ttsCode: '', ttsCode: undefined,
// message: '', // message: undefined,
ttsmessage: '', ttsmessage: undefined,
playTimes: 1, playTimes: 1,
calledShowNumbers: '', calledShowNumbers: undefined,
calledNumber: '', calledNumber: undefined,
} }
}, },
sms: { sms: {
aliyunSms: { aliyunSms: {
code: '', code: undefined,
message: '', message: undefined,
phoneNumber: '', phoneNumber: undefined,
signName: '', signName: undefined,
} }
}, },
webhook: { webhook: {
http: { http: {
contextAsBody: true, contextAsBody: true,
body: '' body: undefined
} }
}, },
}; };

View File

@ -53,9 +53,8 @@ const props = defineProps({
const productList = ref<Record<string, any>[]>([]); const productList = ref<Record<string, any>[]>([]);
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
const formRef = ref(); const formRef = ref();
let id = ref<string>();
const modelRef = ref(); const modelRef = ref();
modelRef.value = props.data modelRef.value = {...props.data};
const rules = { const rules = {
name: [ name: [
{ {

View File

@ -245,6 +245,9 @@ const getActions = (
title: '查看', title: '查看',
}, },
icon: 'EyeOutlined', icon: 'EyeOutlined',
onClick: () => {
openRuleEditor(data);
}
}, },
{ {
key: 'action', key: 'action',

View File

@ -15,7 +15,6 @@
</AddButton> </AddButton>
</a-form-item> </a-form-item>
<Terms /> <Terms />
<Action />
<AddModel v-if='visible' @cancel='visible = false' @save='save' :value='data.trigger.device' :options='data.options.trigger' /> <AddModel v-if='visible' @cancel='visible = false' @save='save' :value='data.trigger.device' :options='data.options.trigger' />
</div> </div>
</template> </template>

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',
};

View File

@ -4,7 +4,7 @@
<span>执行</span> <span>执行</span>
<ShakeLimit <ShakeLimit
v-if="props.openShakeLimit" v-if="props.openShakeLimit"
v-model:value="shakeLimit" v-model:value="FormModel.branches[name].shakeLimit"
/> />
</div> </div>
<div class="actions-warp"> <div class="actions-warp">
@ -21,7 +21,7 @@
<div class="actions-list"> <div class="actions-list">
<List <List
type="serial" type="serial"
:branchesName="props.name" :branchesName="name"
:parallel="false" :parallel="false"
:actions=" :actions="
serialArray.length ? serialArray[0].actions : [] serialArray.length ? serialArray[0].actions : []
@ -43,7 +43,7 @@
<div class="actions-list"> <div class="actions-list">
<List <List
type="parallel" type="parallel"
:branchesName="props.name" :branchesName="name"
:parallel="true" :parallel="true"
:actions=" :actions="
parallelArray.length parallelArray.length
@ -63,30 +63,32 @@
<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 { useSceneStore } from 'store/scene'
interface ActionsProps { const sceneStore = useSceneStore()
name: number; const { data: FormModel } = storeToRefs(sceneStore)
openShakeLimit?: boolean;
thenOptions: BranchesThen[];
}
const props = defineProps({ const props = defineProps({
name: Number, name: {
type: Number,
default: 0
},
thenOptions: { thenOptions: {
type: Array as PropType<ActionsProps['thenOptions']>, type: Array as PropType<BranchesThen[]>,
default: () => [], default: () => [],
}, },
openShakeLimit: Boolean, openShakeLimit: {
type: Boolean,
default: false
},
}); });
const emit = defineEmits(['update', 'add']); const emit = defineEmits(['update', 'add']);
const shakeLimit = ref({
enabled: false,
});
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[]>([]);
@ -110,8 +112,6 @@ watch(
activeKeys.value = ['2']; activeKeys.value = ['2'];
lock.value = true; lock.value = true;
} }
//TODO
}, },
{ {
deep: true, deep: true,
@ -120,46 +120,31 @@ watch(
); );
const onDelete = (_key: string, _parallel: boolean) => { const onDelete = (_key: string, _parallel: boolean) => {
const newArray = _parallel ? [...parallelArray.value] : [...serialArray.value]; const thenName = props.thenOptions.findIndex(item => item.parallel === _parallel)
const aIndex = newArray[0].actions?.findIndex( const actionIndex = FormModel.value.branches?.[props.name].then?.[thenName].actions.findIndex(item => item.key === _key)
(aItem) => aItem.key === _key, if (actionIndex !== -1) {
); FormModel.value.branches?.[props.name].then?.[thenName].actions.splice(actionIndex!, 1)
if (aIndex !== -1) { }
if(_parallel){
parallelArray.value[0].actions?.splice(aIndex, 1);
emit('update', parallelArray.value[0], _parallel);
} else {
serialArray.value[0].actions?.splice(aIndex, 1);
emit('update', serialArray.value[0], _parallel);
}
}
}; };
const onAdd = (actionItem: any, _parallel: boolean) => { const onAdd = (actionItem: any, _parallel: boolean) => {
const newArray = _parallel ? [...parallelArray.value] : [...serialArray.value]; const thenName = props.thenOptions.findIndex(item => item.parallel === _parallel)
if (newArray.length) { if (thenName !== -1) { //
const indexOf = newArray[0].actions?.findIndex( const cacheAction = props.thenOptions[thenName].actions
(aItem) => aItem.key === actionItem.key, const indexOf = cacheAction?.findIndex(item => item.key === actionItem.key) || -1
); if (indexOf !== -1) {
if (indexOf !== -1) { FormModel.value.branches?.[props.name].then?.[thenName].actions.splice(indexOf, 1, actionItem)
newArray[0].actions.splice(indexOf, 1, actionItem);
} else {
newArray[0].actions.push(actionItem);
}
if(_parallel){
parallelArray.value = [...newArray];
} else {
serialArray.value = [...newArray];
}
emit('update', newArray[0], _parallel);
} else { } else {
actionItem.key = randomString(); FormModel.value.branches?.[props.name].then?.[thenName].actions.push(actionItem)
emit('add', {
parallel: _parallel,
key: randomString(),
actions: [actionItem],
});
} }
} else { //
const newThenItem = {
parallel: _parallel,
key: randomString(),
actions: [actionItem]
}
FormModel.value.branches?.[props.name].then.push(newThenItem)
}
}; };
</script> </script>

View File

@ -15,18 +15,18 @@
</div> </div>
<template #overlay> <template #overlay>
<div class='scene-select-content'> <div class='scene-select-content'>
<template v-if='options.length'> <DropdownTimePicker
v-if='["date","time"].includes(component)'
:type='component'
@change='timeSelect'
/>
<template v-else-if='options.length'>
<drop-menus <drop-menus
v-if='component === "select"' v-if='component === "select"'
:value='selectValue' :value='selectValue'
:options='options' :options='options'
@click='menuSelect' @click='menuSelect'
/> />
<DropdownTimePicker
v-else-if='["date","time"].includes(component)'
:type='component'
@change='timeSelect'
/>
<div style='min-width: 400px' v-else> <div style='min-width: 400px' v-else>
<j-tree <j-tree
:selectedKeys='selectValue ? [selectValue] : []' :selectedKeys='selectValue ? [selectValue] : []'

View File

@ -6,6 +6,7 @@
:icon='icon' :icon='icon'
:placeholder='placeholder' :placeholder='placeholder'
:tabs-options='tabsOptions' :tabs-options='tabsOptions'
:metricOptions='metricOptions'
@select='onSelect' @select='onSelect'
/> />
<ParamsDropdown <ParamsDropdown
@ -14,6 +15,7 @@
:icon='icon' :icon='icon'
:placeholder='placeholder' :placeholder='placeholder'
:tabs-options='tabsOptions' :tabs-options='tabsOptions'
:metricOptions='metricOptions'
:options='options' :options='options'
@select='onSelect' @select='onSelect'
/> />

View File

@ -27,35 +27,45 @@
v-model:value='myValue' v-model:value='myValue'
@change='timeChange' @change='timeChange'
/> />
<DropdownMenus <template
v-else-if='["select","enum", "boolean"].includes(item.component)' v-else-if='["select","enum", "boolean"].includes(item.component)'
:options='["metric", "upper"].includes(item.key) ? metricOption : options'
@click='onSelect'
/>
<div
v-else-if='item.component === "tree"'
style='min-width: 400px'
> >
<j-tree <DropdownMenus
:selectedKeys='myValue ? [myValue] : []' v-if='(["metric", "upper"].includes(item.key) ? metricOptions : options).length'
:treeData='item.key === "upper" ? metricOption : options' :options='["metric", "upper"].includes(item.key) ? metricOptions : options'
@select='treeSelect' @click='onSelect'
:height='450' />
:virtual='true' <div class='scene-select-empty' v-else>
> <j-empty />
<template #title="{ name, description }"> </div>
<j-space> </template>
{{ name }} <template v-else-if='item.component === "tree"'>
<span v-if='description' class='tree-title-description'>{{ description }}</span> <div style='min-width: 400px' v-if='(item.key === "upper" ? metricOptions : options).length'>
</j-space> <j-tree
</template> :selectedKeys='myValue ? [myValue] : []'
</j-tree> :treeData='item.key === "upper" ? metricOptions : options'
</div> @select='treeSelect'
:height='450'
:virtual='true'
>
<template #title="{ name, description }">
<j-space>
{{ name }}
<span v-if='description' class='tree-title-description'>{{ description }}</span>
</j-space>
</template>
</j-tree>
</div>
<div class='scene-select-empty' v-else>
<j-empty />
</div>
</template>
<ValueItem <ValueItem
v-else v-else
v-model:modelValue='myValue' v-model:modelValue='myValue'
:itemType='item.component' :itemType='item.component'
:options='item.key === "upper" ? metricOption : options' :options='item.key === "upper" ? metricOptions : options'
@change='valueItemChange' @change='valueItemChange'
/> />
</div> </div>
@ -73,8 +83,6 @@ import { defaultSetting } from './typings'
import { DropdownMenus, DropdownTimePicker} from '../DropdownButton' import { DropdownMenus, DropdownTimePicker} from '../DropdownButton'
import { getComponent, getOption } from '../DropdownButton/util' import { getComponent, getOption } from '../DropdownButton/util'
const valueItemKey = ['int', 'int','long','float','double','string', 'password']
type Emit = { type Emit = {
(e: 'update:value', data: ValueType): void (e: 'update:value', data: ValueType): void
(e: 'update:source', data: string): void (e: 'update:source', data: string): void

View File

@ -46,10 +46,6 @@ export const defaultSetting = {
type: Array as PropType<Array<DropdownButtonOptions>>, type: Array as PropType<Array<DropdownButtonOptions>>,
default: () => [] default: () => []
}, },
metricOption: {
type: Array as PropType<Array<DropdownButtonOptions>>,
default: () => []
},
metricOptions: { // 指标值 metricOptions: { // 指标值
type: Array as PropType<Array<DropdownButtonOptions>>, type: Array as PropType<Array<DropdownButtonOptions>>,
default: () => [] default: () => []

View File

@ -7,9 +7,9 @@
style="margin-right: 12px" style="margin-right: 12px"
/> />
<template v-if="shakeLimit.enabled"> <template v-if="shakeLimit.enabled">
<j-input-number :min="1" :max="100" :precision="0" size="small" v-model:value="shakeLimit.time" style="width: 32px" /> <j-input-number :min="1" :max="100" :precision="0" size="small" v-model:value="shakeLimit.time" style="width: 38px" />
<span>秒内发送</span> <span>秒内发送</span>
<j-input-number :min="1" :max="100" :precision="0" size="small" v-model:value="shakeLimit.threshold" style="width: 32px" /> <j-input-number :min="1" :max="100" :precision="0" size="small" v-model:value="shakeLimit.threshold" style="width: 38px" />
<span>次及以上时处理</span> <span>次及以上时处理</span>
<j-radio-group :options="alarmFirstOptions" optionType="button" v-model:value="shakeLimit.alarmFirst" size="small" /> <j-radio-group :options="alarmFirstOptions" optionType="button" v-model:value="shakeLimit.alarmFirst" size="small" />
</template> </template>
@ -55,8 +55,7 @@ const shakeLimit = reactive<ShakeLimitType>({
Object.assign(shakeLimit, props.value) Object.assign(shakeLimit, props.value)
watch(() => shakeLimit, () => { watch(() => shakeLimit, () => {
const cloneValue = cloneDeep(shakeLimit) emit('update:value', {...shakeLimit})
emit('update:value', cloneValue)
}, { }, {
deep: true deep: true
}) })

View File

@ -49,9 +49,17 @@
</div> </div>
</div> </div>
<div class='actions-branches'> <div class='actions-branches'>
<j-form-item></j-form-item> <j-form-item
:name='["branches", name, "then"]'
:rules='rules'
>
<Action
:name='name'
:openShakeLimit="true"
:thenOptions='FormModel.branches[name].then'
/>
</j-form-item>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
@ -62,6 +70,7 @@ import type { ActionBranchesProps } from '@/views/rule-engine/Scene/typings'
import TermsItem from './TermsItem.vue' import TermsItem from './TermsItem.vue'
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useSceneStore } from 'store/scene' import { useSceneStore } from 'store/scene'
import Action from '../../action/index.vue'
const sceneStore = useSceneStore() const sceneStore = useSceneStore()
const { data: FormModel } = storeToRefs(sceneStore) const { data: FormModel } = storeToRefs(sceneStore)
@ -149,6 +158,18 @@ const optionsClass = computed(() => {
} }
}) })
const rules = [{
validator(_: string, value: any) {
if (!value || (value && !value.length)) {
return Promise.reject('至少配置一个执行动作')
} else {
const isActions = value.some((item: any) => item.actions && item.actions.length)
return isActions ? Promise.resolve() : Promise.reject('至少配置一个执行动作');
}
return Promise.resolve();
}
}]
</script> </script>
<style scoped lang='less'> <style scoped lang='less'>

View File

@ -40,7 +40,7 @@
icon='icon-canshu' icon='icon-canshu'
placeholder='参数值' placeholder='参数值'
:options='valueOptions' :options='valueOptions'
:metricOption='metricOption' :metricOptions='metricOption'
: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'
@ -50,7 +50,7 @@
icon='icon-canshu' icon='icon-canshu'
placeholder='参数值' placeholder='参数值'
:options='valueOptions' :options='valueOptions'
:metricOption='metricOption' :metricOptions='metricOption'
: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'

View File

@ -32,13 +32,13 @@
</div> </div>
</template> </template>
</template> </template>
<j-form-item <!-- <j-form-item-->
v-else <!-- v-else-->
:name='["branches", 0, "then"]' <!-- :name='["branches", 0, "then"]'-->
:rules='rules' <!-- :rules='rules'-->
> <!-- >-->
<!-- -->
</j-form-item> <!-- </j-form-item>-->
</div> </div>
</template> </template>
@ -65,18 +65,6 @@ const change = (e: boolean) => {
open.value = e open.value = e
} }
const rules = [{
validator(_: string, value: any) {
if (!value || (value && !value.length)) {
return Promise.reject('至少配置一个执行动作')
} else {
const isActions = value.some((item: any) => item.actions && item.actions.length)
return isActions ? Promise.resolve() : Promise.reject('至少配置一个执行动作');
}
return Promise.resolve();
}
}]
const handleParamsData = (data: any[]): any[] => { const handleParamsData = (data: any[]): any[] => {
return data?.map(item => { return data?.map(item => {
return { return {