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

This commit is contained in:
easy 2023-03-04 17:23:58 +08:00
commit da1d71e534
43 changed files with 2433 additions and 372 deletions

View File

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

View File

@ -17,6 +17,8 @@ export default {
getHistory: (data: any, id: string) => post(`/notify/history/config/${id}/_query`, data),
// 获取所有平台用户
getPlatformUsers: (data: any) => post<any>(`/user/_query/no-paging`, data),
// 获取所有关系
getRelationUsers: (data: any) => post<any>(`/relation/_query/no-paging`, data),
// 钉钉部门
dingTalkDept: (id: string) => get<any>(`/notifier/dingtalk/corp/${id}/departments/tree`),
// 钉钉部门人员
@ -38,5 +40,9 @@ export default {
//通知类型
queryMessageType: () => get(`/notifier/config/types`),
// 不分页-列表
queryListNoPaging: (data: any) => post(`/notifier/config/_query/no-paging?paging=false`, data)
queryListNoPaging: (data: any) => post(`/notifier/config/_query/no-paging?paging=false`, data),
//
queryDingTalkUsers: (id: string) => get<any>(`/notifier/dingtalk/corp/${id}/users?sorts[0].name='name'&sorts[0].order=asc`),
//
queryWechatUsers: (id: string) => get<any>(`/notifier/wechat/corp/${id}/users?sorts[0].name='name'&sorts[0].order=asc`),
}

View File

@ -19,3 +19,6 @@ export const _action = (id: string, type: '_disable' | '_enable') => server.put(
* @returns
*/
export const _execute = (id: string) => server.post(`/scene/${id}/_execute`);
// 内置参数
export const queryBuiltInParams = (data: any, params?: any) => server.post(`/scene/parse-variables`, data, params);

View File

@ -1,6 +1,6 @@
import type { Slots } from 'vue'
import { TOKEN_KEY } from '@/utils/variable'
import { message } from 'ant-design-vue'
import { message } from 'jetlinks-ui-components';
/**
*

View File

@ -1,11 +1,11 @@
import { isObject, isArray } from 'lodash-es'
const encodeParams = (params: Record<string, any>) => {
export const encodeParams = (params: Record<string, any>) => {
const _params = new URLSearchParams()
for (const key in params) {
const _value = params[key]
const isArrOrObj = isObject(_value) || isArray(_value)
_params.set(key, isArrOrObj ? encodeParams(_value) : _value)
_params.set(key, isArrOrObj ? JSON.stringify(_value) : _value)
}
return _params.toString()
}

View File

@ -103,7 +103,7 @@ import {
saveDeviceCode,
delDeviceCode
} from '@/api/device/instance'
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
import { isBoolean } from 'lodash';
const defaultValue =

View File

@ -72,7 +72,7 @@ import {
testCode,
saveProductCode,
} from '@/api/device/instance'
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
import { isBoolean } from 'lodash';
const defaultValue =

View File

@ -374,6 +374,7 @@ const productStore = useProductStore();
import Driver from 'driver.js';
import 'driver.js/dist/driver.min.css';
import { marked } from 'marked';
import type { FormInstance, TableColumnType } from 'ant-design-vue';
const render = new marked.Renderer();
marked.setOptions({
renderer: render,
@ -690,31 +691,53 @@ const driver1 = new Driver({
/**
* 表格列表
*/
const columnsMQTT: any[] = [
{
title: '分组',
dataIndex: 'group',
key: 'group',
ellipsis: true,
width: 100,
// customCell: (record: any, index: number) => {
// const list =
// (config?.routes || []).sort((a: any, b: any) => a - b) || [];
// const arr = list.filter((res: any) => {
// // gpsNumber
// return res?.group == record?.group;
// });
// if (index == 0 || list[index - 1]?.group != record?.group) {
// return { rowSpan: arr.length };
// } else {
// return { rowSpan: 0 };
// }
// const columnsMQTT: any[] = [
// {
// title: '',
// dataIndex: 'group',
// key: 'group',
// ellipsis: true,
// width: 100,
// // customCell: (record: any, index: number) => {
// // const list =
// // (config?.routes || []).sort((a: any, b: any) => a - b) || [];
// // const arr = list.filter((res: any) => {
// // // gpsNumber
// // return res?.group == record?.group;
// // });
// // if (index == 0 || list[index - 1]?.group != record?.group) {
// // return { rowSpan: arr.length };
// // } else {
// // return { rowSpan: 0 };
// // }
// // },
// },
},
// {
// title: 'topic',
// dataIndex: 'topic',
// key: 'topic',
// },
// {
// title: '',
// dataIndex: 'stream',
// key: 'stream',
// ellipsis: true,
// align: 'center',
// width: 100,
// },
// {
// title: '',
// dataIndex: 'description',
// key: 'description',
// },
// ];
let columnsMQTT = ref(<TableColumnType>[]);
const ColumnsMQTT = [
{
title: 'topic',
dataIndex: 'topic',
key: 'topic',
ellipsis: true,
},
{
title: '上下行',
@ -723,46 +746,71 @@ const columnsMQTT: any[] = [
ellipsis: true,
align: 'center',
width: 100,
scopedSlots: { customRender: 'stream' },
},
{
title: '说明',
dataIndex: 'description',
key: 'description',
ellipsis: true,
},
];
const columnsHTTP: any[] = [
const columnsHTTP = ref(<TableColumnType>[]);
const ColumnsHTTP = [
{
title: '分组',
dataIndex: 'group',
key: 'group',
title: '地址',
dataIndex: 'address',
key: 'address',
ellipsis: true,
width: 100,
// customCell: (record: any, index: number) => {
// const list =
// (config?.routes || []).sort((a: any, b: any) => a - b) || [];
// const arr = list.filter((res: any) => {
// // gpsNumber
// return res?.group == record?.group;
// });
// if (index == 0 || list[index - 1]?.group != record?.group) {
// return { rowSpan: arr.length };
// } else {
// return { rowSpan: 0 };
// }
// },
// scopedSlots: { customRender: 'address' },
},
{
title: '示例',
dataIndex: 'example',
key: 'example',
ellipsis: true,
// scopedSlots: { customRender: 'example' },
},
{
title: '说明',
dataIndex: 'description',
key: 'description',
ellipsis: true,
// scopedSlots: { customRender: 'description' },
},
];
// const columnsHTTP: any[] = [
// {
// title: '',
// dataIndex: 'group',
// key: 'group',
// ellipsis: true,
// width: 100,
// // customCell: (record: any, index: number) => {
// // const list =
// // (config?.routes || []).sort((a: any, b: any) => a - b) || [];
// // const arr = list.filter((res: any) => {
// // // gpsNumber
// // return res?.group == record?.group;
// // });
// // if (index == 0 || list[index - 1]?.group != record?.group) {
// // return { rowSpan: arr.length };
// // } else {
// // return { rowSpan: 0 };
// // }
// // },
// },
// {
// title: '',
// dataIndex: 'example',
// key: 'example',
// },
// {
// title: '',
// dataIndex: 'description',
// key: 'description',
// },
// ];
/**
* 获取上下行数据
*/
@ -806,6 +854,34 @@ const getConfigDetail = async (
(resp) => {
if (resp.status === 200) {
config.value = resp.result;
const Group = {
title: '分组',
dataIndex: 'group',
key: 'group',
ellipsis: true,
align: 'center',
width: 100,
customCell: (record: any, rowIndex: number) => {
const obj = {
children: record,
rowSpan: 0,
};
const list = config.value?.routes || [];
const arr = list.filter(
(res: any) => res.group === record.group,
);
const isRowIndex =
rowIndex === 0 ||
list[rowIndex - 1].group !== record.group;
isRowIndex && (obj.rowSpan = arr.length);
return obj;
},
};
columnsMQTT.value = [Group, ...ColumnsMQTT];
columnsHTTP.value = [Group, ...ColumnsHTTP];
if (config.value?.document) {
markdownToHtml.value = marked(config.value.document);
}
@ -989,10 +1065,12 @@ watchEffect(() => {
});
</script>
<style lang="less" scoped>
:deep(._jtable-body_1eyxz_1
:deep(
._jtable-body_1eyxz_1
._jtable-body-header_1eyxz_6
._jtable-body-header-right_1eyxz_12
._jtable-body-header-right-button_1eyxz_17) {
._jtable-body-header-right-button_1eyxz_17
) {
display: none;
margin-left: 10px;
gap: 8px;

View File

@ -5,7 +5,7 @@
target="product-manage"
@search="handleSearch"
/>
<JTable
<JProTable
:columns="columns"
:request="queryProductList"
ref="tableRef"
@ -85,7 +85,7 @@
</a-row>
</template>
<template #actions="item">
<a-tooltip
<!-- <a-tooltip
v-bind="item.tooltip"
:title="item.disabled && item.tooltip.title"
>
@ -122,7 +122,24 @@
</template>
</a-button>
</template>
</a-tooltip>
</a-tooltip> -->
<PermissionButton
:disabled="item.disabled"
:popConfirm="item.popConfirm"
:tooltip="{
...item.tooltip,
}"
@click="item.onClick"
>
<AIcon
type="DeleteOutlined"
v-if="item.key === 'delete'"
/>
<template v-else>
<AIcon :type="item.icon" />
<span>{{ item?.text }}</span>
</template>
</PermissionButton>
</template>
</CardBox>
</template>
@ -137,7 +154,7 @@
</template>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip
<!-- <a-tooltip
v-for="i in getActions(slotProps)"
:key="i.key"
v-bind="i.tooltip"
@ -168,10 +185,27 @@
><AIcon :type="i.icon"
/></a-button>
</a-button>
</a-tooltip>
</a-tooltip> -->
<template
v-for="i in getActions(slotProps, 'table')"
:key="i.key"
>
<PermissionButton
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
>
<template #icon><AIcon :type="i.icon" /></template>
</PermissionButton>
</template>
</a-space>
</template>
</JTable>
</JProTable>
<!-- 新增编辑 -->
<Save ref="saveRef" :isAdd="isAdd" :title="title" @success="refresh" />
</page-container>

View File

@ -1,39 +1,16 @@
<!-- 绑定设备 -->
<template>
<j-modal
:maskClosable="false"
width="1100px"
:visible="true"
title="选择设备"
okText="确定"
cancelText="取消"
@ok="handleOk"
@cancel="handleCancel"
:confirmLoading="btnLoading"
>
<j-modal :maskClosable="false" width="1100px" :visible="true" title="选择设备" okText="确定" cancelText="取消" @ok="handleOk"
@cancel="handleCancel" :confirmLoading="btnLoading">
<div style="margin-top: 10px">
<Search
:columns="columns"
target="iot-card-bind-device"
@search="handleSearch"
type="simple"
/>
<j-pro-table
ref="bindDeviceRef"
:columns="columns"
:request="queryUnbounded"
model="TABLE"
:defaultParams="{
<Search :columns="columns" target="iot-card-bind-device" @search="handleSearch" type="simple" />
<j-pro-table ref="bindDeviceRef" :columns="columns" :request="queryUnbounded" model="TABLE" :defaultParams="{
sorts: [{ name: 'createTime', order: 'desc' }],
}"
:rowSelection="{
}" :rowSelection="{
type: 'radio',
selectedRowKeys: _selectedRowKeys,
onSelect: onSelectChange,
}"
@cancelSelect="cancelSelect"
:params="params"
>
}" @cancelSelect="cancelSelect" :params="params">
<template #registryTime="slotProps">
{{
slotProps.registryTime
@ -44,10 +21,7 @@
}}
</template>
<template #state="slotProps">
<j-badge
:text="slotProps.state.text"
:status="statusMap.get(slotProps.state.value)"
/>
<j-badge :text="slotProps.state.text" :status="statusMap.get(slotProps.state.value)" />
</template>
</j-pro-table>
</div>
@ -57,7 +31,7 @@
<script setup lang="ts">
import { queryUnbounded, bind } from '@/api/iot-card/cardManagement';
import moment from 'moment';
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
const emit = defineEmits(['change']);
@ -141,7 +115,7 @@ const handleOk = () => {
bind(props.cardId, _selectedRowKeys.value[0])
.then((resp: any) => {
if (resp.status === 200) {
message.success('操作成功');
message.success('操作成功')
emit('change', true);
}
})

View File

@ -1,6 +1,6 @@
<template>
<!-- 导入 -->
<a-modal
<j-modal
:maskClosable="false"
:visible="true"
title="导出"
@ -10,19 +10,19 @@
@cancel="handleCancel"
>
<div style="margin-top: 10px">
<a-space>
<j-space>
<span>文件格式</span>
<a-radio-group
<j-radio-group
v-model:value="type"
placeholder="请选择文件格式"
button-style="solid"
>
<a-radio-button value="xlsx">xlsx</a-radio-button>
<a-radio-button value="csv">csv</a-radio-button>
</a-radio-group>
</a-space>
<j-radio-button value="xlsx">xlsx</j-radio-button>
<j-radio-button value="csv">csv</j-radio-button>
</j-radio-group>
</j-space>
</div>
</a-modal>
</j-modal>
</template>
<script setup lang="ts">

View File

@ -82,7 +82,9 @@ import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
import { LocalStore } from '@/utils/comm';
import { downloadFile, downloadFileByUrl } from '@/utils/utils';
import { queryPlatformNoPage, _import ,exportCard} from '@/api/iot-card/cardManagement';
import { message } from 'ant-design-vue';
// import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
const emit = defineEmits(['close']);
@ -125,10 +127,10 @@ const fileChange = (info: any) => {
_import(modelRef.configId, { fileUrl: r.result })
.then((resp: any) => {
totalCount.value = resp.result.total;
message.success('导入成功');
message.success('导入成功')
})
.catch((err) => {
message.error(err.response.data.message || '导入失败');
message.error(err.response.data.message || '导入失败')
})
.finally(() => {
loading.value = false;

View File

@ -98,7 +98,7 @@ import {
add,
edit,
} from '@/api/iot-card/cardManagement';
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
import { OperatorList, TypeList } from '@/views/iot-card/data';
const emit = defineEmits(['change']);
@ -236,7 +236,7 @@ const handleOk = () => {
: await edit(toRaw(modelRef));
btnLoading.value = false;
if (resp.status === 200) {
message.success('操作成功');
message.success('操作成功')
emit('change', true);
formRef.value.resetFields();
}

View File

@ -248,22 +248,14 @@
</template>
<template #action="slotProps">
<j-space :size="16">
<template
v-for="i in getActions(slotProps,'table')"
:key="i.key"
>
<PermissionButton
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="{
<template v-for="i in getActions(slotProps, 'table')" :key="i.key">
<PermissionButton :disabled="i.disabled" :popConfirm="i.popConfirm" :tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="'iot-card/CardManagement:' + i.key"
>
<template #icon><AIcon :type="i.icon" /></template>
}" @click="i.onClick" type="link" style="padding: 0px"
:hasPermission="'iot-card/CardManagement:' + i.key">
<template #icon>
<AIcon :type="i.icon" />
</template>
</PermissionButton>
</template>
</j-space>
@ -297,7 +289,7 @@ import {
removeCards,
unbind,
} from '@/api/iot-card/cardManagement';
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
import type { CardManagement } from './typing';
import { getImage } from '@/utils/comm';
import BindDevice from './BindDevice.vue';
@ -513,7 +505,7 @@ const getActions = (
onConfirm: async () => {
unbind(data.id).then((resp: any) => {
if (resp.status === 200) {
message.success('操作成功');
message.success('操作成功')
cardManageRef.value?.reload();
}
});
@ -562,21 +554,21 @@ const getActions = (
if (data.cardStateType?.value === 'toBeActivated') {
changeDeploy(data.id).then((resp) => {
if (resp.status === 200) {
message.success('操作成功');
message.success('操作成功')
cardManageRef.value?.reload();
}
});
} else if (data.cardStateType?.value === 'deactivate') {
resumption(data.id).then((resp) => {
if (resp.status === 200) {
message.success('操作成功');
message.success('操作成功')
cardManageRef.value?.reload();
}
});
} else {
unDeploy(data.id).then((resp) => {
if (resp.status === 200) {
message.success('操作成功');
message.success('操作成功')
cardManageRef.value?.reload();
}
});
@ -597,7 +589,7 @@ const getActions = (
onConfirm: async () => {
const resp: any = await del(data.id);
if (resp.status === 200) {
message.success('操作成功');
message.success('操作成功')
cardManageRef.value?.reload();
} else {
message.error('操作失败!');
@ -692,7 +684,7 @@ const handleStop = () => {
) {
unDeployBatch(_selectedRowKeys.value).then((res: any) => {
if (res.status === 200) {
message.success('操作成功');
message.success('操作成功')
}
});
} else {
@ -710,7 +702,7 @@ const handleResumption = () => {
) {
resumptionBatch(_selectedRowKeys.value).then((res: any) => {
if (res.status === 200) {
message.success('操作成功');
message.success('操作成功')
}
});
} else {
@ -736,7 +728,7 @@ const handleSync = () => {
const handelRemove = async () => {
const resp = await removeCards(_selectedRow.value);
if (resp.status === 200) {
message.success('操作成功');
message.success('操作成功')
_selectedRowKeys.value = [];
_selectedRow.value = [];
cardManageRef.value?.reload();

View File

@ -133,7 +133,7 @@
import { getImage } from '@/utils/comm';
import PlatformType from '@/views/iot-card/components/PlatformType.vue';
import { queryById, save, update } from '@/api/iot-card/platform';
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
import Doc from '../doc/index.vue';
const router = useRouter();

View File

@ -1,19 +1,9 @@
<!-- 平台对接 -->
<template>
<page-container>
<Search
:columns="columns"
target="platform-search"
@search="handleSearch"
/>
<j-pro-table
ref="platformRef"
:columns="columns"
:request="queryList"
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }"
:params="params"
:gridColumn="3"
>
<Search :columns="columns" target="platform-search" @search="handleSearch" />
<j-pro-table ref="platformRef" :columns="columns" :request="queryList"
:defaultParams="{ sorts: [{ name: 'createTime', order: 'desc' }] }" :params="params" :gridColumn="3">
<template #headerTitle>
<j-space>
<!-- <j-button type="primary" @click="handleAdd">
@ -25,17 +15,11 @@
</j-space>
</template>
<template #card="slotProps">
<CardBox
:value="slotProps"
:actions="getActions(slotProps, 'card')"
v-bind="slotProps"
:status="slotProps.state.value"
:statusText="slotProps.state.text"
:statusNames="{
<CardBox :value="slotProps" :actions="getActions(slotProps, 'card')" v-bind="slotProps"
:status="slotProps.state.value" :statusText="slotProps.state.text" :statusNames="{
enabled: 'success',
disabled: 'error',
}"
>
}">
<template #img>
<slot name="img">
<img :src="getImage('/iot-card/iot-card-bg.png')" />
@ -72,33 +56,22 @@
</CardBox>
</template>
<template #state="slotProps">
<j-badge
:text="slotProps.state.text"
:status="
<j-badge :text="slotProps.state.text" :status="
slotProps.state.value === 'disabled'
? 'error'
: 'success'
"
/>
" />
</template>
<template #action="slotProps">
<j-space :size="16">
<template
v-for="i in getActions(slotProps,'table')"
:key="i.key"
>
<PermissionButton
:disabled="i.disabled"
:popConfirm="i.popConfirm"
:tooltip="{
<template v-for="i in getActions(slotProps, 'table')" :key="i.key">
<PermissionButton :disabled="i.disabled" :popConfirm="i.popConfirm" :tooltip="{
...i.tooltip,
}"
@click="i.onClick"
type="link"
style="padding: 0px"
:hasPermission="'iot-card/Platform:' + i.key"
>
<template #icon><AIcon :type="i.icon" /></template>
}" @click="i.onClick" type="link" style="padding: 0px"
:hasPermission="'iot-card/Platform:' + i.key">
<template #icon>
<AIcon :type="i.icon" />
</template>
</PermissionButton>
</template>
</j-space>
@ -110,7 +83,7 @@
<script setup lang="ts">
import { getImage } from '@/utils/comm';
import type { ActionsType } from '@/components/Table';
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
import { queryList, update, del } from '@/api/iot-card/platform';
import { useMenuStore } from 'store/menu'
const menuStory = useMenuStore()
@ -207,8 +180,7 @@ const getActions = (
? 'StopOutlined'
: 'PlayCircleOutlined',
popConfirm: {
title: `确认${
data.state.value === 'enabled' ? '禁用' : '启用'
title: `确认${data.state.value === 'enabled' ? '禁用' : '启用'
}`,
okText: ' 确定',
cancelText: '取消',

View File

@ -68,7 +68,7 @@
<script lang="ts" setup>
import { queryPlatformNoPage, recharge } from '@/api/iot-card/cardManagement';
import { message } from 'ant-design-vue';
import { message } from 'jetlinks-ui-components';
import { PaymentMethod } from '@/views/iot-card/data';
const emit = defineEmits(['change']);
@ -167,7 +167,7 @@ const handleOk = () => {
btnLoading.value = false;
if (resp.status === 200) {
if (resp.result === '失败') {
message.error('缴费失败');
message.error('缴费失败')
} else {
window.open(resp.result);
}
@ -176,6 +176,7 @@ const handleOk = () => {
}
})
.catch((err: any) => {
btnLoading.value=false
console.log('error', err);
});
};

View File

@ -10,7 +10,7 @@
>
<Search :columns="columns" @search="handleSearch"></Search>
<div style="height: 500px; overflow-y: auto">
<JTable
<JProTable
model="CARD"
:request="query"
:rowSelection="{
@ -76,7 +76,7 @@
</template>
</SceneCard>
</template>
</JTable>
</JProTable>
</div>
</a-modal>
</template>

View File

@ -1,5 +1,5 @@
<template>
<JTable
<JProTable
model="CARD"
:request="query"
:defaultParams="{
@ -71,7 +71,7 @@
</template>
</SceneCard>
</template>
</JTable>
</JProTable>
<Save
:id="id"
:type="configurationData.current?.targetType"

View File

@ -6,7 +6,7 @@
target="device-instance"
@search="handleSearch"
></Search>
<JTable
<JProTable
:columns="columns"
:request="queryList"
:gridColumn="3"
@ -155,7 +155,7 @@
</template>
</a-space>
</template>
</JTable>
</JProTable>
</div>
</page-container>
</template>

View File

@ -24,7 +24,7 @@
v-if="props.type === 'org'"
@search="search"
></Search>
<JTable
<JProTable
:columns="columns"
:request="handleSearch"
:params="params"
@ -115,7 +115,7 @@
</template>
</CardBox>
</template>
</JTable>
</JProTable>
<SolveComponent :data="data" v-if="data.solveVisible" @closeSolve="closeSolve"/>
<SolveLog :data="data.current" v-if="data.logVisible" @closeLog="closeLog"/>
</div>

View File

@ -1,11 +1,16 @@
<template>
<div>
<Action :thenOptions="data.branches ? data?.branches[0].then : []" :name="0" />
</div>
</template>
<script>
export default {
name: 'inex'
}
<script lang="ts" setup>
import { useSceneStore } from '@/store/scene'
import Action from '../action/index.vue'
import { storeToRefs } from 'pinia';
const sceneStore = useSceneStore()
const { data } = storeToRefs(sceneStore)
</script>
<style scoped>

View File

@ -10,7 +10,7 @@
<a-input-number
style="max-width: 220px"
placeholder="请输入时间"
v-model:value="value"
v-model:value="_value"
:precision="3"
:min="0"
:max="6535"
@ -32,6 +32,18 @@
<script lang="ts" setup>
import { onlyMessage } from '@/utils/comm';
const props = defineProps({
value: {
type: Object,
default: () => {
return {
time: 0,
unit: 'seconds',
};
},
},
});
const timeUnitEnum = {
seconds: '秒',
minutes: '分',
@ -40,24 +52,38 @@ const timeUnitEnum = {
const emit = defineEmits(['cancel', 'save']);
const value = ref<number>(0);
const unit = ref<'seconds' | 'minutes' | 'hours'>('seconds');
const _value = ref<number>(props.value.time);
const unit = ref<'seconds' | 'minutes' | 'hours'>(
props.value?.unit || 'seconds',
);
watch(
() => props.value,
(newVal) => {
_value.value = newVal?.time || 0
unit.value = newVal?.unit || 'seconds'
},
{
immediate: true,
deep: true,
},
);
const onCancel = () => {
emit('cancel');
};
const onOk = () => {
if (unref(value) || unref(value) === 0) {
if (unref(_value) || unref(_value) === 0) {
} else {
onlyMessage('请输入时间', 'error');
}
emit(
'save',
{
time: value.value,
time: _value.value,
unit: unit.value,
},
{ name: `${value.value} ${timeUnitEnum[unit.value]}后,执行后续动作` },
{ name: `${_value.value} ${timeUnitEnum[unit.value]}后,执行后续动作` },
);
};
</script>

View File

@ -0,0 +1,261 @@
<template>
<Search
:columns="columns"
type='simple'
@search="handleSearch"
class='search'
target="scene-triggrt-device-device"
/>
<a-divider style='margin: 0' />
<j-pro-table
ref='actionRef'
model='CARD'
:columns='columns'
:params='params'
:request='productQuery'
:gridColumn='2'
:gridColumns='[2,2,2]'
:bodyStyle='{
paddingRight: 0,
paddingLeft: 0
}'
>
<template #card="slotProps">
<CardBox
:value='slotProps'
:active="rowKey === slotProps.id"
:status="slotProps.state"
:statusText="slotProps.state === 1 ? '正常' : '禁用'"
:statusNames="{ 1: 'success', 0: 'error', }"
@click="handleClick"
>
<template #img>
<slot name="img">
<img width='88' height='88' :src="slotProps.photoUrl || getImage('/device-product.png')" />
</slot>
</template>
<template #content>
<div style='width: calc(100% - 100px)'>
<Ellipsis>
<span style="font-size: 16px;font-weight: 600" >
{{ slotProps.name }}
</span>
</Ellipsis>
</div>
<a-row>
<a-col :span="12">
<div class="card-item-content-text">
设备类型
</div>
<div>直连设备</div>
</a-col>
</a-row>
</template>
</CardBox>
</template>
</j-pro-table>
</template>
<script setup lang='ts' name='Product'>
import { getProviders, queryGatewayList, queryProductList } from '@/api/device/product'
import { queryTree } from '@/api/device/category'
import { getTreeData_api } from '@/api/system/department'
import { isNoCommunity } from '@/utils/utils'
import { getImage } from '@/utils/comm'
type Emit = {
(e: 'update:rowKey', data: string): void
(e: 'update:detail', data: string): void
(e: 'change', data: string): void
}
const actionRef = ref()
const params = ref({})
const props = defineProps({
rowKey: {
type: String,
default: ''
},
detail: {
type: Object,
default: () => ({})
}
})
const emit = defineEmits<Emit>()
const columns = [
{
title: 'ID',
dataIndex: 'id',
width: 300,
ellipsis: true,
fixed: 'left',
search: {
type: 'string',
},
},
{
title: '名称',
dataIndex: 'name',
width: 200,
ellipsis: true,
search: {
type: 'string',
first: true
}
},
{
title: '网关类型',
dataIndex: 'accessProvider',
width: 150,
ellipsis: true,
hideInTable: true,
search: {
type: 'select',
options: () => getProviders().then((resp: any) => {
if (isNoCommunity) {
return (resp?.result || []).map((item: any) => ({
label: item.name,
value: item.id
}))
} else {
return (resp?.result || []).filter((item: any) => [
'mqtt-server-gateway',
'http-server-gateway',
'mqtt-client-gateway',
'tcp-server-gateway',
].includes(item.id))
.map((item: any) => ({
label: item.name,
value: item.id,
}))
}
})
}
},
{
title: '接入方式',
dataIndex: 'accessName',
width: 150,
ellipsis: true,
search: {
type: 'select',
options: () => queryGatewayList().then((resp: any) =>
resp.result.map((item: any) => ({
label: item.name, value: item.id
}))
)
}
},
{
title: '设备类型',
dataIndex: 'deviceType',
width: 150,
search: {
type: 'select',
options: [
{ label: '直连设备', value: 'device' },
{ label: '网关子设备', value: 'childrenDevice' },
{ label: '网关设备', value: 'gateway' },
]
}
},
{
title: '状态',
dataIndex: 'state',
width: '90px',
search: {
type: 'select',
options: [
{ label: '禁用', value: 0 },
{ label: '正常', value: 1 },
]
}
},
{
title: '说明',
dataIndex: 'describe',
ellipsis: true,
width: 300,
},
{
dataIndex: 'classifiedId',
title: '分类',
hideInTable: true,
search: {
type: 'treeSelect',
options: queryTree({ paging: false }).then(resp => resp.result),
componentProps: {
fieldNames: {
label: 'name',
value: 'id',
}
}
}
},
{
dataIndex: 'id$dim-assets',
title: '所属组织',
hideInTable: true,
search: {
type: 'treeSelect',
options: getTreeData_api({ paging: false }).then((resp: any) => {
const formatValue = (list: any[]) => {
return list.map((item: any) => {
if (item.children) {
item.children = formatValue(item.children);
}
return {
...item,
value: JSON.stringify({
assetType: 'product',
targets: [
{
type: 'org',
id: item.id,
},
],
}),
}
})
}
return formatValue(resp.result)
}),
}
}
]
const handleSearch = (p: any) => {
params.value = p
}
const productQuery = (p: any) => {
const sorts: any = [];
if (props.rowKey) {
sorts.push({
name: 'id',
value: props.rowKey,
});
}
sorts.push({ name: 'createTime', order: 'desc' });
p.sorts = sorts
return queryProductList(p)
}
const handleClick = (detail: any) => {
emit('update:rowKey', detail.id)
emit('update:detail', detail)
emit('change', detail)
}
</script>
<style scoped lang='less'>
.search {
margin-bottom: 0;
padding-right: 0px;
padding-left: 0px;
}
</style>

View File

@ -0,0 +1,14 @@
<template>
<div>
</div>
</template>
<script setup lang='ts' name="Device">
</script>
<style scoped lang='less'>
</style>

View File

@ -1,22 +1,104 @@
<template>
<j-modal
title="执行动作"
visible
:width="860"
@cancel="onCancel"
@ok="onOk"
:maskClosable="false"
>
device
<j-modal title="执行动作" visible :width="860" @cancel="onCancel" @ok="save" :maskClosable="false">
<j-steps :current='DeviceModel.current' @change='stepChange'>
<j-step>
<template #title>选择产品</template>
</j-step>
<j-step>
<template #title>选择设备</template>
</j-step>
<j-step>
<template #title>执行动作</template>
</j-step>
</j-steps>
<j-divider style='margin-bottom: 10px;' />
<div class='steps-content'>
<Product v-if='DeviceModel.current === 0' v-model:rowKey='DeviceModel.productId'
v-model:detail='DeviceModel.productDetail' />
</div>
<template #footer>
<div class='steps-action'>
<j-button v-if='DeviceModel.current === 0' @click='cancel'>取消</j-button>
<j-button v-else @click='prev'>上一步</j-button>
<j-button type='primary' v-if='DeviceModel.current < 2' @click='saveClick'>下一步</j-button>
<j-button type='primary' v-else @click='saveClick'>确定</j-button>
</div>
</template>
</j-modal>
</template>
<script lang="ts" setup>
const emit = defineEmits(['cancel', 'save']);
const onCancel = () => {
import { DeviceModelType } from './typings';
import Product from './Product.vue';
import { onlyMessage } from '@/utils/comm';
type Emit = {
(e: 'cancel'): void
(e: 'save', data: any, options: Record<string, any>): void
}
const DeviceModel = reactive<DeviceModelType>({
steps: [],
current: 0,
productId: '',
deviceId: '',
productDetail: {},
device: {},
deviceDetail: {},
options: {},
selector: 'fixed',
selectorValues: [],
upperKey: '',
source: 'fixed',
relationName: '',
message: {},
propertiesName: '',
propertiesValue: '',
columns: [],
actionName: '',
tagList: [],
})
const emit = defineEmits<Emit>()
const cancel = () => {
emit('cancel');
};
const onOk = () => {
emit('save');
const save = async(step?: number) => {
let _step = step !== undefined ? step : DeviceModel.current
if (_step === 0) {
DeviceModel.productId ? DeviceModel.current = 1 : onlyMessage('请选择产品', 'error')
} else if (_step === 1) {
} else {
}
};
const stepChange = (step: number) => {
if (step !== 0) {
save(step - 1)
} else {
DeviceModel.current = 0
}
}
const prev = () => {
DeviceModel.current = DeviceModel.current - 1
}
const saveClick = () => save()
</script>
<style lang="less" scoped>
.steps-steps {
width: 100%;
margin-bottom: 17px;
padding-bottom: 17px;
border-bottom: 1px solid #f0f0f0;
}
.steps-content {
width: 100%;
}
</style>

View File

@ -0,0 +1,28 @@
import { ProductItem } from '@/views/device/Product/typings';
import { ActionsDeviceProps } from '../../../typings';
type DeviceModelType = {
steps: {
key: string;
title: string;
content: React.ReactNode;
}[];
current: number;
productId: string;
deviceId: string;
productDetail: ProductItem | any;
device: Partial<ActionsDeviceProps>;
deviceDetail: any;
options: any;
selector: string;
selectorValues: any;
upperKey: string;
source: string;
relationName: string;
message: any;
propertiesName: string;
propertiesValue: string | any;
columns: string[];
actionName: string;
tagList: any[];
}

View File

@ -1,3 +1,631 @@
<template>
<div>ITEM</div>
<div class="actions-item-warp">
<div class="actions-item">
<div class="item-options-warp">
<div class="item-options-type" @click="onAdd">
<img
style="width: 18px"
:src="
iconMap.get(
data?.executor === 'alarm'
? data?.alarm?.mode
: data?.executor,
)
"
/>
</div>
<div
class="item-options-content"
v-if="data?.executor === 'alarm'"
>
<template v-if="data?.alarm?.mode === 'trigger'">
满足条件后将触发<a-button
type="link"
@click.stop="triggerVisible = true"
>关联此场景的告警</a-button
>
</template>
<template v-else>
满足条件后将解除<a-button
type="link"
@click.stop="triggerVisible = true"
>关联此场景的告警</a-button
>
</template>
</div>
<div
class="item-options-content"
v-else-if="data?.executor === 'notify'"
@click="onType('notify')"
>
<template v-if="data?.notify?.notifyType === 'dingTalk'">
<template
v-if="options?.provider === 'dingTalkRobotWebHook'"
>
<div>
通过<span class="notify-text-highlight"
>群机器人消息</span
>
发送
<span class="notify-text-highlight">
{{
options?.templateName ||
data?.notify?.templateId
}}
</span>
</div>
</template>
<template v-else>
<div>
通过
<span class="notify-text-highlight">
<img
style="width: 18px"
:src="
itemNotifyIconMap.get(
data?.notify?.notifyType,
)
"
/>
钉钉
</span>
<span class="notify-text-highlight">{{
options?.orgName || ''
}}</span>
<span class="notify-text-highlight">{{
options?.sendTo || ''
}}</span>
发送
<span class="notify-text-highlight">
{{
options?.templateName ||
data?.notify?.templateId
}}
</span>
</div>
</template>
</template>
<template v-else-if="data?.notify?.notifyType === 'weixin'">
<div>
通过
<span class="notify-text-highlight">
<img
style="width: 18px"
:src="
itemNotifyIconMap.get(
data?.notify?.notifyType,
)
"
/>
微信
</span>
<span class="notify-text-highlight">{{
options?.orgName || ''
}}</span>
<span class="notify-text-highlight">{{
options?.sendTo || ''
}}</span>
<span class="notify-text-highlight">{{
options?.tagName || ''
}}</span>
发送
<span class="notify-text-highlight">
{{
options?.templateName ||
data?.notify?.templateId
}}
</span>
</div>
</template>
<template v-else-if="data?.notify?.notifyType === 'email'">
<div>
通过
<span class="notify-text-highlight">
<img
style="width: 18px"
:src="
itemNotifyIconMap.get(
data?.notify?.notifyType,
)
"
/>
邮件
</span>
<span class="notify-text-highlight">{{
options?.sendTo || ''
}}</span>
发送
<span class="notify-text-highlight">
{{
options?.templateName ||
data?.notify?.templateId
}}
</span>
</div>
</template>
<template v-else-if="data?.notify?.notifyType === 'voice'">
<div>
通过
<span class="notify-text-highlight">
<img
style="width: 18px"
:src="
itemNotifyIconMap.get(
data?.notify?.notifyType,
)
"
/>
语音
</span>
<span class="notify-text-highlight">{{
options?.sendTo || ''
}}</span>
发送
<span class="notify-text-highlight">
{{
options?.templateName ||
data?.notify?.templateId
}}
</span>
</div>
</template>
<template v-else-if="data?.notify?.notifyType === 'sms'">
<div>
通过
<span class="notify-text-highlight">
<img
style="width: 18px"
:src="
itemNotifyIconMap.get(
data?.notify?.notifyType,
)
"
/>
短信
</span>
<span class="notify-text-highlight">{{
options?.sendTo || ''
}}</span>
发送
<span class="notify-text-highlight">
{{
options?.templateName ||
data?.notify?.templateId
}}
</span>
</div>
</template>
<template
v-else-if="data?.notify?.notifyType === 'webhook'"
>
<div>
通过
<span class="notify-text-highlight">
<img
style="width: 18px"
:src="
itemNotifyIconMap.get(
data?.notify?.notifyType,
)
"
/>
webhook
</span>
发送
<span>{{
options?.templateName ||
data?.notify?.templateId
}}</span>
</div>
</template>
</div>
<div
class="item-options-content"
v-else-if="data?.executor === 'delay'"
@click="onType('delay')"
>
{{ options?.name }}
</div>
<div
class="item-options-content"
v-else-if="data?.executor === 'device'"
@click="onType('device')"
>
<template v-if="data?.device?.selector === 'fixed'">
<div>
<AIcon
:type="
typeIconMap[
data?.device?.message?.messageType ||
'INVOKE_FUNCTION'
]
"
/>
<span style="padding-left: 4px">{{
data?.options?.type
}}</span>
<AIcon
type="icon-mubiao"
style="padding-right: 2px"
/>
{{
`${data?.options?.name} ${
data?.options?.properties
}
${
(
isBoolean(
data?.options?.propertiesValue,
)
? true
: data?.options?.propertiesValue
)
? `${data?.options?.propertiesValue}`
: ''
}`
}}
</div>
</template>
<template v-else-if="data?.device?.selector === 'tag'">
<div>
<AIcon
:type="
typeIconMap[
data?.device?.message?.messageType ||
'INVOKE_FUNCTION'
]
"
/>
{{ data?.options?.type }}
<span
v-for="i in data?.options?.taglist || []"
:key="i.value"
>
{{ i.type }}
{{ i.name }}{{ i.value }}
</span>
{{ data?.options?.productName }}
{{ data?.options?.properties }}
</div>
</template>
<template v-else-if="data?.device?.selector === 'relation'">
<div>
<AIcon
:type="
typeIconMap[
data?.device?.message?.messageType ||
'INVOKE_FUNCTION'
]
"
/>
{{ data?.options?.type }}<span>{{
data?.options?.triggerName
}}</span
>具有相同 {{ data?.options?.relationName }}{{
data?.options?.productName
}}设备的
{{ data?.options?.properties }}
</div>
</template>
</div>
<a-button v-else @click="onAdd">点击配置执行动作</a-button>
</div>
<div class="item-number">{{ name + 1 }}</div>
<a-popconfirm title="确认删除?" @confirm="onDelete">
<div class="item-delete">
<AIcon type="DeleteOutlined" />
</div>
</a-popconfirm>
</div>
<template v-if="!isLast && type === 'serial'">
<div class="actions-item-filter-warp">
<!-- filter-border -->
满足此条件后执行后续动作
</div>
</template>
<!-- 编辑 -->
<template v-if="visible">
<Modal
:name="name"
:branchGroup="branchGroup"
:branchesName="branchesName"
:data="data"
@cancel="onClose"
@save="onSave"
/>
</template>
<template>
<ActionTypeComponent
v-bind="props"
v-if="!!actionType"
:actionType="actionType"
@save="onPropsOk"
@cancel="onPropsCancel"
/>
</template>
</div>
</template>
<script lang="ts" setup>
import { getImage } from '@/utils/comm';
import { isBoolean } from 'lodash-es';
import { PropType } from 'vue';
import { ActionsType, ParallelType } from '../../../typings';
import Modal from '../Modal/index.vue';
import ActionTypeComponent from '../Modal/ActionTypeComponent.vue';
const props = defineProps({
branchesName: {
type: Number,
default: 0,
},
branchGroup: {
type: Number,
default: 0,
},
name: {
type: Number,
default: 0,
},
data: {
type: Object as PropType<ActionsType>,
},
type: {
type: Object as PropType<ParallelType>,
},
parallel: {
type: Boolean,
},
options: {
type: Object,
},
isLast: {
type: Boolean,
},
});
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 triggerVisible = ref<boolean>(false);
const actionType = ref('');
const onDelete = () => {
emit('delete');
};
const onClose = () => {
visible.value = false;
};
const onSave = (data: ActionsType, options?: any) => {
emit('update', data, options);
// setTimeout(() => {
// getParams();
// }, 10);
visible.value = false;
};
const onAdd = () => {
visible.value = true;
};
const onType = (_type: string) => {
actionType.value = _type
}
const onPropsOk = (data: ActionsType, options?: any) => {
emit('update', data, options);
// setTimeout(() => {
// getParams();
// }, 10);
actionType.value = '';
};
const onPropsCancel = () => {
actionType.value = '';
};
</script>
<style lang="less" scoped>
.deleteBtn() {
position: absolute;
top: -10px;
right: -10px;
display: none;
width: 20px;
height: 20px;
color: #999;
line-height: 20px;
text-align: center;
background-color: #f1f1f1;
border-radius: 50%;
cursor: pointer;
&.show {
display: block;
}
&:hover {
background-color: #f3f3f3;
}
}
.actions-item {
position: relative;
margin-bottom: 24px;
padding: 16px;
border: 1px dashed #999;
border-radius: 2px;
.item-options-warp {
display: inline-flex;
height: 48px;
border: 1px solid #e0e0e0;
border-radius: 6px;
.item-options-type {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
background-color: #f0f0f0;
border-radius: 6px 0 0 6px;
cursor: pointer;
}
.item-options-content {
display: flex;
align-items: center;
padding: 0 8px;
background: #fafafa;
border-radius: 0 6px 6px 0;
cursor: pointer;
div {
padding: 6px 10px;
color: #333;
font-size: 14px;
line-height: 22px;
background-color: #fff;
border-radius: 22px;
.notify-text-highlight {
margin-left: 5px;
font-weight: bold;
}
.notify-img-highlight {
margin: 0 10px;
color: rgba(0, 0, 0, 0.8);
}
}
}
}
.item-number {
position: absolute;
top: 0;
left: 16px;
font-weight: 800;
transform: translateY(-50%);
}
.item-delete {
position: absolute;
top: 0;
right: 0;
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
color: #e50012;
background-color: rgba(229, 0, 18, 0.1);
border-radius: 50%;
transform: translate(50%, -50%);
cursor: pointer;
&:hover {
background-color: rgba(#e50012, 0.2);
}
}
}
.actions-item-filter-warp {
position: relative;
margin-bottom: 24px;
padding: 2px 0;
border: 1px dashed #999;
border-radius: 30px;
&.filter-border {
padding: 2px 16px;
border-radius: 2px;
}
.actions-item-filter-overflow {
display: flex;
padding-top: 4px;
overflow-x: auto;
overflow-y: visible;
row-gap: 16px;
}
.terms-params {
// display: inline-block;
display: flex;
flex-shrink: 0;
// &:not(:first-child) {
// margin-bottom: 16px;
// }
.terms-params-warp {
display: flex;
align-items: baseline;
}
.terms-params-content {
position: relative;
display: flex;
background-color: #fafafa;
border: unset;
row-gap: 16px;
.terms-params-item {
display: flex;
align-items: center;
}
.ant-form-item {
margin-bottom: 8px;
&:not(:first-child) {
.ant-form-item-explain-error {
padding-left: 80px !important;
}
}
}
}
.term-type-warp {
// display: inline-block;
width: 50px;
margin: 0 16px;
.term-type {
padding-top: 4px;
padding-bottom: 4px;
border-radius: 2px;
}
}
}
}
</style>

View File

@ -1,19 +1,43 @@
<template>
<div class="action-list-content">
<template v-for="(item, index) in actions" :key="item.key">
<Item
:parallel="parallel"
:data="item"
:branchesName="branchesName"
:branchGroup="parallel ? 1 : 0"
:name="index"
:type="type"
:isLast="index === actions.length - 1"
:options="item.options"
@delete="_delete(item.key || '')"
@update="(data, options) => _update(data, options, item)"
/>
</template>
<div class="actions-add-list" :class="{ 'border': props.actions.length }">
<j-button type="primary" ghost style="width: 100%" @click="onAdd">
<template #icon><AIcon type="PlusOutlined" /></template>
添加执行动作
</j-button>
</div>
<Modal v-if="visible" @cancel="visible = false" />
<Modal
v-if="visible"
@cancel="onCancel"
:parallel="parallel"
:name="actions.length"
:branchGroup="parallel ? 1 : 0"
@save="onSave"
:branchesName="branchesName"
/>
</div>
</template>
<script lang="ts" setup>
import { PropType } from 'vue';
import { ActionsType, ParallelType } from '../../../typings';
import Modal from '../Modal/index.vue'
import Modal from '../Modal/index.vue';
import Item from './Item.vue';
import { pick } from 'lodash';
interface ListProps {
branchesName: number;
@ -35,12 +59,38 @@ const props = defineProps({
parallel: Boolean,
});
const visible = ref<boolean>(false)
const emit = defineEmits(['delete', 'add']);
const visible = ref<boolean>(false);
const onAdd = () => {
visible.value = true
visible.value = true;
};
const onSave = (data: any, options?: any) => {
const { type, ...extra } = data;
const item: ActionsType = {
...extra,
key: data.key,
options,
};
emit('add', item)
visible.value = false
};
const onCancel = () => {
visible.value = false
}
const _delete = (_key: string) => {
emit('delete', _key)
}
const _update = (data: ActionsType, options: any, item: any) => {
const olData = pick(item, ['terms']);
emit('add', {...olData, ...data, options})
visible.value = false
}
</script>
<style lang="less" scoped>

View File

@ -0,0 +1,64 @@
<template>
<div>
<template v-if="actionType === 'device'">
<Device v-bind="props" :value="data?.device" @cancel="onCancel" @save="onPropsOk" />
</template>
<template v-else-if="actionType === 'notify'">
<Notify v-bind="props" :value="data?.notify" @cancel="onCancel" @save="onPropsOk" />
</template>
<template v-else-if="actionType === 'delay'">
<Delay :value="data?.delay" @cancel="onCancel" @save="onPropsOk" />
</template>
</div>
</template>
<script lang="ts" setup>
import { PropType } from 'vue';
import { ActionsType } from '../../../typings';
import Delay from '../Delay/index.vue';
import Notify from '../Notify/index.vue';
import Device from '../Device/index.vue';
const props = defineProps({
branchesName: {
type: Number,
default: 0,
},
branchGroup: {
type: Number,
default: 0,
},
name: {
type: Number,
default: 0,
},
data: {
type: Object as PropType<ActionsType>,
},
parallel: {
type: Boolean,
},
actionType: {
type: String,
default: '',
},
});
const emit = defineEmits(['cancel', 'save']);
const onCancel = () => {
emit('cancel');
};
const onPropsOk = (data: any, options: any) => {
const _data = {
type: props.actionType,
executor: props.actionType,
key: props?.data?.key || `${props.actionType}_${new Date().getTime()}`,
device: {
...data,
},
};
emit('save', { ..._data }, options);
};
</script>

View File

@ -25,24 +25,51 @@
float="right"
/>
</a-form-item>
<template v-if="actionType === 'device'">
<Device @cancel="onCancel" @save="onPropsOk" />
</template>
<template v-else-if="actionType === 'notify'">
<Notify @cancel="onCancel" @save="onPropsOk" />
</template>
<template v-else-if="actionType === 'delay'">
<Delay @cancel="onCancel" @save="onPropsOk" />
</template>
<ActionTypeComponent
v-bind="props"
v-if="!!actionType"
:actionType="actionType"
@save="onPropsOk"
@cancel="onPropsCancel"
/>
</a-form>
</j-modal>
</template>
<script lang="ts" setup>
import { getImage } from '@/utils/comm';
import Delay from '../Delay/index.vue'
import Notify from '../Notify/index.vue'
import Device from '../Device/index.vue'
import Delay from '../Delay/index.vue';
import Notify from '../Notify/index.vue';
import Device from '../Device/index.vue';
import { PropType } from 'vue';
import { ActionsType } from '../../../typings';
import ActionTypeComponent from './ActionTypeComponent.vue'
import { randomString } from '@/utils/utils';
const props = defineProps({
branchesName: {
type: Number,
default: 0,
},
branchGroup: {
type: Number,
default: 0,
},
name: {
type: Number,
default: 0,
},
data: {
type: Object as PropType<ActionsType>,
default: () => ({
key: randomString()
})
},
parallel: {
type: Boolean,
},
});
const emit = defineEmits(['cancel', 'save']);
@ -81,25 +108,42 @@ const options = [
const actionForm = ref();
const formModel = reactive({
type: '',
type: 'device',
});
const actionType = ref<string>('')
const actionType = ref<string>('');
watch(
() => props.data,
(newVal) => {
if (newVal?.executor) {
formModel.type = (newVal?.executor === 'alarm' ? newVal?.alarm?.mode : newVal?.executor) as string
}
},
{
immediate: true,
deep: true,
},
);
const onOk = () => {
actionForm.value.validate().then((values: any) => {
actionType.value = values?.type;
if (values?.type === 'relieve' || values?.type === 'trigger') {
emit('save', { ...props.data, executor: 'alarm', alarm: { mode: values.type } }, {});
}
});
};
const onCancel = () => {
emit('cancel');
};
const onOk = () => {
actionForm.value.validate().then((values: any) => {
actionType.value = values?.type
if (values?.type === 'relieve' || values?.type === 'trigger') {
// emit('save');
// props.save({ ...props.data, executor: 'alarm', alarm: { mode: values.type } }, {});
}
});
const onPropsOk = (data: any, options?: any) => {
emit('save', { ...data, executor: data.type }, options);
actionType.value = ''
};
const onPropsOk = (data: any, option: any) => {
console.log(data, option)
const onPropsCancel = () => {
actionType.value = ''
}
</script>

View File

@ -7,16 +7,12 @@
class="search"
/>
<div style="height: 400px; overflow-y: auto">
<JTable
<JProTable
:columns="columns"
:request="ConfigApi.list"
model="CARD"
:bodyStyle="{
paddingRight: 0,
paddingLeft: 0,
}"
:defaultParams="{
:request="(e) => ConfigApi.list({
...e,
terms: [
...e?.terms,
{
terms: [
{
@ -27,11 +23,15 @@
],
},
],
sorts: [{ name: 'createTime', order: 'desc' }],
sorts: [{ name: 'id', value: props.value }, { name: 'createTime', order: 'desc' }],
})"
model="CARD"
:bodyStyle="{
paddingRight: 0,
paddingLeft: 0,
}"
:params="params"
:gridColumn="2"
:gridColumns="[2, 2, 2]"
:rowSelection="{
selectedRowKeys: _selectedRowKeys,
}"
@ -82,7 +82,7 @@
</template>
</CardBox>
</template>
</JTable>
</JProTable>
</div>
</template>
@ -175,4 +175,9 @@ watch(
padding-right: 0px;
padding-left: 0px;
}
.logo{
width: 88px;
height: 88px;
}
</style>

View File

@ -7,20 +7,16 @@
class="search"
/>
<div style="height: 400px; overflow-y: auto">
<JTable
<JProTable
:columns="columns"
:request="(e) => TemplateApi.getListByConfigId(props.notifierId, e)"
:request="(e) => handleData(e)"
model="CARD"
:bodyStyle="{
paddingRight: 0,
paddingLeft: 0,
}"
:defaultParams="{
sorts: [{ name: 'createTime', order: 'desc' }],
}"
:params="params"
:gridColumn="2"
:gridColumns="[2, 2, 2]"
:rowSelection="{
selectedRowKeys: _selectedRowKeys,
}"
@ -71,7 +67,7 @@
</template>
</CardBox>
</template>
</JTable>
</JProTable>
</div>
</template>
@ -142,6 +138,27 @@ const handleClick = (dt: any) => {
emit('update:value', dt.id);
};
const handleData = async (e: any) => {
const sorts = [
{ name: 'id', value: props.value },
{ name: 'createTime', order: 'desc' },
];
const resp = await TemplateApi.getListByConfigId(props.notifierId, {
...e,
sorts: sorts,
});
return {
code: resp.message,
result: {
data: resp.result ? resp.result : [],
pageIndex: 0,
pageSize: resp.result.length,
total: resp.result.length,
},
status: resp.status,
};
};
watch(
() => props.value,
(newValue) => {
@ -164,4 +181,9 @@ watch(
padding-right: 0px;
padding-left: 0px;
}
.logo {
width: 88px;
height: 88px;
}
</style>

View File

@ -42,6 +42,7 @@ watch(
);
onMounted(() => {
loading.value = true
notice.queryMessageType().then((resp) => {
if (resp.status === 200) {
options.value = (resp.result as any[]).map((item) => {
@ -52,6 +53,7 @@ onMounted(() => {
};
});
}
loading.value = false
});
notifyType.value = props.value
});

View File

@ -5,38 +5,44 @@
ref="formRef"
:model="modelRef"
>
<template v-for="item in variableDefinitions" :key="item.id">
<a-form-item
:name="item.id"
:label="item.name"
:rules="[{ required: true, message: `请输入${item.name}` }]"
:name="`${item?.id}`"
:label="item?.name"
v-for="item in variableDefinitions"
:key="item.id"
:required="getType(item) !== 'file' ? true : false"
:rules="[
{
validator: (_rule, value) => checkValue(_rule, value, item),
trigger: 'change',
},
]"
>
<User
:notify="notify"
v-if="getType(item) === 'user'"
v-model="modelRef[`${item.id}`]"
v-model:value="modelRef[item.id]"
/>
<Org
:notify="notify"
v-else-if="getType(item) === 'org'"
v-model="modelRef[`${item.id}`]"
v-model:value="modelRef[item.id]"
/>
<Tag
:notify="notify"
v-else-if="getType(item) === 'tag'"
v-model="modelRef[`${item.id}`]"
v-model:value="modelRef[item.id]"
/>
<InputFile
v-else-if="getType(item) === 'file'"
v-model="modelRef[`${item.id}`]"
v-model:value="modelRef[item.id]"
/>
<a-input
v-else-if="getType(item) === 'link'"
v-model="modelRef[`${item.id}`]"
/>
<BuildIn
v-else
v-model="modelRef[`${item.id}`]"
v-model:value="modelRef[item.id]"
/>
<BuildIn v-else :item="item" v-model:value="modelRef[item.id]" />
</a-form-item>
</template>
</a-form>
</template>
@ -46,25 +52,103 @@ import Org from './variableItem/Org.vue';
import Tag from './variableItem/Tag.vue';
import InputFile from './variableItem/InputFile.vue';
import User from './variableItem/User.vue';
import { PropType } from 'vue';
const props = defineProps({
variableDefinitions: {
type: Array,
type: Array as PropType<any>,
default: () => [],
},
value: {
type: Object,
default: () => {},
},
notify: {
type: Object,
default: () => {},
},
});
const formRef = ref();
const modelRef = reactive({});
Object.assign(formRef, props.value);
watchEffect(() => {
Object.assign(modelRef, props.value);
});
const getType = (item: any) => {
return item.expands?.businessType || item.type;
};
const checkValue = (_rule: any, value: any, item: any) => {
const type = item.expands?.businessType || item?.type;
if (type === 'file') {
return Promise.resolve();
} else if (type === 'link') {
if (!value) {
return Promise.reject(new Error('请输入' + item.name));
} else if (value.length > 64) {
return Promise.reject(new Error('最多64个字符'));
}
} else if (type === 'tag' && !value) {
return Promise.reject(new Error('请选择' + item.name));
} else if (['date', 'org'].includes(type)) {
if (!value) {
return Promise.reject(new Error('请选择' + item.name));
} else {
if (value?.source === 'upper') {
if (!value.upperKey) {
return Promise.reject(new Error('请选择' + item.name));
} else {
return Promise.resolve();
}
} else {
if (!value.value) {
return Promise.reject(new Error('请选择' + item.name));
} else {
return Promise.resolve();
}
}
}
} else if (value.source === 'fixed' && !value.value) {
return Promise.reject(new Error('请输入' + item.name));
} else if (value.source === 'relation' && !value.value && !value.relation) {
return Promise.reject(new Error('请选择' + item.name));
} else if (value.source === 'upper' && !value.upperKey) {
return Promise.reject(new Error('请选择' + item.name));
} else if (type === 'user') {
if (props.notify.notifyType === 'email') {
if (Array.isArray(value.value)) {
if (!value.value.length) {
return Promise.reject(new Error('请输入收件人'));
}
const reg =
/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/;
const flag = value.value.every((it: string) => {
return reg.test(it);
});
if (!flag) {
return Promise.reject(new Error('请输入正确的邮箱地址'));
} else {
return Promise.resolve();
}
} else {
return Promise.resolve();
}
}
if (
props.notify.notifyType &&
['sms', 'voice'].includes(props?.notify?.notifyType)
) {
const reg = /^[1][3-9]\d{9}$/;
if (!reg.test(value.value)) {
return Promise.reject(new Error('请输入正确的手机号码'));
} else {
return Promise.resolve();
}
}
}
return Promise.resolve();
};
</script>

View File

@ -33,12 +33,18 @@
</template>
<template v-if="current === 1">
<a-form-item name="notifierId">
<NotifyConfig v-model:value="formModel.notifierId" :notifyType="formModel.notifyType" />
<NotifyConfig
v-model:value="formModel.notifierId"
:notifyType="formModel.notifyType"
/>
</a-form-item>
</template>
<template v-if="current === 2">
<a-form-item name="templateId">
<NotifyTemplate v-model:value="formModel.templateId" :notifierId="formModel.notifierId" />
<NotifyTemplate
v-model:value="formModel.templateId"
:notifierId="formModel.notifierId"
/>
</a-form-item>
</template>
<template v-if="current === 3">
@ -46,6 +52,7 @@
<VariableDefinitions
:variableDefinitions="variable"
v-model:value="formModel.variables"
:notify="formModel"
/>
</a-form-item>
</template>
@ -55,8 +62,12 @@
<a-space>
<j-button v-if="current === 0" @click="onCancel">取消</j-button>
<j-button v-if="current > 0" @click="prev">上一步</j-button>
<j-button v-if="current < 3" type="primary" @click="next">下一步</j-button>
<j-button v-if="current === 3" type="primary" @click="onOk">确定</j-button>
<j-button v-if="current < 3" type="primary" @click="next"
>下一步</j-button
>
<j-button v-if="current === 3" type="primary" @click="onOk"
>确定</j-button
>
</a-space>
</template>
</j-modal>
@ -69,6 +80,22 @@ import NotifyTemplate from './NotifyTemplate.vue';
import VariableDefinitions from './VariableDefinitions.vue';
import { onlyMessage } from '@/utils/comm';
import Template from '@/api/notice/template';
import { PropType } from 'vue';
import { NotifyProps } from '../../../typings';
const props = defineProps({
value: {
type: Object as PropType<Partial<NotifyProps>>,
default: () => undefined,
},
options: {
type: Object,
},
name: {
type: Number,
default: 0,
},
});
const emit = defineEmits(['cancel', 'save']);
@ -78,33 +105,42 @@ const formModel = reactive({
notifyType: '',
notifierId: '',
templateId: '',
variables: [],
variables: {},
});
const variable = ref([]);
watch(
() => props.value,
(newVal) => {
Object.assign(formModel, newVal);
},
{ deep: true, immediate: true },
);
const jumpStep = async (val: number) => {
if (val === 0) {
current.value = val;
} else if (val === 1) {
console.log(formModel)
if (formModel.notifyType) {
current.value = val
current.value = val;
} else {
onlyMessage('请选择通知方式', 'error');
}
} else if (val === 2) {
if (formModel.notifierId) {
current.value = val
current.value = val;
} else {
onlyMessage('请选择通知配置', 'error');
}
} else if (val === 3) {
formModel.templateId = '1628943618904956928'
formModel.templateId = '1628943618904956928';
if (formModel.templateId) {
const resp = await Template.getTemplateDetail(formModel.templateId);
if (resp.status === 200) {
variable.value = resp.result?.variableDefinitions || [];
current.value = val
current.value = val;
}
} else {
onlyMessage('请选择通知模板', 'error');
@ -113,7 +149,7 @@ const jumpStep = async (val: number) => {
};
const onChange = (cur: number) => {
jumpStep(cur)
jumpStep(cur);
};
const prev = () => {
@ -121,7 +157,7 @@ const prev = () => {
};
const next = async () => {
jumpStep(current.value + 1)
jumpStep(current.value + 1);
};
const onCancel = () => {

View File

@ -1,3 +1,163 @@
<template>
build-in
<a-input-group compact>
<a-select
:options="[
{ label: '手动输入', value: 'fixed' },
{ label: '内置参数', value: 'upper' },
]"
style="width: 120px"
:value="value?.source"
@change="sourceChange"
/>
<template v-if="source === 'upper'">
<a-tree-select
v-model:value="upperKey"
:treeData="builtInList"
placeholder="请选择参数"
style="width: calc(100% - 120px)"
>
<template #title="{ name, description }">
<a-space>
{{ name }}
<spn style="color: grey; margin-left: 5px">{{ description }}</spn>
</a-space>
</template>
</a-tree-select>
</template>
<template v-else>
<a-date-picker
:value="value.value"
allowClear
format="YYYY-MM-DD HH:mm:ss"
style="width: calc(100% - 120px)"
v-if="item.type === 'date'"
@change="(_, dateString) => itemOnChange(dateString)"
/>
<a-input-number
:value="value.value"
allowClear
style="width: calc(100% - 120px)"
v-else-if="item.type === 'number'"
:placeholder="`请输入${item.name}`"
@change="itemOnChange"
/>
<a-input
:value="value.value"
allowClear
style="width: calc(100% - 120px)"
v-else
:placeholder="`请输入${item.name}`"
@change="(e) => itemOnChange(e.target.value)"
/>
</template>
</a-input-group>
</template>
<script lang="ts" setup>
import { queryBuiltInParams } from '@/api/rule-engine/scene';
import { useSceneStore } from '@/store/scene';
import { storeToRefs } from 'pinia';
const sceneStore = useSceneStore();
const { data } = storeToRefs(sceneStore);
const props = defineProps({
value: {
type: Object,
default: () => {
return {
source: 'fixed',
value: undefined,
upperKey: undefined,
};
},
},
item: {
type: Object,
default: () => {},
},
name: {
type: Number,
default: 0,
},
});
const emit = defineEmits(['update:value']);
const source = computed(() => {
return props.value?.source || 'fixed';
});
const builtInList = ref<any[]>([]);
const upperKey = ref();
const sourceChange = (val: any) => {
emit('update:value', {
...props.value,
source: val,
value: undefined,
});
};
const itemOnChange = (val: any) => {
emit('update:value', {
...props.value,
value: val,
});
};
const treeDataFilter = (arr: any[], type: string) => {
if (Array.isArray(arr) && arr.length) {
const list: any[] = [];
arr.map((item: any) => {
if (item.children) {
const children = treeDataFilter(item.children, type);
if (children.length) {
list.push({
...item,
title: item.name,
value: item.id,
disabled: true,
children,
});
}
} else {
if (
item.type === type ||
(type === 'double' &&
['int', 'float', 'double', 'long'].includes(item.type))
) {
list.push(item);
}
}
});
return list;
} else {
return [];
}
};
watch(
() => source.value,
(newVal) => {
const v = newVal;
if (v === 'upper') {
const params =
props.name - 1 >= 0 ? { action: props.name - 1 } : undefined;
queryBuiltInParams(unref(data), params).then((resp) => {
if (resp.status === 200) {
const arr = treeDataFilter(
resp.result as any[],
props.item.expands?.businessType || props.item?.type,
);
builtInList.value = arr;
}
});
}
},
{ deep: true, immediate: true },
);
</script>
<style lang="less" scoped>
</style>

View File

@ -1,3 +1,77 @@
<template>
input-file
<a-input
allowClear
placeholder="请上传文件"
v-model:value="url"
@change="onChange"
>
<template #addonAfter>
<a-upload
name="file"
:showUploadList="false"
:accept="'image/jpeg,image/png'"
:disabled="loading"
:headers="{
[TOKEN_KEY]: LocalStore.get(TOKEN_KEY),
}"
:action="`${BASE_API_PATH}/file/static`"
@change="handleChange"
@beforeUpload="handleBeforeUpload"
>
<j-button type="link" style="height: 30px">
<AIcon type="LoadingOutlined" v-if="loading" />
<AIcon type="PlusOutlined" v-else />
上传附件
</j-button>
</a-upload>
</template>
</a-input>
</template>
<script lang="ts" setup>
import { BASE_API_PATH, TOKEN_KEY } from '@/utils/variable';
import { LocalStore, onlyMessage } from '@/utils/comm';
const props = defineProps({
id: {
type: String,
default: '',
},
value: {
type: String,
default: '',
},
});
const emit = defineEmits(['update:value']);
const url = ref(props.value || undefined);
const loading = ref<boolean>(false);
const handleChange = (info: any) => {
if (info.file.status === 'uploading') {
loading.value = true;
}
if (info.file.status === 'done') {
info.file.url = info.file.response?.result;
loading.value = false;
const result = info.file.response?.result;
emit('update:value', result);
}
};
const handleBeforeUpload = (file: any) => {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
onlyMessage('请上传正确格式图片', 'error');
}
const isSize = file.size / 1024 / 1024 < 4;
if (!isSize) {
onlyMessage(`图片大小必须小于4M`, 'error');
}
return isJpgOrPng && isSize;
};
const onChange = (e: any) => {
emit('update:value', e.target.value);
};
</script>

View File

@ -1,3 +1,75 @@
<template>
org
<j-tree-select
:listHeight="200"
v-model:value="keys"
placeholder="请选择组织"
:tree-data="departmentTree"
@change="onChange"
:fieldNames="{
label: 'name',
value: 'id',
}"
/>
</template>
<script lang="ts" setup>
import ConfigApi from '@/api/notice/config';
const props = defineProps({
notify: {
type: Object,
default: () => {},
},
value: {
type: Array,
default: () => [],
},
});
const emit = defineEmits(['update:value']);
const departmentTree = ref<any[]>([]);
const keys = ref<any[]>([]);
const getDepartment = async (id: string) => {
if (props.notify.notifyType === 'dingTalk') {
const resp = await ConfigApi.dingTalkDept(id);
if (resp.status === 200) {
departmentTree.value = resp.result;
}
} else {
const resp = await ConfigApi.weChatDept(id);
if (resp.status === 200) {
departmentTree.value = resp.result;
}
}
};
watch(
() => props.value,
() => {
keys.value = props?.value || [];
},
{ deep: true, immediate: true },
);
watch(
() => props.notify.notifierId,
(newVal) => {
if (newVal) {
getDepartment(newVal);
}
},
{ deep: true, immediate: true },
);
const onChange = (key: string[], label: string[]) => {
emit('update:value', {
source: 'fixed',
value: key,
});
};
</script>
<style lang="less" scoped>
</style>

View File

@ -1,3 +1,65 @@
<template>
tag
<j-select
style="width: 100%"
v-model:value="keys"
placeholder="请选择标签"
:options="tagsList"
@change="onChange"
/>
</template>
<script lang="ts" setup>
import TemplateApi from '@/api/notice/template';
const props = defineProps({
notify: {
type: Object,
default: () => {},
},
value: {
type: String,
default: ''
},
});
const emit = defineEmits(['update:value']);
const tagsList = ref<any[]>([]);
const keys = ref<string>('');
const getDepartment = async (id: string) => {
const resp = await TemplateApi.getTags(id);
if (resp.status === 200) {
tagsList.value = resp.result.map((item: any) => ({value: item.id, label: item.name}))
}
};
watch(
() => props.value,
(newVal) => {
keys.value = newVal || ''
},
{ deep: true, immediate: true },
);
watch(
() => props.notify.notifierId,
(newVal) => {
if (newVal) {
getDepartment(newVal);
}
},
{ deep: true, immediate: true },
);
const onChange = (key: string, label: string[]) => {
// TODO label
emit('update:value', {
source: 'fixed',
value: key,
});
};
</script>
<style lang="less" scoped>
</style>

View File

@ -1,3 +1,274 @@
<template>
user
<a-input-group compact>
<a-select
style="width: 120px"
v-model:value="mySource"
@change="sourceChange"
>
<a-select-option key="5" value="relation">
平台用户
</a-select-option>
<a-select-option
v-if="notifyType === 'dingTalk'"
key="1"
value="fixed"
>
钉钉用户
</a-select-option>
<a-select-option
v-else-if="notifyType === 'weixin'"
key="2"
value="fixed"
>
微信用户
</a-select-option>
<a-select-option
v-else-if="notifyType === 'email'"
key="3"
value="fixed"
>
固定邮箱
</a-select-option>
<a-select-option v-else key="4" value="fixed">
固定号码
</a-select-option>
</a-select>
<a-tree-select
v-if="source === 'relation'"
style="width: calc(100% - 120px)"
placeholder="请选择收信人"
@select="treeSelect"
:tree-data="treeData"
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
:value="relationData"
/>
<a-select
style="width: calc(100% - 120px)"
v-else-if="['dingTalk', 'weixin'].includes(notifyType)"
placeholder="请选择收信人"
:value="value?.value"
@change="(val) => onChange(val)"
>
<a-select-option
v-for="item in relationList"
:key="item.id"
:value="item.id"
>{{ item.name }}</a-select-option
>
</a-select>
<a-input
style="width: calc(100% - 120px)"
v-else-if="['email'].includes(notifyType)"
placeholder="请输入固定邮箱"
:value="value?.value"
:multiple="true"
@change="(e) => onChange(e.target.value)"
></a-input>
<a-input
style="width: calc(100% - 120px)"
v-else-if="['sms', 'voice'].includes(notifyType)"
placeholder="请输入固定号码"
:value="value?.value"
@change="(e) => onChange(e.target.value)"
></a-input>
</a-input-group>
</template>
<script lang="ts" setup>
import { storeToRefs } from 'pinia';
import { useSceneStore } from '@/store/scene';
import NoticeApi from '@/api/notice/config';
const sceneStore = useSceneStore();
const { data } = storeToRefs(sceneStore);
const props = defineProps({
notify: {
type: Object,
default: () => {},
},
value: {
type: [Object],
default: () => {},
},
});
const emit = defineEmits(['update:value']);
const notifyType = computed(() => {
return props.notify?.notifyType;
});
const notifierId = computed(() => {
return props.notify?.notifierId;
});
const source = computed(() => {
if (props.value) {
return props.value?.source || 'relation';
} else {
return 'relation';
}
});
const triggerType = computed(() => {
return data?.trigger || data?.trigger?.type;
});
const relationData = computed(() => {
const item = props.value;
if (item?.source === 'relation') {
const relation = item?.relation;
if (relation) {
if (relation.objectId) {
//
return relation.objectId;
} else {
//
return relation.related?.relation;
}
}
}
});
const relationList = ref<any[]>([]);
const treeData = ref<any[]>([
{
title: '平台用户',
value: 'p1',
key: 'p1',
selectable: false,
children: [],
},
]);
const mySource = ref<string>('relation');
const getRelationUsers = async (notifyType: string, notifierId: string) => {
let resp = undefined;
if (notifyType === 'dingTalk') {
resp = await NoticeApi.queryDingTalkUsers(notifierId);
} else {
resp = await NoticeApi.queryWechatUsers(notifierId);
}
if (resp && resp.status === 200) {
relationList.value = resp.result;
}
};
const getUser = async (_source: string, triggerType: string) => {
const newTree = [
{
title: '平台用户',
value: 'p1',
key: 'p1',
selectable: false,
children: [],
},
];
let relationResp = undefined;
const platformResp = await NoticeApi.getPlatformUsers({
paging: false,
sorts: [{ name: 'name', order: 'asc' }],
});
if (triggerType && triggerType === 'device' && _source === 'relation') {
relationResp = await NoticeApi.getRelationUsers({
paging: false,
sorts: [{ name: 'name', order: 'asc' }],
});
}
if (platformResp.status === 200) {
newTree[0].children = platformResp.result.map((item: any) => {
return {
...item,
value: item.id,
key: item.id,
title: item.name,
};
});
}
if (relationResp && relationResp.success) {
newTree.push({
title: '关系用户',
value: 'p2',
key: 'p2',
selectable: false,
children: relationResp.result.map((item: any) => {
return {
...item,
value: item.id,
key: item.id,
title: item.name,
isRelation: true,
};
}),
});
}
treeData.value = newTree;
};
const treeSelect = (val: string, _data: any) => {
const obj = {
source: source.value,
relation: {}
};
if (_data?.isRelation) {
obj.relation = {
objectType: 'device',
objectSource: {
source: 'upper',
upperKey: 'scene.deviceId',
},
related: {
objectType: 'user',
relation: val,
},
};
} else {
obj.relation = {
objectType: 'user',
objectId: val,
};
}
emit('update:value', obj);
};
const sourceChange = (v: any) => {
emit('update:value', {
source: v,
});
};
const onChange = (val: any) => {
emit('update:value', {
source: source.value,
value: val,
});
};
watch(
() => triggerType.value,
(newVal) => {
if (newVal && source.value === 'relation') {
getUser(source.value, newVal);
}
},
{ deep: true, immediate: true },
);
watch(
() => source.value,
(newVal) => {
const v = newVal;
mySource.value = v as string;
if (
v === 'fixed' &&
['dingTalk', 'weixin'].includes(notifyType.value)
) {
getRelationUsers(notifyType.value, notifierId.value);
} else {
getUser(v, triggerType.value);
}
},
{ deep: true, immediate: true },
);
</script>

View File

@ -42,6 +42,8 @@
:branchesName="props.name"
:parallel="true"
:actions="parallelArray.length ? parallelArray[0].actions : []"
@add="onAdd"
@delete="onDelete"
/>
</div>
</a-collapse-panel>
@ -53,8 +55,6 @@
<script lang="ts" setup>
import ShakeLimit from '../components/ShakeLimit/index.vue';
import { List } from './ListItem';
import { storeToRefs } from 'pinia';
import { useSceneStore } from '@/store/scene';
import { BranchesThen } from '../../typings';
import { PropType } from 'vue';
@ -73,7 +73,9 @@ const props = defineProps({
openShakeLimit: Boolean,
});
const shakeLimit = ref({});
const shakeLimit = ref({
enabled: false
});
const activeKeys = ref<string[]>(['1']);
const parallelArray = ref<BranchesThen[]>([]);
const serialArray = ref<BranchesThen[]>([]);
@ -105,6 +107,13 @@ watch(
immediate: true,
},
);
const onDelete = (_key: string) => {
console.log(_key)
}
const onAdd = (data: any) => {
console.log(data)
}
</script>
<style lang="less" scoped>

View File

@ -3901,10 +3901,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.3:
version "1.0.3"
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.3.tgz#303ca83cf6096721e49e72d1a3a73b054b0aa7fa"
integrity sha512-Jm7tP/CtnK2GIRSPTjd/UOw8emZ3C7/i9af8m+XCM8wi/J1SZh4cZGc487vR1DPxyWZfJjG87Zdy45DZ5EMw2w==
jetlinks-ui-components@^1.0.4:
version "1.0.4"
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.4.tgz#253d07d94bc7471497aca5ec80aad6bda247e047"
integrity sha512-QqfYH8ShXou3aMvgUSJ23sf1ftmIghDm1zOZyHkwpxh0SMfPHv5k0/sMZsvfGGu+u8iyFlmjpezbReuxvwj9fA==
dependencies:
"@vueuse/core" "^9.12.0"
ant-design-vue "^3.2.15"