Merge branch 'dev' into dev-hub

This commit is contained in:
jackhoo_98 2023-03-16 09:56:14 +08:00
commit 62592b6e83
93 changed files with 7582 additions and 6593 deletions

20
package-lock.json generated
View File

@ -5180,7 +5180,7 @@
"node_modules/jetlinks-ui-components": {
"version": "1.0.5",
"resolved": "http://47.108.170.157:9013/jetlinks-ui-components/-/jetlinks-ui-components-1.0.5.tgz",
"integrity": "sha512-u1jqTyBWHB+b/NOKimucxNMYxet6g9Hi/I30T1VNWGq+x/Gf2zjz8dJoxjUcLW+9t/yjG6opWUE8Z8NWQRp3Eg==",
"integrity": "sha512-zrMe/4PjtWNCQtlP6kNLA7hVyQ7ftPSgivQZp8TGcpbUMOU/IYyCLQ0+VoWcqEopTSt1zEzEazodR4EjEzv7pQ==",
"dependencies": {
"@vueuse/core": "^9.12.0",
"ant-design-vue": "^3.2.15",
@ -8343,6 +8343,8 @@
},
"node_modules/npm/node_modules/debug/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"inBundle": true,
"license": "MIT"
},
@ -9913,6 +9915,8 @@
},
"node_modules/npm/node_modules/request/node_modules/form-data": {
"version": "2.3.3",
"resolved": "https://registry.jetlinks.cn/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"inBundle": true,
"license": "MIT",
"dependencies": {
@ -9926,6 +9930,8 @@
},
"node_modules/npm/node_modules/request/node_modules/tough-cookie": {
"version": "2.5.0",
"resolved": "https://registry.jetlinks.cn/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"inBundle": true,
"license": "BSD-3-Clause",
"dependencies": {
@ -10156,6 +10162,8 @@
},
"node_modules/npm/node_modules/string-width/node_modules/strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==",
"inBundle": true,
"license": "MIT",
"dependencies": {
@ -17272,7 +17280,7 @@
"jetlinks-ui-components": {
"version": "1.0.5",
"resolved": "http://47.108.170.157:9013/jetlinks-ui-components/-/jetlinks-ui-components-1.0.5.tgz",
"integrity": "sha512-u1jqTyBWHB+b/NOKimucxNMYxet6g9Hi/I30T1VNWGq+x/Gf2zjz8dJoxjUcLW+9t/yjG6opWUE8Z8NWQRp3Eg==",
"integrity": "sha512-zrMe/4PjtWNCQtlP6kNLA7hVyQ7ftPSgivQZp8TGcpbUMOU/IYyCLQ0+VoWcqEopTSt1zEzEazodR4EjEzv7pQ==",
"requires": {
"@vueuse/core": "^9.12.0",
"ant-design-vue": "^3.2.15",
@ -19672,6 +19680,8 @@
"dependencies": {
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"bundled": true
}
}
@ -20833,6 +20843,8 @@
"dependencies": {
"form-data": {
"version": "2.3.3",
"resolved": "https://registry.jetlinks.cn/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"bundled": true,
"requires": {
"asynckit": "^0.4.0",
@ -20842,6 +20854,8 @@
},
"tough-cookie": {
"version": "2.5.0",
"resolved": "https://registry.jetlinks.cn/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"bundled": true,
"requires": {
"psl": "^1.1.28",
@ -20994,6 +21008,8 @@
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==",
"bundled": true,
"requires": {
"ansi-regex": "^3.0.0"

View File

@ -1,7 +1,9 @@
import server from '@/utils/request'
// 获取记录列表
export const getList_api = (data:object): any =>server.post(`/notifications/_query`,data)
export const getList_api = (data:object): any =>server.get(`/notifications/_query`,encodeParams(data))
// 获取未读记录列表
export const getListByUnRead_api = (data:object): any =>server.post(`/notifications/_query`,data)
// 修改记录状态
export const changeStatus_api = (type:'_read'|'_unread',data:string[]): any =>server.post(`/notifications/${type}`,data)

View File

@ -42,7 +42,7 @@ export default {
// 不分页-列表
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`),
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`),
queryWechatUsers: (id: string) => get<any>(`/notifier/wechat/corp/${id}/users?sorts[0].name="name"&sorts[0].order=asc`),
}

View File

@ -19,7 +19,7 @@
</template>
<script setup lang="tsx">
import { getList_api } from '@/api/account/notificationRecord';
import { getListByUnRead_api } from '@/api/account/notificationRecord';
import NoticeInfo from './NoticeInfo.vue';
import { getWebSocket } from '@/utils/websocket';
import { notification, Button } from 'ant-design-vue';
@ -93,7 +93,7 @@ const getList = () => {
},
],
};
getList_api(params)
getListByUnRead_api(params)
.then((resp: any) => {
list.value = resp.result.data;
total.value = resp.result.total;

View File

@ -53,9 +53,10 @@
v-model:value="myValue"
placeholder="请输入图片链接"
allowClear
@change='inputChange'
>
<template #addonAfter>
<a-upload
<j-upload
name="file"
:action="FILE_UPLOAD"
:headers="headers"
@ -63,7 +64,7 @@
@change="handleFileChange"
>
<AIcon type="UploadOutlined" />
</a-upload>
</j-upload>
</template>
</j-input>
<j-input
@ -182,6 +183,7 @@ const handleFileChange = (info: UploadChangeParam<UploadFile<any>>) => {
const url = info.file.response?.result;
myValue.value = url;
emit('update:modelValue', url);
emit('change', url);
}
};

View File

@ -149,8 +149,8 @@ const columns = [
},
{
title: '说明',
dataIndex: 'describe',
key: 'describe',
dataIndex: 'description',
key: 'description',
search: {
type: 'string',
},

View File

@ -191,8 +191,8 @@ const columns = [
},
{
title: '说明',
dataIndex: 'describe',
key: 'describe',
dataIndex: 'description',
key: 'description',
},
{
title: '状态',

View File

@ -24,7 +24,7 @@
margin-top: 20px;
"
>
<a-upload
<j-upload
v-model:file-list="upload.fileList"
accept=".jpg,.png,.jfif,.pjp,.pjpeg,.jpeg"
:maxCount="1"
@ -40,7 +40,7 @@
<AIcon type="UploadOutlined" />
更换头像
</j-button>
</a-upload>
</j-upload>
</div>
</div>
<div

View File

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

View File

@ -1,6 +1,6 @@
<!-- 绑定设备 -->
<template>
<a-modal
<j-modal
:maskClosable="false"
width="1000px"
:visible="true"
@ -12,7 +12,7 @@
:confirmLoading="btnLoading"
>
<div style="margin-top: 10px">
<Search
<pro-search
:columns="columns"
target="child-device-bind"
@search="handleSearch"
@ -73,14 +73,14 @@
}}
</template>
<template #state="slotProps">
<a-badge
<j-badge
:text="slotProps.state.text"
:status="statusMap.get(slotProps.state.value)"
/>
</template>
</JProTable>
</div>
</a-modal>
</j-modal>
</template>
<script setup lang="ts">

View File

@ -1,14 +1,14 @@
<template>
<a-select allowClear v-model:value="_value" @change="onChange" placeholder="请选择" style="width: 100%">
<a-select-option
<j-select allowClear v-model:value="_value" @change="onChange" placeholder="请选择" style="width: 100%">
<j-select-option
v-for="item in list"
:key="item.id"
:value="item.id"
:label="item.name"
:filter-option="filterOption"
>{{ item.name }}</a-select-option
>{{ item.name }}</j-select-option
>
</a-select>
</j-select>
</template>
<script lang="ts" setup>

View File

@ -1,5 +1,5 @@
<template>
<a-modal
<j-modal
width="900px"
title="批量映射"
visible
@ -10,49 +10,49 @@
<div class="map-tree-top">
采集器的点位名称与属性名称一致时将自动映射绑定有多个采集器点位名称与属性名称一致时以第1个采集器的点位数据进行绑定
</div>
<a-spin :spinning="loading">
<j-spin :spinning="loading">
<div class="map-tree-content">
<a-card class="map-tree-content-card" title="源数据">
<a-tree
<j-card class="map-tree-content-card" title="源数据">
<j-tree
checkable
:height="300"
:tree-data="dataSource"
:checkedKeys="checkedKeys"
@check="onCheck"
/>
</a-card>
</j-card>
<div style="width: 100px">
<a-button
<j-button
:disabled="rightList.length >= leftList.length"
@click="onRight"
>加入右侧</a-button
>加入右侧</j-button
>
</div>
<a-card class="map-tree-content-card" title="采集器">
<a-list
<j-card class="map-tree-content-card" title="采集器">
<j-list
size="small"
:data-source="rightList"
class="map-tree-content-card-list"
>
<template #renderItem="{ item }">
<a-list-item>
<j-list-item>
{{ item.title }}
<template #actions>
<a-popconfirm
<j-popconfirm
title="确定删除?"
@confirm="_delete(item.key)"
>
<AIcon type="DeleteOutlined" />
</a-popconfirm>
</j-popconfirm>
</template>
</a-list-item>
</j-list-item>
</template>
</a-list>
</a-card>
</j-list>
</j-card>
</div>
</a-spin>
</j-spin>
</div>
</a-modal>
</j-modal>
</template>
<script lang="ts" setup>
@ -119,6 +119,7 @@ const onCheck = (keys: string[], e: any) => {
};
const onRight = () => {
console.log(rightList.value,leftList.value);
rightList.value = leftList.value;
};

View File

@ -1,49 +1,51 @@
<template>
<a-spin :spinning="loading" v-if="_metadata.length">
<a-card :bordered="false">
<j-spin :spinning="loading" v-if="_metadata.length">
<j-card :bordered="false">
<template #title>
<TitleComponent data="点位映射"></TitleComponent>
</template>
<template #extra>
<a-space>
<a-button @click="showModal">批量映射</a-button>
<a-button type="primary" @click="onSave">保存并应用</a-button>
</a-space>
<j-space>
<j-button @click="showModal">批量映射</j-button>
<j-button type="primary" @click="onSave"
>保存并应用</j-button
>
</j-space>
</template>
<a-form ref="formRef" :model="modelRef">
<a-table :dataSource="modelRef.dataSource" :columns="columns">
<j-form ref="formRef" :model="modelRef">
<j-table :dataSource="modelRef.dataSource" :columns="columns">
<template #headerCell="{ column }">
<template v-if="column.key === 'collectorId'">
采集器
<a-tooltip title="边缘网关代理的真实物理设备">
<j-tooltip title="边缘网关代理的真实物理设备">
<AIcon type="QuestionCircleOutlined" />
</a-tooltip>
</j-tooltip>
</template>
</template>
<template #bodyCell="{ column, record, index }">
<template v-if="column.dataIndex === 'channelId'">
<a-form-item
<j-form-item
:name="['dataSource', index, 'channelId']"
>
<a-select
<j-select
style="width: 100%"
v-model:value="record[column.dataIndex]"
placeholder="请选择"
allowClear
:filter-option="filterOption"
>
<a-select-option
<j-select-option
v-for="item in channelList"
:key="item.value"
:value="item.value"
:label="item.label"
>{{ item.label }}</a-select-option
>{{ item.label }}</j-select-option
>
</a-select>
</a-form-item>
</j-select>
</j-form-item>
</template>
<template v-if="column.dataIndex === 'collectorId'">
<a-form-item
<j-form-item
:name="['dataSource', index, 'collectorId']"
:rules="[
{
@ -58,10 +60,10 @@
type="COLLECTOR"
:edgeId="instanceStore.current.id"
/>
</a-form-item>
</j-form-item>
</template>
<template v-if="column.dataIndex === 'pointId'">
<a-form-item
<j-form-item
:name="['dataSource', index, 'pointId']"
:rules="[
{
@ -76,33 +78,33 @@
type="POINT"
:edgeId="instanceStore.current.id"
/>
</a-form-item>
</j-form-item>
</template>
<template v-if="column.dataIndex === 'id'">
<a-badge
<j-badge
v-if="record[column.dataIndex]"
status="success"
text="已绑定"
/>
<a-badge v-else status="error" text="未绑定" />
<j-badge v-else status="error" text="未绑定" />
</template>
<template v-if="column.key === 'action'">
<a-tooltip title="解绑">
<a-popconfirm
<j-tooltip title="解绑">
<j-popconfirm
title="确认解绑"
:disabled="!record.id"
@confirm="unbind(record.id)"
>
<a-button type="link" :disabled="!record.id"
<j-button type="link" :disabled="!record.id"
><AIcon type="icon-jiebang"
/></a-button>
</a-popconfirm>
</a-tooltip>
/></j-button>
</j-popconfirm>
</j-tooltip>
</template>
</template>
</a-table>
</a-form>
</a-card>
</j-table>
</j-form>
</j-card>
<PatchMapping
:deviceId="instanceStore.current.parentId"
v-if="visible"
@ -112,10 +114,10 @@
:edgeId="instanceStore.current.id"
:deviceData="deviceData"
/>
</a-spin>
<a-card v-else>
<JEmpty description="暂无数据,请配置物模型" style="margin: 10% 0" />
</a-card>
</j-spin>
<j-card v-else>
<JEmpty description="暂无数据,请配置物模型" style="margin: 10% 0" />
</j-card>
</template>
<script lang="ts" setup>
@ -175,7 +177,7 @@ const filterOption = (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
};
const props = defineProps(['productList']);
const _emit = defineEmits(['close']);
const _emit = defineEmits(['close','getEdgeMap']);
const instanceStore = useInstanceStore();
let _metadata = ref();
const loading = ref<boolean>(false);
@ -187,7 +189,7 @@ const modelRef = reactive({
const deviceData = ref();
const formRef = ref();
const visible = ref<boolean>(false);
/**获取通道列表 */
const getChannel = async () => {
if (instanceStore.current?.id) {
const resp: any = await edgeChannel(instanceStore.current.id);
@ -200,51 +202,31 @@ const getChannel = async () => {
}
}
};
/**
* 初始化
*/
const handleSearch = async () => {
loading.value = true;
getChannel();
modelRef.dataSource = _metadata.value;
console.log(modelRef.dataSource);
// if (_metadata.value && _metadata.value.length) {
// console.log(1234);
// const resp: any = await getEdgeMap(instanceStore.current?.orgId || '', {
// deviceId: instanceStore.current.id,
// query: {},
// }).catch(() => {
// modelRef.dataSource = _metadata;
// loading.value = false;
// });
// if (resp.status === 200) {
// const array = resp.result?.[0].reduce((x: any, y: any) => {
// const metadataId = _metadata.find(
// (item: any) => item.metadataId === y.metadataId,
// );
// if (metadataId) {
// Object.assign(metadataId, y);
// } else {
// x.push(y);
// }
// return x;
// }, _metadata);
// modelRef.dataSource = array;
// }
// }
loading.value = false;
};
/**
* 解绑映射
* @param id 选择解绑id
*/
const unbind = async (id: string) => {
if (id) {
const resp = await removeEdgeMap(
instanceStore.current?.parentId || '',
instanceStore.current?.id || '',
{
deviceId: instanceStore.current.id,
deviceId: instanceStore.current.parentId,
idList: [id],
},
);
if (resp.status === 200) {
message.success('操作成功!');
handleSearch();
_emit('getEdgeMap');
}
}
};
@ -280,8 +262,8 @@ const onSave = async () => {
productName: props.productList.find(
(item: any) => item.id === form.value?.productId,
).name,
parentId: instanceStore.current.id,
id: instanceStore.current.parentId
parentId: instanceStore.current.id, // id
id: instanceStore.current.parentId // id
? instanceStore.current.parentId
: undefined,
};
@ -329,7 +311,4 @@ const showModal = async () => {
</script>
<style lang="less" scoped>
:deep(.ant-form-item) {
margin: 0 !important;
}
</style>

View File

@ -2,7 +2,7 @@
<div>
<TitleComponent data="基本信息">
<template #extra>
<j-button @click="comeBack">返回</j-button>
<j-button @click="comeBack" style="margin-left: 10px;">返回</j-button>
</template>
</TitleComponent>
<j-form layout="vertical" :model="form" ref="formRef">
@ -39,7 +39,10 @@
</j-row>
<j-row :gutter="24" v-if="visible">
<j-col :span="24"
><EdgeMap :productList="productList" @close="comeBack"
><EdgeMap
:productList="productList"
@close="comeBack"
@getEdgeMap="getEdgeMapData"
/></j-col>
</j-row>
</j-form>
@ -64,67 +67,80 @@ const formRef = ref();
const emit = defineEmits(['closeChildSave']);
const productList = ref();
const visible = ref(false);
/**
* 获取产品列表
*/
const getProductList = async () => {
const res = await getProductListNoPage({
terms: [{ column: 'accessProvider', value: 'edge-child-device' }],
});
if (res.status === 200) {
productList.value = res.result;
if (props.childData?.id) {
current.value.parentId = props.childData.id;
form.name = props.childData?.name;
form.productId = props.childData?.productId;
selectChange(form.productId);
if (current.value.metadata) {
const metadata = current.value.metadata;
if (metadata && metadata.length !== 0) {
getEdgeMap(current.value.id, {
deviceId: props.childData.id,
query: {},
}).then((res) => {
if (res.status === 200) {
// console.log(res.result)
//
const array = res.result[0]?.reduce(
(x: any, y: any) => {
const metadataId = metadata.find(
(item: any) =>
item.metadataId === y.metadataId,
);
if (metadataId) {
Object.assign(metadataId, y);
} else {
x.push(y);
}
return x;
},
metadata,
);
//
const items = array.filter(
(item: any) => item.metadataName,
);
current.value.metadata = items;
const delList = array
.filter((a: any) => !a.metadataName)
.map((b: any) => b.id);
//
if (delList && delList.length !== 0) {
removeEdgeMap(current.value.id, {
deviceId: props.childData.id,
idList: [...delList],
});
}
}
});
}
}
visible.value = true;
} else {
current.value.parentId = '';
}
getEdgeMapData();
}
};
/**
* 获取映射数据
*/
const getEdgeMapData = () => {
if (props.childData?.id) {
current.value.parentId = props.childData.id;
form.name = props.childData?.name;
form.productId = props.childData?.productId;
selectChange(form.productId);
if (current.value.metadata) {
const metadata = current.value.metadata;
if (metadata && metadata.length !== 0) {
getEdgeMap(current.value.id, {
deviceId: props.childData.id,
query: {},
}).then((res) => {
if (res.status === 200) {
// console.log(res.result)
//
const array = res.result[0]?.reduce(
(x: any, y: any) => {
const metadataId = metadata.find(
(item: any) =>
item.metadataId === y.metadataId,
);
if (metadataId) {
Object.assign(metadataId, y);
} else {
x.push(y);
}
return x;
},
metadata,
);
//
const items = array.filter(
(item: any) => item.metadataName,
);
current.value.metadata = items;
const delList = array
.filter((a: any) => !a.metadataName)
.map((b: any) => b.id);
//
if (delList && delList.length !== 0) {
removeEdgeMap(current.value.id, {
deviceId: props.childData.id,
idList: [...delList],
});
}
}
});
}
}
visible.value = true;
} else {
current.value.parentId = '';
}
};
/**
* 根据产品id获取对应映射列表
* @param e 产品id
*/
const selectChange = (e: any) => {
if (e) {
visible.value = true;

View File

@ -1,12 +1,12 @@
<template>
<a-card>
<j-card>
<SaveChild
v-if="childVisible"
@close-child-save="closeChildSave"
:childData="_current"
/>
<div v-else>
<Search
<pro-search
:columns="columns"
target="child-device"
@search="handleSearch"
@ -76,7 +76,7 @@
}}
</template>
<template #state="slotProps">
<a-badge
<j-badge
:text="slotProps.state.text"
:status="statusMap.get(slotProps.state.value)"
/>
@ -108,7 +108,7 @@
</JProTable>
<BindChildDevice v-if="visible" @change="closeBindDevice" />
</div>
</a-card>
</j-card>
</template>
<script setup lang="ts">

View File

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

View File

@ -144,7 +144,7 @@
</template>
<script lang="ts" setup>
import { queryNoPagingPost } from '@/api/device/product';
import { queryGatewayList, queryNoPagingPost } from '@/api/device/product';
import { queryTree } from '@/api/device/category';
import { message } from 'jetlinks-ui-components';
import { ActionsType } from '@/views/device/Instance/typings';
@ -260,6 +260,26 @@ const columns = [
}),
},
},
{
key: 'productId$product-info',
dataIndex: 'productId$product-info',
title: '接入方式',
hideInTable: true,
search: {
type: 'select',
options: () =>
new Promise((resolve) => {
queryGatewayList({}).then((resp: any) => {
resolve(
resp.result.map((item: any) => ({
label: item.name,
value: `accessId is ${item.id}`,
})),
);
});
}),
},
},
{
dataIndex: 'deviceType',
title: '设备类型',

View File

@ -10,12 +10,7 @@
@cancel="_vis = false"
:confirmLoading="loading"
>
<pro-search
type="simple"
:columns="columns"
target="media"
@search="handleSearch"
/>
<pro-search :columns="columns" target="media" @search="handleSearch" />
<JProTable
ref="listRef"
@ -23,6 +18,7 @@
:columns="columns"
:request="CascadeApi.queryChannelList"
:defaultParams="{
pageSize: 10,
sorts: [{ name: 'name', order: 'desc' }],
terms: [
{
@ -46,6 +42,10 @@
onSelectAll: onSelectAllChange,
}"
@cancelSelect="_selectedRowKeys = []"
:pagination="{
showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'],
}"
>
<template #headerTitle>
<h3>通道列表</h3>
@ -96,6 +96,7 @@ watch(
() => _vis.value,
(val) => {
if (val) handleSearch({ terms: [] });
else _selectedRowKeys.value = [];
},
);
@ -114,6 +115,7 @@ const columns = [
key: 'name',
search: {
type: 'string',
first: true,
},
},
{
@ -158,7 +160,6 @@ const params = ref<Record<string, any>>({});
*/
const handleSearch = (e: any) => {
params.value = e;
console.log('params.value: ', params.value);
};
const listRef = ref();
@ -193,7 +194,10 @@ const getSetRowKey = (selectedRows: any[]) =>
const loading = ref(false);
const handleSave = async () => {
if (!_selectedRowKeys.value.length) message.error('请勾选数据');
if (!_selectedRowKeys.value.length) {
message.error('请勾选数据');
return;
}
loading.value = true;
const resp = await CascadeApi.bindChannel(
route.query.id as string,

View File

@ -1,12 +1,7 @@
<!-- 国标级联-通道列表 -->
<template>
<page-container>
<pro-search
type="simple"
:columns="columns"
target="media"
@search="handleSearch"
/>
<pro-search :columns="columns" target="media" @search="handleSearch" />
<JProTable
ref="listRef"
@ -14,6 +9,7 @@
:columns="columns"
:request="(e:any) => CascadeApi.queryBindChannel(route?.query.id as string, e)"
:defaultParams="{
pageSize: 10,
sorts: [{ name: 'name', order: 'desc' }],
}"
:params="params"
@ -23,6 +19,10 @@
onSelectAll: onSelectAllChange,
}"
@cancelSelect="_selectedRowKeys = []"
:pagination="{
showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'],
}"
>
<template #headerTitle>
<h3>通道列表</h3>
@ -50,9 +50,9 @@
</j-space>
</j-tooltip>
</template>
<template #gbChannelId="slotProps">
<template #channelId="slotProps">
<j-space>
<Ellipsis>
<Ellipsis style="width: 150px">
{{ slotProps.gbChannelId }}
</Ellipsis>
<j-popover
@ -73,6 +73,7 @@
<j-input
v-model:value="gbID"
@change="validField(slotProps)"
placeholder="请输入国标ID"
/>
<div
class="error"
@ -152,7 +153,7 @@
<script setup lang="ts">
import CascadeApi from '@/api/media/cascade';
import type { ActionsType } from '@/components/Table/index.vue';
import type { ActionsType } from '@/views/device/Instance/typings';
import { message } from 'ant-design-vue';
import BindChannel from './BindChannel/index.vue';
@ -175,12 +176,13 @@ const columns = [
key: 'name',
search: {
type: 'string',
first: true,
},
},
{
title: '国标ID',
dataIndex: 'gbChannelId',
key: 'gbChannelId',
dataIndex: 'channelId',
key: 'channelId',
scopedSlots: true,
headerCell: 'gbChannelIdHeader', //
search: {
@ -204,7 +206,7 @@ const columns = [
},
},
{
title: '状态',
title: '在线状态',
dataIndex: 'status',
key: 'status',
scopedSlots: true,

View File

@ -17,8 +17,8 @@
message: '请输入名称',
},
{
max: 84,
message: '最多可输入84个字符',
max: 64,
message: '最多可输入64个字符',
},
]"
>
@ -238,6 +238,7 @@
v-model:value="formData.host"
placeholder="请选择IP地址"
:options="allList"
@change="setPorts"
/>
</j-col>
<j-col :span="10">
@ -301,7 +302,7 @@
<j-radio-group
button-style="solid"
v-model:value="formData.transport"
@change="setPorts"
@change="handleTransportChange"
>
<j-radio-button value="UDP">
UDP
@ -566,31 +567,31 @@ const route = useRoute();
//
const formData = ref({
id: route.query.id || undefined,
// name: '',
cascadeName: '',
// name: undefined,
cascadeName: undefined,
proxyStream: false,
// , sipConfigs[{}]
clusterNodeId: '',
name: '',
sipId: '',
domain: '',
remoteAddress: '',
clusterNodeId: undefined,
name: undefined,
sipId: undefined,
domain: undefined,
remoteAddress: undefined,
remotePort: undefined,
localSipId: '',
host: '',
localSipId: undefined,
host: undefined,
port: undefined,
// remotePublic: {
// host: '',
// host: undefined,
// port: undefined,
// },
publicHost: '',
publicHost: undefined,
publicPort: undefined,
transport: 'UDP',
user: '',
password: '',
manufacturer: '',
model: '',
firmware: '',
user: undefined,
password: undefined,
manufacturer: undefined,
model: undefined,
firmware: undefined,
keepaliveInterval: '60',
registerInterval: '3600',
});
@ -616,19 +617,29 @@ const getAllList = async () => {
allList.value = result.map((m: any) => ({
label: m.host,
value: m.host,
...m,
}));
setPorts();
};
getAllList();
const handleTransportChange = () => {
formData.value.host = undefined;
formData.value.port = undefined;
setPorts();
};
/**
* 传输协议改变, 获取对应的端口
* 获取端口
*/
const allListPorts = ref([]);
const setPorts = () => {
allListPorts.value = allList.value.find(
(f: any) => f.host === formData.value.host,
)?.ports[formData.value.transport || ''];
if (!formData.value.host) return;
allListPorts.value = allList.value
.find((f: any) => f.host === formData.value.host)
?.ports[formData.value.transport || '']?.map((m: string) => ({
label: m,
value: m,
}));
};
/**

View File

@ -141,7 +141,7 @@
<script setup lang="ts">
import CascadeApi from '@/api/media/cascade';
import type { ActionsType } from '@/components/Table/index.vue';
import type { ActionsType } from '@/views/device/Instance/typings';
import { message } from 'ant-design-vue';
import { getImage } from '@/utils/comm';
import Publish from './Publish/index.vue';
@ -180,12 +180,14 @@ const columns = [
title: '通道数量',
dataIndex: 'count',
key: 'count',
width: 100,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
scopedSlots: true,
width: 100,
search: {
type: 'select',
options: [
@ -202,6 +204,7 @@ const columns = [
dataIndex: 'onlineStatus',
key: 'onlineStatus',
scopedSlots: true,
width: 100,
search: {
type: 'select',
options: [
@ -237,20 +240,12 @@ const handleSearch = (e: any) => {
const lastValueFrom = async (params: any) => {
const res = await CascadeApi.list(params);
res.result.data.forEach(async (item: any) => {
const resp = await queryChannelCount(item.id);
const resp = await CascadeApi.queryBindChannel(item.id, {});
item.count = resp.result.total;
});
return res;
};
/**
* 查询通道数量
* @param id
*/
const queryChannelCount = async (id: string) => {
return await CascadeApi.queryBindChannel(id, {});
};
/**
* 新增
*/

View File

@ -3,13 +3,15 @@
<div class="top-card-content">
<div class="content-left">
<div class="content-left-title">
<span>{{ title }}</span>
<j-tooltip placement="top" v-if="tooltip">
<template #title>
<span>{{ tooltip }}</span>
</template>
<AIcon type="QuestionCircleOutlined" />
</j-tooltip>
<j-space>
<span>{{ title }}</span>
<j-tooltip placement="top" v-if="tooltip">
<template #title>
<span>{{ tooltip }}</span>
</template>
<AIcon type="QuestionCircleOutlined" />
</j-tooltip>
</j-space>
</div>
<div class="content-left-value">{{ value }}</div>
</div>

View File

@ -28,13 +28,18 @@
<JProTable
ref="listRef"
model="table"
:columns="columns"
:request="(e:any) => ChannelApi.list(e, route?.query.id as string)"
:defaultParams="{
pageSize: 10,
sorts: [{ name: 'notifyTime', order: 'desc' }],
}"
:params="params"
model="table"
:pagination="{
showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'],
}"
>
<template #headerTitle>
<j-tooltip
@ -109,7 +114,7 @@
<script setup lang="ts">
import ChannelApi from '@/api/media/channel';
import type { ActionsType } from '@/components/Table/index.vue';
import type { ActionsType } from '@/views/device/Instance/typings';
import { useMenuStore } from 'store/menu';
import { message } from 'ant-design-vue';
import Save from './Save.vue';
@ -136,6 +141,7 @@ const columns = [
key: 'name',
search: {
type: 'string',
first: true,
},
},
{

View File

@ -13,6 +13,7 @@
sorts: [{ name: 'createTime', order: 'desc' }],
}"
:params="params"
:gridColumn="3"
>
<template #headerTitle>
<PermissionButton
@ -119,7 +120,7 @@
<script setup lang="ts">
import DeviceApi from '@/api/media/device';
import type { ActionsType } from '@/components/Table/index.vue';
import type { ActionsType } from '@/views/device/Instance/typings';
import { message } from 'ant-design-vue';
import { getImage } from '@/utils/comm';
import { PROVIDER_OPTIONS } from '@/views/media/Device/const';
@ -147,6 +148,7 @@ const columns = [
key: 'name',
search: {
type: 'string',
first: true,
},
},
{

View File

@ -44,6 +44,7 @@
:columns="columns"
:request="deviceApi.list"
:defaultParams="{
pageSize: 10,
sorts: [{ name: 'createTime', order: 'desc' }],
}"
:params="params"
@ -56,7 +57,14 @@
deviceItem = record;
}
}"
:pagination="{
showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100'],
}"
>
<template #channelNumber="slotProps">
<span>{{ slotProps.channelNumber || 0 }}</span>
</template>
<template #state="slotProps">
<a-space>
<a-badge
@ -160,12 +168,14 @@ const columns = [
key: 'name',
search: {
type: 'string',
first: true,
},
},
{
title: '通道数量',
dataIndex: 'channelNumber',
key: 'channelNumber',
scopedSlots: true,
},
{
title: '状态',

View File

@ -1,3 +1,4 @@
<!-- 调试 -->
<template>
<j-modal
v-model:visible="_vis"

View File

@ -1,21 +1,22 @@
<!-- 通知记录 -->
<template>
<j-modal v-model:visible="_vis" title="通知记录" :footer="null" width="70%">
<pro-search
type="simple"
:columns="columns"
@search="handleSearch"
/>
<pro-search type="simple" :columns="columns" @search="handleSearch" />
<JProTable
ref="instanceRef"
ref="logRef"
model="table"
:columns="columns"
:request="(e:any) => configApi.getHistory(e, data.id)"
:defaultParams="{
pageSize: 5,
sorts: [{ name: 'notifyTime', order: 'desc' }],
terms: [{ column: 'notifyType$IN', value: data.type }],
}"
:params="params"
model="table"
:pagination="{
pageSizeOptions: ['5', '10', '20', '50', '100'],
}"
>
<template #notifyTime="slotProps">
{{ moment(slotProps.notifyTime).format('YYYY-MM-DD HH:mm:ss') }}
@ -127,9 +128,7 @@ const params = ref<Record<string, any>>({});
* @param params
*/
const handleSearch = (e: any) => {
// console.log('handleSearch e:', e);
params.value = e;
// console.log('params.value: ', params.value);
};
/**
@ -169,5 +168,3 @@ const handleDetail = (e: any) => {
});
};
</script>
<style lang="less" scoped></style>

View File

@ -1,3 +1,4 @@
<!-- 同步用户 -->
<template>
<div>
<j-modal
@ -46,8 +47,9 @@
total: dataSource.length,
current: current,
pageSize: pageSize,
pageSizeOptions: ['12', '24', '48', '96'],
pageSizeOptions: ['5', '10', '20', '50', '100'],
showSizeChanger: true,
hideOnSinglePage: true,
showTotal: (total: number, range: number) => `${range[0]} - ${range[1]} 条/总共 ${total}`,
}"
@change="handleTableChange"
@ -133,9 +135,8 @@
<script setup lang="ts" name="SyncUser">
import configApi from '@/api/notice/config';
import { PropType } from 'vue';
import moment from 'moment';
import { Modal, message } from 'ant-design-vue';
import type { ActionsType } from '@/components/Table/index.vue';
import { message } from 'ant-design-vue';
import type { ActionsType } from '@/views/device/Instance/typings';
import { Form } from 'ant-design-vue';
const useForm = Form.useForm;
@ -384,7 +385,7 @@ const getTableData = () => {
* 前端分页
*/
const current = ref(1);
const pageSize = ref(12);
const pageSize = ref(5);
const handleTableChange = (pagination: any) => {
current.value = pagination.current;
pageSize.value = pagination.pageSize;

View File

@ -1,3 +1,4 @@
<!-- 通知配置 -->
<template>
<page-container>
<pro-search
@ -24,7 +25,7 @@
>
新增
</PermissionButton>
<a-upload
<j-upload
name="file"
accept=".json"
:showUploadList="false"
@ -33,7 +34,7 @@
<PermissionButton hasPermission="notice/Config:import">
导入
</PermissionButton>
</a-upload>
</j-upload>
<j-popconfirm
title="确认导出?"
ok-text="确定"

View File

@ -1,3 +1,4 @@
<!-- 调试 -->
<template>
<j-modal
v-model:visible="_vis"

View File

@ -8,7 +8,7 @@
>
<j-input v-model:value="item.name" @change="emitEvents">
<template #addonAfter>
<a-upload
<j-upload
name="file"
:action="FILE_UPLOAD"
:headers="{
@ -18,7 +18,7 @@
@change="(e) => handleChange(e, item.id)"
>
<upload-outlined />
</a-upload>
</j-upload>
</template>
</j-input>
<delete-outlined

View File

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

View File

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

View File

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

View File

@ -820,49 +820,49 @@ const resetPublicFiles = () => {
formData.value.template = {};
switch (formData.value.provider) {
case 'dingTalkMessage':
formData.value.template.agentId = '';
formData.value.template.message = '';
formData.value.template.departmentIdList = '';
formData.value.template.userIdList = '';
formData.value.template.agentId = undefined;
formData.value.template.message = undefined;
formData.value.template.departmentIdList = undefined;
formData.value.template.userIdList = undefined;
break;
case 'dingTalkRobotWebHook':
formData.value.template.message = '';
formData.value.template.message = undefined;
formData.value.template.messageType = 'markdown';
formData.value.template.markdown = { text: '', title: '' };
formData.value.template.markdown = { text: undefined, title: undefined };
break;
case 'corpMessage':
formData.value.template.agentId = '';
formData.value.template.message = '';
formData.value.template.toParty = '';
formData.value.template.toUser = '';
formData.value.template.toTag = '';
formData.value.template.agentId = undefined;
formData.value.template.message = undefined;
formData.value.template.toParty = undefined;
formData.value.template.toUser = undefined;
formData.value.template.toTag = undefined;
break;
case 'embedded':
formData.value.template.subject = '';
formData.value.template.message = '';
formData.value.template.text = '';
formData.value.template.subject = undefined;
formData.value.template.message = undefined;
formData.value.template.text = undefined;
formData.value.template.sendTo = [];
formData.value.template.attachments = [];
break;
case 'aliyun':
formData.value.template.templateType = 'tts';
formData.value.template.templateCode = '';
formData.value.template.ttsCode = '';
// formData.value.template.message = '';
formData.value.template.ttsmessage = '';
formData.value.template.templateCode = undefined;
formData.value.template.ttsCode = undefined;
// formData.value.template.message = undefined;
formData.value.template.ttsmessage = undefined;
formData.value.template.playTimes = 1;
formData.value.template.calledShowNumbers = '';
formData.value.template.calledNumber = '';
formData.value.template.calledShowNumbers = undefined;
formData.value.template.calledNumber = undefined;
break;
case 'aliyunSms':
formData.value.template.code = '';
formData.value.template.message = '';
formData.value.template.phoneNumber = '';
formData.value.template.signName = '';
formData.value.template.code = undefined;
formData.value.template.message = undefined;
formData.value.template.phoneNumber = undefined;
formData.value.template.signName = undefined;
break;
case 'http':
formData.value.template.contextAsBody = true;
formData.value.template.body = '';
formData.value.template.body = undefined;
break;
}
@ -1012,7 +1012,10 @@ const { resetFields, validate, validateInfos, clearValidate } = useForm(
watch(
() => formData.value.template.markdown?.title,
(val) => {
if (!val) return;
if (!val) {
formData.value.variableDefinitions = [];
return;
}
variableReg();
},
{ deep: true },
@ -1021,7 +1024,10 @@ watch(
watch(
() => formData.value.template.link?.title,
(val) => {
if (!val) return;
if (!val) {
formData.value.variableDefinitions = [];
return;
}
variableReg();
},
{ deep: true },
@ -1030,7 +1036,10 @@ watch(
watch(
() => formData.value.template.subject,
(val) => {
if (!val) return;
if (!val) {
formData.value.variableDefinitions = [];
return;
}
variableReg();
},
{ deep: true },
@ -1040,7 +1049,10 @@ watch(
watch(
() => formData.value.template.message,
(val) => {
if (!val) return;
if (!val) {
formData.value.variableDefinitions = [];
return;
}
variableReg();
},
{ deep: true },
@ -1049,7 +1061,10 @@ watch(
watch(
() => formData.value.template.ttsmessage,
(val) => {
if (!val) return;
if (!val) {
formData.value.variableDefinitions = [];
return;
}
variableReg();
},
{ deep: true },
@ -1058,7 +1073,10 @@ watch(
watch(
() => formData.value.template.body,
(val) => {
if (!val) return;
if (!val) {
formData.value.variableDefinitions = [];
return;
}
variableReg();
},
{ deep: true },
@ -1162,7 +1180,10 @@ const getConfigList = async () => {
{ column: 'type$IN', value: formData.value.type },
{ column: 'provider', value: formData.value.provider },
];
const { result } = await templateApi.getConfig({ terms });
const { result } = await templateApi.getConfig({
terms,
sorts: [{ name: 'createTime', order: 'desc' }],
});
configList.value = result;
};

View File

@ -1,21 +1,22 @@
<!-- 通知记录 -->
<template>
<j-modal v-model:visible="_vis" title="通知记录" :footer="null" width="70%">
<pro-search
type="simple"
:columns="columns"
@search="handleSearch"
/>
<pro-search type="simple" :columns="columns" @search="handleSearch" />
<JProTable
ref="instanceRef"
ref="logRef"
model="table"
:columns="columns"
:request="(e:any) => templateApi.getHistory(e, data.id)"
:defaultParams="{
pageSize: 5,
sorts: [{ name: 'notifyTime', order: 'desc' }],
terms: [{ column: 'notifyType$IN', value: data.type }],
}"
:params="params"
model="table"
:pagination="{
pageSizeOptions: ['5', '10', '20', '50', '100'],
}"
>
<template #notifyTime="slotProps">
{{ moment(slotProps.notifyTime).format('YYYY-MM-DD HH:mm:ss') }}
@ -127,9 +128,7 @@ const params = ref<Record<string, any>>({});
* @param params
*/
const handleSearch = (e: any) => {
// console.log('handleSearch e:', e);
params.value = e;
// console.log('params.value: ', params.value);
};
/**
@ -169,5 +168,3 @@ const handleDetail = (e: any) => {
});
};
</script>
<style lang="less" scoped></style>

View File

@ -1,3 +1,4 @@
<!-- 通知模板 -->
<template>
<page-container>
<pro-search
@ -24,7 +25,7 @@
>
新增
</PermissionButton>
<a-upload
<j-upload
name="file"
accept=".json"
:showUploadList="false"
@ -35,7 +36,7 @@
>
导入
</PermissionButton>
</a-upload>
</j-upload>
<j-popconfirm
title="确认导出?"
ok-text="确定"
@ -154,7 +155,7 @@
<script setup lang="ts">
import TemplateApi from '@/api/notice/template';
import type { ActionsType } from '@/components/Table/index.vue';
import type { ActionsType } from '@/views/device/Instance/typings';
import { message } from 'ant-design-vue';
import { NOTICE_METHOD, MSG_TYPE } from '@/views/notice/const';
import Debug from './Debug/index.vue';

View File

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

View File

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

View File

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

View File

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

View File

@ -13,20 +13,28 @@
import { useSceneStore } from '@/store/scene';
import Action from '../action/index.vue';
import { storeToRefs } from 'pinia';
import { ActionsType } from '@/views/rule-engine/Scene/typings';
const sceneStore = useSceneStore();
const { data } = storeToRefs(sceneStore);
const onActionAdd = (_data: any) => {
if (data.value?.branches && _data) {
data?.value.branches?.[0].then.push(_data)
console.log(data?.value.branches?.[0].then)
const newThen = [...data.value.branches[0].then, _data];
data.value.branches![0].then = newThen
}
};
const onActionUpdate = (_data: ActionsType, type: boolean) => {
console.log(_data, type)
const onActionUpdate = (_data: any, type: boolean) => {
const indexOf = data.value.branches![0].then.findIndex(
(item) => item.parallel === type,
);
if (indexOf !== -1) {
if (_data?.actions?.length) {
data.value.branches![0].then[indexOf] = _data;
} else {
data.value.branches![0].then[indexOf].actions = [];
}
}
};
</script>

View File

@ -7,7 +7,7 @@
@ok="onOk"
:maskClosable="false"
>
<a-input-number
<j-input-number
style="max-width: 220px"
placeholder="请输入时间"
v-model:value="_value"
@ -16,7 +16,7 @@
:max="6535"
>
<template #addonAfter>
<a-select
<j-select
:options="[
{ label: '秒', value: 'seconds' },
{ label: '分', value: 'minutes' },
@ -25,7 +25,7 @@
v-model:value="unit"
/>
</template>
</a-input-number>
</j-input-number>
</j-modal>
</template>

View File

@ -1,12 +1,12 @@
<template>
<j-advanced-search
<pro-search
:columns="columns"
type="simple"
@search="handleSearch"
class="search"
target="scene-trigger-device-product"
/>
<a-divider style="margin: 0" />
<j-divider style="margin: 0" />
<j-pro-table
ref="actionRef"
model="CARD"
@ -48,16 +48,16 @@
</span>
</Ellipsis>
</div>
<a-row>
<a-col :span="12">
<j-row>
<j-col :span="12">
<div class="card-item-content-text">设备类型</div>
<Ellipsis>{{ slotProps.deviceType?.text }}</Ellipsis>
</a-col>
<a-col :span="12">
</j-col>
<j-col :span="12">
<div class="card-item-content-text">接入方式</div>
<Ellipsis>{{ slotProps?.accessName || '未接入' }}</Ellipsis>
</a-col>
</a-row>
</j-col>
</j-row>
</template>
</CardBox>
</template>

View File

@ -15,13 +15,16 @@
</template>
<template v-else>
<ParamsDropdown
icon="icon-canshu"
placeholder="请选择"
:options="[]"
:tabsOptions="tabOptions"
:metricOption="upperOptions(record.valueType)"
v-model:value="record.value"
/>
>
<template v-slot="{label}">
<j-input :value="label" />
</template>
</ParamsDropdown>
</template>
</div>
</template>

View File

@ -1,6 +1,6 @@
<template>
<div>
<a-form :layout="'vertical'" ref="formRef" :model="modelRef">
<j-form :layout="'vertical'" ref="formRef" :model="modelRef">
<j-row>
<j-col :span="11">
<j-form-item
@ -40,7 +40,7 @@
</j-form-item>
</j-col>
</j-row>
</a-form>
</j-form>
</div>
</template>

View File

@ -1,6 +1,6 @@
<template>
<div>
<a-form :layout="'vertical'" ref="formRef" :model="modelRef">
<j-form :layout="'vertical'" ref="formRef" :model="modelRef">
<j-form-item
:name="['message', 'messageType']"
label="动作类型"
@ -67,7 +67,7 @@
:builtInList="builtInList"
/>
</template>
</a-form>
</j-form>
</div>
</template>
@ -211,6 +211,7 @@ watch(
watch(
() => props.values?.message,
(newVal) => {
console.log(newVal)
if (newVal?.messageType) {
modelRef.message = newVal;
if (newVal.messageType === 'INVOKE_FUNCTION' && newVal.functionId) {

View File

@ -1,12 +1,12 @@
<template>
<j-advanced-search
<pro-search
:columns="columns"
type="simple"
@search="handleSearch"
class="search"
target="scene-trigger-device-device"
/>
<a-divider style="margin: 0" />
<j-divider style="margin: 0" />
<j-pro-table
ref="actionRef"
model="CARD"
@ -153,7 +153,7 @@ const deviceQuery = (p: any) => {
if (props.value) {
sorts.push({
name: 'id',
value: props.value,
value: props.value[0]?.value,
});
}
sorts.push({ name: 'createTime', order: 'desc' });

View File

@ -1,6 +1,6 @@
<template>
<div>
<a-form :layout="'vertical'" ref="formRef" :model="modelRef">
<j-form :layout="'vertical'" ref="formRef" :model="modelRef">
<j-form-item
name="selector"
label="选择方式"
@ -64,16 +64,16 @@
:fieldNames="{ label: 'name', value: 'id' }"
>
<template #title="{ name, description }">
<a-space>
<j-space>
{{ name }}
<span style="color: grey; margin-left: 5px">{{
description
}}</span>
</a-space>
</j-space>
</template>
</j-tree-select>
</j-form-item>
</a-form>
</j-form>
</div>
</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

@ -19,17 +19,17 @@
v-if="data?.executor === 'alarm'"
>
<template v-if="data?.alarm?.mode === 'trigger'">
满足条件后将触发<a-button
满足条件后将触发<j-button style="padding: 0;"
type="link"
@click.stop="triggerVisible = true"
>关联此场景的告警</a-button
>关联此场景的告警</j-button
>
</template>
<template v-else>
满足条件后将解除<a-button
满足条件后将解除<j-button style="padding: 0;"
type="link"
@click.stop="triggerVisible = true"
>关联此场景的告警</a-button
>关联此场景的告警</j-button
>
</template>
</div>
@ -308,26 +308,36 @@
</div>
</template>
</div>
<a-button v-else @click="onAdd">点击配置执行动作</a-button>
<j-button v-else @click="onAdd">点击配置执行动作</j-button>
</div>
<div class="item-number">{{ name + 1 }}</div>
<a-popconfirm title="确认删除?" @confirm="onDelete">
<j-popconfirm title="确认删除?" @confirm="onDelete">
<div class="item-delete">
<AIcon type="DeleteOutlined" />
</div>
</a-popconfirm>
</j-popconfirm>
</div>
<template v-if="!isLast && type === 'serial'">
<div class="actions-item-filter-warp">
<!-- filter-border -->
满足此条件后执行后续动作
<div :class='["actions-item-filter-warp", termsOptions.length ? "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>
</template>
<!-- 编辑 -->
<template v-if="visible">
<Modal
:name="name"
:branchGroup="branchGroup"
:branchGroup="thenName"
:branchesName="branchesName"
:data="data"
@cancel="onClose"
@ -352,7 +362,6 @@
</template>
<script lang="ts" setup>
import { getImage } from '@/utils/comm';
import { isBoolean } from 'lodash-es';
import { PropType } from 'vue';
import { ActionsType, ParallelType } from '../../../typings';
@ -361,6 +370,7 @@ import ActionTypeComponent from '../Modal/ActionTypeComponent.vue';
import TriggerAlarm from '../TriggerAlarm/index.vue';
import { useSceneStore } from '@/store/scene';
import { storeToRefs } from 'pinia';
import { iconMap, itemNotifyIconMap, typeIconMap } from './util'
const sceneStore = useSceneStore();
const { data: _data } = storeToRefs(sceneStore);
@ -370,9 +380,9 @@ const props = defineProps({
type: Number,
default: 0,
},
branchGroup: {
type: Number,
default: 0,
thenName: {
type: Number,
default: 0,
},
name: {
type: Number,
@ -397,37 +407,17 @@ const props = defineProps({
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 termsOptions = computed(() => {
if (!props.parallel) { //
return _data.value.branches![props.branchesName].then?.[props.thenName].actions?.[props.name].terms || []
}
return []
})
const onDelete = () => {
emit('delete');
};
@ -463,6 +453,7 @@ const onPropsOk = (data: ActionsType, options?: any) => {
const onPropsCancel = () => {
actionType.value = '';
};
</script>
<style lang="less" scoped>
@ -582,6 +573,19 @@ const onPropsCancel = () => {
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 {
display: flex;
padding-top: 4px;
@ -590,6 +594,13 @@ const onPropsCancel = () => {
row-gap: 16px;
}
.filter-add-button{
width: 100%;
color: rgba(0, 0, 0, 0.3);
text-align: center;
cursor: pointer;
}
.terms-params {
// display: inline-block;
display: flex;

View File

@ -5,7 +5,7 @@
:parallel="parallel"
:data="item"
:branchesName="branchesName"
:branchGroup="parallel ? 1 : 0"
:thenName="thenName"
:name="index"
:type="type"
:isLast="index === actions.length - 1"
@ -25,7 +25,7 @@
@cancel="onCancel"
:parallel="parallel"
:name="actions.length"
:branchGroup="parallel ? 1 : 0"
:branchGroup="thenName"
@save="onSave"
:branchesName="branchesName"
/>
@ -38,6 +38,11 @@ import { ActionsType, ParallelType } from '../../../typings';
import Modal from '../Modal/index.vue';
import Item from './Item.vue';
import { pick } from 'lodash';
import { useSceneStore } from '@/store/scene';
import { storeToRefs } from 'pinia';
const sceneStore = useSceneStore();
const { data: _data } = storeToRefs(sceneStore);
interface ListProps {
branchesName: number;
@ -47,7 +52,10 @@ interface ListProps {
}
const props = defineProps({
branchesName: Number,
branchesName: {
type: Number,
default: 0
},
type: {
type: String as PropType<ListProps['type']>,
default: 'serial',
@ -63,6 +71,10 @@ const emit = defineEmits(['delete', 'add']);
const visible = ref<boolean>(false);
const thenName = computed(() => {
return _data.value.branches![props.branchesName].then.findIndex(item => item.parallel === props.parallel)
})
const onAdd = () => {
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 @@
<Device v-bind="props" :value="data?.device" @cancel="onCancel" @save="onPropsOk" :thenName="branchesName" />
</template>
<template v-else-if="actionType === 'notify'">
<Notify v-bind="props" :value="data?.notify" @cancel="onCancel" @save="onPropsOk" />
<Notify :options="data?.options" :value="data?.notify" @cancel="onCancel" @save="onPropsOk" />
</template>
<template v-else-if="actionType === 'delay'">
<Delay :value="data?.delay" @cancel="onCancel" @save="onPropsOk" />
@ -55,7 +55,7 @@ const onPropsOk = (data: any, options: any) => {
type: props.actionType,
executor: props.actionType,
key: props?.data?.key || `${props.actionType}_${new Date().getTime()}`,
device: {
[props.actionType]: {
...data,
},
};

View File

@ -7,8 +7,8 @@
@ok="onOk"
:maskClosable="false"
>
<a-form ref="actionForm" :model="formModel" layout="vertical">
<a-form-item
<j-form ref="actionForm" :model="formModel" layout="vertical">
<j-form-item
label="类型"
name="type"
:rules="[
@ -19,7 +19,7 @@
]"
>
<CardSelect v-model:value="formModel.type" :options="options"/>
</a-form-item>
</j-form-item>
<ActionTypeComponent
v-bind="props"
v-if="!!actionType"
@ -27,7 +27,7 @@
@save="onPropsOk"
@cancel="onPropsCancel"
/>
</a-form>
</j-form>
</j-modal>
</template>

View File

@ -1,5 +1,5 @@
<template>
<j-advanced-search
<pro-search
:columns="columns"
type="simple"
target="action-notice-config"
@ -9,22 +9,7 @@
<div style="height: 400px; overflow-y: auto">
<JProTable
:columns="columns"
:request="(e) => ConfigApi.list({
...e,
terms: [
...e?.terms,
{
terms: [
{
termType: 'eq',
column: 'type',
value: props.notifyType,
},
],
},
],
sorts: [{ name: 'id', value: props.value }, { name: 'createTime', order: 'desc' }],
})"
:request="query"
model="CARD"
:bodyStyle="{
paddingRight: 0,
@ -34,8 +19,8 @@
:gridColumn="2"
:rowSelection="{
selectedRowKeys: _selectedRowKeys,
onChange: onSelectChange,
}"
@cancelSelect="cancelSelect"
>
<template #card="slotProps">
<CardBox
@ -63,22 +48,22 @@
{{ slotProps.name }}
</span>
</Ellipsis>
<a-row>
<a-col :span="12">
<j-row>
<j-col :span="12">
<div class="card-item-content-text">
通知方式
</div>
<div>
{{ getMethodTxt(slotProps.type) }}
</div>
</a-col>
<a-col :span="12">
</j-col>
<j-col :span="12">
<div class="card-item-content-text">说明</div>
<Ellipsis>
{{ slotProps.description }}
</Ellipsis>
</a-col>
</a-row>
</j-col>
</j-row>
</template>
</CardBox>
</template>
@ -100,7 +85,7 @@ const props = defineProps({
},
});
const emit = defineEmits(['update:value']);
const emit = defineEmits(['update:value', 'change']);
const getLogo = (type: string, provider: string) => {
return MSG_TYPE[type].find((f: any) => f.value === provider)?.logo;
@ -140,17 +125,39 @@ const columns = [
},
];
const query = (e: Record<string, any>) =>
ConfigApi.list({
...e,
terms: [
...e?.terms,
{
terms: [
{
termType: 'eq',
column: 'type',
value: props.notifyType,
},
],
},
],
sorts: [
{ name: 'id', value: props.value },
{ name: 'createTime', order: 'desc' },
],
});
const handleSearch = (_params: any) => {
params.value = _params;
};
const cancelSelect = () => {
_selectedRowKeys.value = [];
const onSelectChange = (keys: string[]) => {
_selectedRowKeys.value = [...keys];
};
const handleClick = (dt: any) => {
_selectedRowKeys.value = [dt.id];
emit('update:value', dt.id);
emit('change', { provider: dt?.provider });
};
watch(
@ -176,7 +183,7 @@ watch(
padding-left: 0px;
}
.logo{
.logo {
width: 88px;
height: 88px;
}

View File

@ -1,5 +1,5 @@
<template>
<j-advanced-search
<pro-search
:columns="columns"
type="simple"
target="action-notice-template"
@ -19,8 +19,8 @@
:gridColumn="2"
:rowSelection="{
selectedRowKeys: _selectedRowKeys,
onChange: onSelectChange,
}"
@cancelSelect="cancelSelect"
>
<template #card="slotProps">
<CardBox
@ -48,22 +48,22 @@
{{ slotProps.name }}
</span>
</Ellipsis>
<a-row>
<a-col :span="12">
<j-row>
<j-col :span="12">
<div class="card-item-content-text">
通知方式
</div>
<div>
{{ getMethodTxt(slotProps.type) }}
</div>
</a-col>
<a-col :span="12">
</j-col>
<j-col :span="12">
<div class="card-item-content-text">说明</div>
<Ellipsis>
{{ slotProps.description }}
</Ellipsis>
</a-col>
</a-row>
</j-col>
</j-row>
</template>
</CardBox>
</template>
@ -85,7 +85,7 @@ const props = defineProps({
},
});
const emit = defineEmits(['update:value']);
const emit = defineEmits(['update:value', 'change']);
const getLogo = (type: string, provider: string) => {
return MSG_TYPE[type].find((f: any) => f.value === provider)?.logo;
@ -129,13 +129,14 @@ const handleSearch = (_params: any) => {
params.value = _params;
};
const cancelSelect = () => {
_selectedRowKeys.value = [];
};
const handleClick = (dt: any) => {
_selectedRowKeys.value = [dt.id];
emit('update:value', dt.id);
emit('change', { templateName: dt?.name });
};
const onSelectChange = (keys: string[]) => {
_selectedRowKeys.value = [...keys];
};
const handleData = async (e: any) => {
@ -178,8 +179,7 @@ watch(
<style lang="less" scoped>
.search {
margin-bottom: 0;
padding-right: 0px;
padding-left: 0px;
padding: 0;
}
.logo {

View File

@ -1,5 +1,5 @@
<template>
<a-spin :spinning="loading">
<j-spin :spinning="loading">
<div class="notify-type-warp" :class="{ disabled: disabled }">
<div
:key="item.id"
@ -14,7 +14,7 @@
<div class="notify-type-item-title">{{item.label}}</div>
</div>
</div>
</a-spin>
</j-spin>
</template>
<script lang="ts" setup>
@ -40,7 +40,7 @@ const props = defineProps({
},
});
const emit = defineEmits(['update:value']);
const emit = defineEmits(['update:value', 'change']);
const loading = ref<boolean>(false);
const notifyType = ref('');
@ -57,6 +57,7 @@ watch(
const onSelect = (val: string) => {
if (!props.disabled) {
emit('update:value', val);
emit('change', val);
}
};

View File

@ -1,11 +1,11 @@
<template>
<a-form
<j-form
v-if="variableDefinitions.length"
:layout="'vertical'"
ref="formRef"
:model="modelRef"
>
<a-form-item
<j-form-item
:name="`${item?.id}`"
:label="item?.name"
v-for="item in variableDefinitions"
@ -14,7 +14,7 @@
:rules="[
{
validator: (_rule, value) => checkValue(_rule, value, item),
trigger: 'change',
trigger: ['change', 'blur']
},
]"
>
@ -27,23 +27,25 @@
:notify="notify"
v-else-if="getType(item) === 'org'"
v-model:value="modelRef[item.id]"
@change="(val) => onChange(val, 'org')"
/>
<Tag
:notify="notify"
v-else-if="getType(item) === 'tag'"
v-model:value="modelRef[item.id]"
@change="(val) => onChange(val, 'tag')"
/>
<InputFile
v-else-if="getType(item) === 'file'"
v-model:value="modelRef[item.id]"
/>
<a-input
<j-input
v-else-if="getType(item) === 'link'"
v-model:value="modelRef[item.id]"
/>
<BuildIn v-else :item="item" v-model:value="modelRef[item.id]" />
</a-form-item>
</a-form>
</j-form-item>
</j-form>
</template>
<script lang="ts" setup>
@ -69,6 +71,8 @@ const props = defineProps({
},
});
const emit = defineEmits(['update:value', 'change']);
const formRef = ref();
const modelRef = reactive({});
@ -111,14 +115,14 @@ const checkValue = (_rule: any, value: any, item: any) => {
}
}
}
} else if (value.source === 'fixed' && !value.value) {
} else if (value?.source === 'fixed' && !value.value) {
return Promise.reject(new Error('请输入' + item.name));
} else if (value.source === 'relation' && !value.value && !value.relation) {
} else if (value?.source === 'relation' && !value.value && !value.relation) {
return Promise.reject(new Error('请选择' + item.name));
} else if (value.source === 'upper' && !value.upperKey) {
} else if (value?.source === 'upper' && !value.upperKey) {
return Promise.reject(new Error('请选择' + item.name));
} else if (type === 'user') {
if (props.notify.notifyType === 'email') {
if (props.notify.notifyType === 'email' && value?.source !== 'relation') {
if (Array.isArray(value.value)) {
if (!value.value.length) {
return Promise.reject(new Error('请输入收件人'));
@ -139,7 +143,7 @@ const checkValue = (_rule: any, value: any, item: any) => {
}
if (
props.notify.notifyType &&
['sms', 'voice'].includes(props?.notify?.notifyType)
['sms', 'voice'].includes(props?.notify?.notifyType) && value?.source !== 'relation'
) {
const reg = /^[1][3-9]\d{9}$/;
if (!reg.test(value.value)) {
@ -151,4 +155,21 @@ const checkValue = (_rule: any, value: any, item: any) => {
}
return Promise.resolve();
};
const onChange = (val: any, type: any) => {
if (type === 'org') {
emit('change', { orgName: val.join(',') });
} else if (type === 'tag') {
emit('change', { tagName: val });
}
};
const onSave = () =>
new Promise((resolve) => {
formRef.value.validate().then(async (_data: any) => {
resolve(_data);
});
});
defineExpose({ onSave });
</script>

View File

@ -8,17 +8,17 @@
:maskClosable="false"
>
<div class="steps-steps">
<a-steps :current="current" size="small" @change="onChange">
<a-step title="通知方式" key="way" />
<a-step title="通知配置" key="config" />
<a-step title="通知模板" key="template" />
<a-step title="模板变量" key="variable" />
</a-steps>
<j-steps :current="current" size="small" @change="onChange">
<j-step title="通知方式" key="way" />
<j-step title="通知配置" key="config" />
<j-step title="通知模板" key="template" />
<j-step title="模板变量" key="variable" />
</j-steps>
</div>
<div class="steps-content">
<a-form ref="actionForm" :model="formModel" layout="vertical">
<j-form ref="actionForm" :model="formModel" layout="vertical">
<template v-if="current === 0">
<a-form-item
<j-form-item
label="应用"
name="notifyType"
:rules="[
@ -28,38 +28,45 @@
},
]"
>
<NotifyWay v-model:value="formModel.notifyType" />
</a-form-item>
<NotifyWay
v-model:value="formModel.notifyType"
@change="(val) => onValChange(val, 'notifyType')"
/>
</j-form-item>
</template>
<template v-if="current === 1">
<a-form-item name="notifierId">
<j-form-item name="notifierId">
<NotifyConfig
v-model:value="formModel.notifierId"
:notifyType="formModel.notifyType"
@change="(val) => onValChange(val, 'notifierId')"
/>
</a-form-item>
</j-form-item>
</template>
<template v-if="current === 2">
<a-form-item name="templateId">
<j-form-item name="templateId">
<NotifyTemplate
v-model:value="formModel.templateId"
:notifierId="formModel.notifierId"
@change="(val) => onValChange(val, 'templateId')"
/>
</a-form-item>
</j-form-item>
</template>
<template v-if="current === 3">
<a-form-item name="variables">
<j-form-item name="variables">
<VariableDefinitions
:variableDefinitions="variable"
v-model:value="formModel.variables"
:value="formModel.variables"
:notify="formModel"
@change="(val) => onValChange(val, 'variables')"
ref="variableRef"
/>
</a-form-item>
</j-form-item>
</template>
</a-form>
</j-form>
</div>
<template #footer>
<a-space>
<j-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"
@ -68,7 +75,7 @@
<j-button v-if="current === 3" type="primary" @click="onOk"
>确定</j-button
>
</a-space>
</j-space>
</template>
</j-modal>
</template>
@ -90,6 +97,7 @@ const props = defineProps({
},
options: {
type: Object,
default: () => {},
},
name: {
type: Number,
@ -106,9 +114,11 @@ const formModel = reactive({
notifierId: '',
templateId: '',
variables: {},
options: {},
});
const variable = ref([]);
const variableRef = ref();
watch(
() => props.value,
@ -118,6 +128,27 @@ watch(
{ deep: true, immediate: true },
);
watchEffect(() => {
formModel.options = props.options || {};
});
const onValChange = (val: any, type: string) => {
if (type === 'notifyType') {
formModel.templateId = '';
formModel.variables = [];
formModel.notifierId = '';
} else if (type === 'notifierId') {
formModel.templateId = '';
formModel.variables = [];
} else if (type === 'templateId') {
formModel.variables = [];
}
formModel.options = {
...unref(formModel.options),
...val,
};
};
const jumpStep = async (val: number) => {
if (val === 0) {
current.value = val;
@ -134,7 +165,6 @@ const jumpStep = async (val: number) => {
onlyMessage('请选择通知配置', 'error');
}
} else if (val === 3) {
formModel.templateId = '1628943618904956928';
if (formModel.templateId) {
const resp = await Template.getTemplateDetail(formModel.templateId);
if (resp.status === 200) {
@ -162,8 +192,11 @@ const next = async () => {
const onCancel = () => {
emit('cancel');
};
const onOk = () => {
emit('save');
const onOk = async () => {
const _data = await variableRef.value.onSave();
formModel.variables = _data;
const { options, ...extra } = formModel;
emit('save', { ...extra }, { ...options });
};
</script>

View File

@ -1,6 +1,6 @@
<template>
<a-input-group compact>
<a-select
<j-input-group compact>
<j-select
:options="[
{ label: '手动输入', value: 'fixed' },
{ label: '内置参数', value: 'upper' },
@ -10,22 +10,22 @@
@change="sourceChange"
/>
<template v-if="source === 'upper'">
<a-tree-select
<j-tree-select
v-model:value="upperKey"
:treeData="builtInList"
placeholder="请选择参数"
style="width: calc(100% - 120px)"
>
<template #title="{ name, description }">
<a-space>
<j-space>
{{ name }}
<span style="color: grey; margin-left: 5px">{{ description }}</span>
</a-space>
</j-space>
</template>
</a-tree-select>
</j-tree-select>
</template>
<template v-else>
<a-date-picker
<j-date-picker
:value="value.value"
allowClear
format="YYYY-MM-DD HH:mm:ss"
@ -33,7 +33,7 @@
v-if="item.type === 'date'"
@change="(_, dateString) => itemOnChange(dateString)"
/>
<a-input-number
<j-input-number
:value="value.value"
allowClear
style="width: calc(100% - 120px)"
@ -41,7 +41,7 @@
:placeholder="`请输入${item.name}`"
@change="itemOnChange"
/>
<a-input
<j-input
:value="value.value"
allowClear
style="width: calc(100% - 120px)"
@ -50,7 +50,7 @@
@change="(e) => itemOnChange(e.target.value)"
/>
</template>
</a-input-group>
</j-input-group>
</template>
<script lang="ts" setup>

View File

@ -1,12 +1,12 @@
<template>
<a-input
<j-input
allowClear
placeholder="请上传文件"
v-model:value="url"
@change="onChange"
>
<template #addonAfter>
<a-upload
<j-upload
name="file"
:showUploadList="false"
:accept="'image/jpeg,image/png'"
@ -23,9 +23,9 @@
<AIcon type="PlusOutlined" v-else />
上传附件
</j-button>
</a-upload>
</j-upload>
</template>
</a-input>
</j-input>
</template>
<script lang="ts" setup>

View File

@ -26,7 +26,7 @@ const props = defineProps({
},
});
const emit = defineEmits(['update:value']);
const emit = defineEmits(['update:value', 'change']);
const departmentTree = ref<any[]>([]);
const keys = ref<any[]>([]);
@ -68,6 +68,7 @@ const onChange = (key: string[], label: string[]) => {
source: 'fixed',
value: key,
});
emit('change', label)
};
</script>

View File

@ -22,10 +22,10 @@ const props = defineProps({
},
});
const emit = defineEmits(['update:value']);
const emit = defineEmits(['update:value', 'change']);
const tagsList = ref<any[]>([]);
const keys = ref<string>('');
const keys = ref<string | undefined>(undefined);
const getDepartment = async (id: string) => {
const resp = await TemplateApi.getTags(id);
@ -37,9 +37,9 @@ const getDepartment = async (id: string) => {
watch(
() => props.value,
(newVal) => {
keys.value = newVal || ''
keys.value = newVal || undefined
},
{ deep: true, immediate: true },
{ immediate: true },
);
watch(
@ -52,12 +52,12 @@ watch(
{ deep: true, immediate: true },
);
const onChange = (key: string, label: string[]) => {
// TODO label
const onChange = (key: string, option: any) => {
emit('update:value', {
source: 'fixed',
value: key,
});
emit('change', option ? option?.label : '')
};
</script>

View File

@ -1,83 +1,87 @@
<template>
<a-input-group compact>
<a-select
<j-input-group compact>
<j-select
style="width: 120px"
v-model:value="mySource"
@change="sourceChange"
>
<a-select-option key="5" value="relation">
<j-select-option key="5" value="relation">
平台用户
</a-select-option>
<a-select-option
</j-select-option>
<j-select-option
v-if="notifyType === 'dingTalk'"
key="1"
value="fixed"
>
钉钉用户
</a-select-option>
<a-select-option
</j-select-option>
<j-select-option
v-else-if="notifyType === 'weixin'"
key="2"
value="fixed"
>
微信用户
</a-select-option>
<a-select-option
</j-select-option>
<j-select-option
v-else-if="notifyType === 'email'"
key="3"
value="fixed"
>
固定邮箱
</a-select-option>
<a-select-option v-else key="4" value="fixed">
</j-select-option>
<j-select-option v-else key="4" value="fixed">
固定号码
</a-select-option>
</a-select>
<a-tree-select
</j-select-option>
</j-select>
<j-tree-select
v-if="source === 'relation'"
style="width: calc(100% - 120px)"
placeholder="请选择收信人"
@select="treeSelect"
@select="(key, node) => onChange(source, key, false, node?.relation, node.name)"
:tree-data="treeData"
:multiple="['email'].includes(notifyType)"
: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 #title="{ key, username, title }">
<div style="display: flex; justify-content: space-between; margin-right: 10px;" v-if="key !== 'p1' && key !== 'p2'">{{ title }} <span style="color: #cfcfcf;">{{ username }}</span></div>
<span v-else>{{ title }}</span>
</template>
</j-tree-select>
<template v-else>
<j-select
style="width: calc(100% - 120px)"
v-if="['dingTalk', 'weixin'].includes(notifyType)"
placeholder="请选择收信人"
:value="value?.value"
showSearch
@change="(val, option) => onChange(source, val, false, option?.label || option?.name)"
:options="relationList"
/>
<j-select
style="width: calc(100% - 120px)"
v-else-if="['email'].includes(notifyType)"
placeholder="请输入收件人邮箱,多个收件人用换行分隔"
:value="value?.value"
mode="tags"
@change="(val) => onChange(source, val, false, Array.isArray(val) ? val.join(',') : val)"
/>
<j-input
style="width: calc(100% - 120px)"
v-else-if="['sms', 'voice'].includes(notifyType)"
placeholder="请输入固定号码"
:value="value?.value"
@change="(e) => onChange(source, e.target.value, false, e.target.value)"
></j-input>
</template>
</j-input-group>
</template>
<script lang="ts" setup>
import { storeToRefs } from 'pinia';
import { useSceneStore } from '@/store/scene';
import NoticeApi from '@/api/notice/config';
import { unionBy } from 'lodash-es';
const sceneStore = useSceneStore();
const { data } = storeToRefs(sceneStore);
@ -93,7 +97,7 @@ const props = defineProps({
},
});
const emit = defineEmits(['update:value']);
const emit = defineEmits(['update:value', 'change']);
const notifyType = computed(() => {
return props.notify?.notifyType;
@ -142,6 +146,7 @@ const treeData = ref<any[]>([
},
]);
const mySource = ref<string>('relation');
const labelMap = new Map();
const getRelationUsers = async (notifyType: string, notifierId: string) => {
let resp = undefined;
@ -151,7 +156,12 @@ const getRelationUsers = async (notifyType: string, notifierId: string) => {
resp = await NoticeApi.queryWechatUsers(notifierId);
}
if (resp && resp.status === 200) {
relationList.value = resp.result;
relationList.value = unionBy(resp.result, 'id').map((item: any) => {
return {
value: item.id,
label: item.name,
};
});
}
};
@ -206,43 +216,72 @@ const getUser = async (_source: string, triggerType: string) => {
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,
});
const getObj = (
_source: string = 'fixed',
_value?: string | string[],
isRelation?: boolean,
) => {
const obj: any = {
source: _source,
};
if (_source === 'relation') {
if (isRelation) {
obj.relation = {
objectType: 'device',
objectSource: {
source: 'upper',
upperKey: 'deviceId',
},
related: {
objectType: 'user',
relation: _value,
},
};
} else {
obj.relation = {
objectType: 'user',
objectId: _value,
};
}
} else {
obj.value = _value;
}
return obj;
};
const onChange = (
_source: string = 'fixed',
_value?: string | string[],
isRelation?: boolean,
_name?: string,
) => {
let _values: any = undefined;
const _names: string[] = [_name || ''];
if (Array.isArray(_value)) {
if (props?.notify?.notifyType === 'email') {
if (isRelation) {
const arr = _value.map((item) => {
const _item = labelMap.get(item);
_names.push(_item?.name || '');
return getObj('relation', item, _item?.relation);
});
_values = arr;
} else {
_values = getObj(_source, _value, false);
}
}
} else {
_values = getObj(_source, _value, isRelation);
}
console.log(_values, '_values')
emit('update:value', _values);
emit('change', { sendTo: _names.filter((item) => !!item).join(',') });
};
watch(

View File

@ -4,12 +4,12 @@
<span>执行</span>
<ShakeLimit
v-if="props.openShakeLimit"
v-model:value="shakeLimit"
v-model:value="FormModel.branches[name].shakeLimit"
/>
</div>
<div class="actions-warp">
<a-collapse v-model:activeKey="activeKeys">
<a-collapse-panel key="1">
<j-collapse v-model:activeKey="activeKeys">
<j-collapse-panel key="1">
<template #header>
<span>
串行
@ -21,17 +21,17 @@
<div class="actions-list">
<List
type="serial"
:branchesName="props.name"
:branchesName="name"
:parallel="false"
:actions="
serialArray.length ? serialArray[0].actions : []
"
@add="(_item) => onAdd(_item, false)"
@delete="onDelete"
@delete="(_key) => onDelete(_key, false)"
/>
</div>
</a-collapse-panel>
<a-collapse-panel key="2">
</j-collapse-panel>
<j-collapse-panel key="2">
<template #header>
<span>
并行
@ -43,7 +43,7 @@
<div class="actions-list">
<List
type="parallel"
:branchesName="props.name"
:branchesName="name"
:parallel="true"
:actions="
parallelArray.length
@ -51,11 +51,11 @@
: []
"
@add="(_item) => onAdd(_item, true)"
@delete="onDelete"
@delete="(_key) => onDelete(_key, true)"
/>
</div>
</a-collapse-panel>
</a-collapse>
</j-collapse-panel>
</j-collapse>
</div>
</div>
</template>
@ -63,30 +63,32 @@
<script lang="ts" setup>
import ShakeLimit from '../components/ShakeLimit/index.vue';
import { List } from './ListItem';
import { BranchesThen } from '../../typings';
import { BranchesThen } from '../../typings'
import { PropType } from 'vue';
import { randomString } from '@/utils/utils';
import { storeToRefs } from 'pinia';
import { useSceneStore } from 'store/scene'
interface ActionsProps {
name: number;
openShakeLimit?: boolean;
thenOptions: BranchesThen[];
}
const sceneStore = useSceneStore()
const { data: FormModel } = storeToRefs(sceneStore)
const props = defineProps({
name: Number,
name: {
type: Number,
default: 0
},
thenOptions: {
type: Array as PropType<ActionsProps['thenOptions']>,
type: Array as PropType<BranchesThen[]>,
default: () => [],
},
openShakeLimit: Boolean,
openShakeLimit: {
type: Boolean,
default: false
},
});
const emit = defineEmits(['update', 'add']);
const shakeLimit = ref({
enabled: false,
});
const activeKeys = ref<string[]>(['1']);
const parallelArray = ref<BranchesThen[]>([]);
const serialArray = ref<BranchesThen[]>([]);
@ -98,8 +100,6 @@ watch(
parallelArray.value = newVal.filter((item) => item.parallel);
serialArray.value = newVal.filter((item) => !item.parallel);
console.log(parallelArray.value, serialArray.value, '123')
const isSerialActions = serialArray.value.some((item) => {
return !!item.actions.length;
});
@ -112,8 +112,6 @@ watch(
activeKeys.value = ['2'];
lock.value = true;
}
//TODO
},
{
deep: true,
@ -121,36 +119,32 @@ watch(
},
);
const onDelete = (_key: string) => {
const aIndex = unref(serialArray)[0].actions?.findIndex(
(aItem) => aItem.key === _key,
);
if (aIndex !== -1) {
serialArray.value[0].actions?.splice(aIndex, 1);
emit('update', serialArray[0], false);
}
const onDelete = (_key: string, _parallel: boolean) => {
const thenName = props.thenOptions.findIndex(item => item.parallel === _parallel)
const actionIndex = FormModel.value.branches?.[props.name].then?.[thenName].actions.findIndex(item => item.key === _key)
if (actionIndex !== -1) {
FormModel.value.branches?.[props.name].then?.[thenName].actions.splice(actionIndex!, 1)
}
};
const onAdd = (actionItem: any, _parallel: boolean) => {
const newParallelArray = [...parallelArray.value];
if (newParallelArray.length) {
const indexOf = newParallelArray[0].actions?.findIndex(
(aItem) => aItem.key === actionItem.key,
);
if (indexOf !== -1) {
newParallelArray[0].actions.splice(indexOf, 1, actionItem);
} else {
newParallelArray[0].actions.push(actionItem);
}
parallelArray.value = [...newParallelArray];
emit('update', newParallelArray[0], _parallel);
const thenName = props.thenOptions.findIndex(item => item.parallel === _parallel)
if (thenName !== -1) { //
const cacheAction = props.thenOptions[thenName].actions
const indexOf = cacheAction?.findIndex(item => item.key === actionItem.key) || -1
if (indexOf !== -1) {
FormModel.value.branches?.[props.name].then?.[thenName].actions.splice(indexOf, 1, actionItem)
} else {
actionItem.key = randomString();
emit('add', {
parallel: _parallel,
key: randomString(),
actions: [actionItem],
});
FormModel.value.branches?.[props.name].then?.[thenName].actions.push(actionItem)
}
} else { //
const newThenItem = {
parallel: _parallel,
key: randomString(),
actions: [actionItem]
}
FormModel.value.branches?.[props.name].then.push(newThenItem)
}
};
</script>

View File

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

View File

@ -1,7 +1,7 @@
<template>
<div class='dropdown-time-picker'>
<j-time-picker
v-if='!_type'
v-if='type === "time"'
open
v-model:value='myValue'
class='manual-time-picker'
@ -59,9 +59,6 @@ const change = (e: Dayjs) => {
emit('change', e.format(myFormat))
}
const _type = computed(() => {
return props.value?.includes('-')
})
</script>
<style lang='less'>

View File

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

View File

@ -23,39 +23,49 @@
<div class='select-box-content'>
<DropdownTimePicker
v-if='["time","date"].includes(item.component)'
:type='item.component'
type='time'
v-model:value='myValue'
@change='timeChange'
/>
<DropdownMenus
<template
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
:selectedKeys='myValue ? [myValue] : []'
:treeData='item.key === "upper" ? metricOption : options'
@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>
<DropdownMenus
v-if='(["metric", "upper"].includes(item.key) ? metricOptions : options).length'
:options='["metric", "upper"].includes(item.key) ? metricOptions : options'
@click='onSelect'
/>
<div class='scene-select-empty' v-else>
<j-empty />
</div>
</template>
<template v-else-if='item.component === "tree"'>
<div style='min-width: 400px' v-if='(item.key === "upper" ? metricOptions : options).length'>
<j-tree
:selectedKeys='myValue ? [myValue] : []'
:treeData='item.key === "upper" ? metricOptions : options'
@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
v-else
v-model:modelValue='myValue'
:itemType='item.component'
:options='item.key === "upper" ? metricOption : options'
:options='item.key === "upper" ? metricOptions : options'
@change='valueItemChange'
/>
</div>
@ -73,8 +83,6 @@ import { defaultSetting } from './typings'
import { DropdownMenus, DropdownTimePicker} from '../DropdownButton'
import { getComponent, getOption } from '../DropdownButton/util'
const valueItemKey = ['int', 'int','long','float','double','string', 'password']
type Emit = {
(e: 'update:value', data: ValueType): void
(e: 'update:source', data: string): void

View File

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

View File

@ -1,17 +1,17 @@
<template>
<div class="shakeLimit">
<a-switch
<j-switch
checkedChildren="开启防抖"
unCheckedChildren="关闭防抖"
v-model:checked="shakeLimit.enabled"
style="margin-right: 12px"
/>
<template v-if="shakeLimit.enabled">
<a-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>
<a-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>
<a-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>
</div>
</template>
@ -55,8 +55,7 @@ const shakeLimit = reactive<ShakeLimitType>({
Object.assign(shakeLimit, props.value)
watch(() => shakeLimit, () => {
const cloneValue = cloneDeep(shakeLimit)
emit('update:value', cloneValue)
emit('update:value', {...shakeLimit})
}, {
deep: true
})

View File

@ -19,19 +19,23 @@
>
<j-popconfirm
title='该操作将清空其它所有否则条件,确认删除?'
placement='topRight'
placement="topRight"
@confirm='onDeleteAll'
>
<AIcon type='CloseOutlined' v-show='showDelete' />
<div class='terms-params-delete' v-show='showDelete'>
<AIcon type='CloseOutlined' />
</div>
</j-popconfirm>
<div class='actions-terms-list-content'>
<template v-if='showWhen'>
<TermsItem
v-for='(item, index) in data.when'
v-for='(item, index) in whenData'
:key='item.key'
:name='index'
:showDeleteBtn='whenData.length !== 1'
:isFirst='index === 0'
:isLast='index === data.when.length -1'
:isLast='index === whenData.length -1'
:branchName='name'
:whenName='index'
:data='item'
@ -45,9 +49,17 @@
</div>
</div>
<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>
</template>
@ -58,6 +70,7 @@ import type { ActionBranchesProps } from '@/views/rule-engine/Scene/typings'
import TermsItem from './TermsItem.vue'
import { storeToRefs } from 'pinia';
import { useSceneStore } from 'store/scene'
import Action from '../../action/index.vue'
const sceneStore = useSceneStore()
const { data: FormModel } = storeToRefs(sceneStore)
@ -92,6 +105,10 @@ const showWhen = computed(() => {
return props.data.when.length
})
const whenData = computed(() => {
return props.data.when
})
const onDelete = () => {
}
@ -101,19 +118,36 @@ const onDeleteAll = () => {
}
const mouseover = () => {
if (props.isFirst && props.data.when.length){
if (!props.isFirst){
showDelete.value = true
}
}
const mouseout = () => {
if (props.isFirst && props.data.when.length){
if (!props.isFirst){
showDelete.value = false
}
}
const addWhen = () => {
const whenItem = {
key: `when_${new Date().getTime()}`,
type: 'and',
terms: [
{
column: undefined,
value: {
source: 'fixed',
value: undefined
},
termType: undefined,
key: 'params_1',
type: 'and',
}
]
}
FormModel.value.branches?.[props.name].when.push(whenItem)
FormModel.value.branches?.push(null)
}
const optionsClass = computed(() => {
@ -124,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>
<style scoped lang='less'>

View File

@ -40,7 +40,7 @@
icon='icon-canshu'
placeholder='参数值'
:options='valueOptions'
:metricOption='metricOption'
:metricOptions='metricOption'
:tabsOptions='tabsOptions'
v-model:value='paramsValue.value.value'
v-model:source='paramsValue.value.source'
@ -50,7 +50,7 @@
icon='icon-canshu'
placeholder='参数值'
:options='valueOptions'
:metricOption='metricOption'
:metricOptions='metricOption'
:tabsOptions='tabsOptions'
v-model:value='paramsValue.value.value'
v-model:source='paramsValue.value.source'
@ -75,6 +75,11 @@ import { getOption } from '../DropdownButton/util'
import ParamsDropdown, { DoubleParamsDropdown } from '../ParamsDropdown'
import { inject } from 'vue'
import { ContextKey } from './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
@ -95,10 +100,26 @@ const props = defineProps({
type: Boolean,
default: true
},
showDeleteBtn: {
type: Boolean,
default: true
},
name: {
type: Number,
default: 0
},
termsName: {
type: Number,
default: 0
},
branchName: {
type: Number,
default: 0
},
whenName: {
type: Number,
default: 0
},
value: {
type: Object as PropType<TermsType>,
default: () => ({
@ -176,13 +197,13 @@ const showDouble = computed(() => {
})
const mouseover = () => {
if (props.name !== 0){
if (props.showDeleteBtn){
showDelete.value = true
}
}
const mouseout = () => {
if (props.name !== 0){
if (props.showDeleteBtn){
showDelete.value = false
}
}
@ -205,11 +226,21 @@ const termsTypeSelect = () => {
}
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]?.when?.[props.whenName]?.terms?.push(terms)
}
const onDelete = () => {
formModel.value.branches?.[props.branchName]?.when?.[props.whenName]?.terms?.splice(props.name, 1)
}
nextTick(() => {

View File

@ -32,13 +32,13 @@
</div>
</template>
</template>
<j-form-item
v-else
:name='["branches", 0, "then"]'
:rules='rules'
>
</j-form-item>
<!-- <j-form-item-->
<!-- v-else-->
<!-- :name='["branches", 0, "then"]'-->
<!-- :rules='rules'-->
<!-- >-->
<!-- -->
<!-- </j-form-item>-->
</div>
</template>
@ -65,18 +65,6 @@ const change = (e: boolean) => {
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[] => {
return data?.map(item => {
return {
@ -97,7 +85,19 @@ const queryColumn = (dataModel: FormModelType) => {
}
const addBranches = () => {
const branchesItem = {
when: [],
key: `branches_${new Date().getTime()}`,
shakeLimit: {
enabled: false,
time: 1,
threshold: 1,
alarmFirst: false,
},
then: [],
}
const lastIndex = data.value.branches!.length - 1 || 0
data.value.branches?.splice(lastIndex, 1, branchesItem)
}
const branchesDelete = (index: number) => {

View File

@ -26,21 +26,29 @@
</j-popconfirm>
<j-form-item
v-for='(item, index) in data.terms'
v-for='(item, index) in termsData'
:key='item.key'
:name='["branches", branchName, "when", whenName, "terms", index]'
>
<ParamsItem
v-model:value='formModel.branches[branchName].when[whenName].terms[index]'
:isFirst='index === 0'
:isLast='index === data.terms.length - 1'
:isLast='index === termsData.length - 1'
:showDeleteBtn='termsData.length !== 1'
:name='index'
:termsName='name'
:whenName='whenName'
:branchName='branchName'
@change='paramsChange'
@delete='paramsDelete'
@add='paramsAdd'
/>
</j-form-item>
</div>
<div class='terms-group-add' @click='addTerms' v-if='isLast'>
<div class='terms-content'>
<AIcon type='PlusOutlined' />
<span>分组</span>
</div>
</div>
</div>
</div>
</template>
@ -61,6 +69,14 @@ const props = defineProps({
type: Boolean,
default: true
},
isLast: {
type: Boolean,
default: true
},
showDeleteBtn: {
type: Boolean,
default: true
},
data: {
type: Object as PropType<TermsType>,
default: () => ({
@ -73,6 +89,10 @@ const props = defineProps({
type: String,
default: ''
},
name: {
type: Number,
default: 0
},
branchName: {
type: Number,
default: 0
@ -85,36 +105,48 @@ const props = defineProps({
const showDelete = ref(false)
const termsData = computed(() => {
return props.data.terms
})
const mouseover = () => {
if (props.whenName !== 0){
if (props.showDeleteBtn){
showDelete.value = true
}
}
const mouseout = () => {
if (props.whenName !== 0){
if (props.showDeleteBtn){
showDelete.value = false
}
}
const onDelete = () => {
}
const onDeleteAll = () => {
formModel.value.branches?.[props.branchName]?.when?.splice(props.name, 1)
}
const paramsChange = () => {
}
const paramsDelete = () => {
}
const paramsAdd = () => {
const addTerms = () => {
const terms = {
type: 'and',
terms: [
{
column: undefined,
value: {
source: 'fixed',
value: undefined
},
termType: undefined,
key: 'params_1',
type: 'and',
}
],
key: `terms_${new Date().getTime()}`
}
formModel.value.branches?.[props.branchName]?.when?.push(terms)
}
</script>

View File

@ -15,7 +15,6 @@
position: absolute;
top: -10px;
right: -10px;
display: none;
width: 20px;
height: 20px;
color: #999;
@ -37,7 +36,7 @@
.actions-terms {
.actions-terms-warp {
display: flex;
width: 66.66%;
//width: 66.66%;
margin-bottom: 24px;
&.first-children {

View File

@ -714,7 +714,7 @@
placeholder="请选中角色"
></j-select>
<PermissionButton
:uhasPermission="`${rolePermission}:update`"
:hasPermission="`${rolePermission}:update`"
type="link"
@click="
clickAddItem(
@ -755,7 +755,7 @@
</template>
</j-tree-select>
<PermissionButton
:uhasPermission="`${deptPermission}:update`"
:hasPermission="`${deptPermission}:update`"
type="link"
@click="
clickAddItem(
@ -1000,7 +1000,7 @@
/>
</j-form-item>
<j-form-item label="logo">
<a-upload
<j-upload
v-model:file-list="form.fileList"
accept=".jpg,.png,.jfif,.pjp,.pjpeg,.jpeg"
:maxCount="1"
@ -1036,7 +1036,7 @@
点击上传图片
</div>
</div>
</a-upload>
</j-upload>
</j-form-item>
<j-form-item
@ -1297,7 +1297,7 @@
placeholder="请选中角色"
></j-select>
<PermissionButton
:uhasPermission="`${rolePermission}:update`"
:hasPermission="`${rolePermission}:update`"
type="link"
@click="
clickAddItem(
@ -1332,7 +1332,7 @@
</template>
</j-tree-select>
<PermissionButton
:uhasPermission="`${deptPermission}:update`"
:hasPermission="`${deptPermission}:update`"
type="link"
@click="
clickAddItem(

View File

@ -20,7 +20,7 @@
<template #headerTitle>
<div style="display: flex; align-items: center">
<PermissionButton
:uhasPermission="`${permission}:add`"
:hasPermission="`${permission}:add`"
type="primary"
@click="() => table.toSave()"
>
@ -110,7 +110,7 @@
</j-dropdown>
<PermissionButton
v-else
:uhasPermission="item.permission"
:hasPermission="item.permission"
:tooltip="item.tooltip"
:pop-confirm="item.popConfirm"
@click="item.onClick"
@ -151,7 +151,7 @@
<j-space :size="16">
<PermissionButton
v-for="i in table.getActions(slotProps, 'table')"
:uhasPermission="i.permission"
:hasPermission="i.permission"
type="link"
:tooltip="i.tooltip"
:pop-confirm="i.popConfirm"
@ -190,7 +190,7 @@ import { useMenuStore } from '@/store/menu';
import { message } from 'ant-design-vue';
const menuStory = useMenuStore();
const permission = 'system/User';
const permission = 'system/Apply';
const typeOptions = [
{
label: '内部独立应用',
@ -312,7 +312,7 @@ const table = {
const result = [
{
permission: true,
permission: `${permission}:update`,
key: 'edit',
text: '编辑',
tooltip: {
@ -322,7 +322,7 @@ const table = {
onClick: () => table.toSave(data.id),
},
{
permission: true,
permission: `${permission}:update`,
key: 'action',
text: disabled ? '禁用' : '启用',
tooltip: {
@ -335,7 +335,7 @@ const table = {
icon: disabled ? 'StopOutlined' : 'PlayCircleOutlined',
},
{
permission: true,
permission: `${permission}:delete`,
key: 'delete',
text: '删除',
tooltip: {
@ -348,7 +348,7 @@ const table = {
disabled,
icon: 'DeleteOutlined',
},
] as ActionsType[];
] as any[];
const otherServers = data.integrationModes.map(
(item: any) => item.value as string,
);
@ -356,12 +356,12 @@ const table = {
key: 'others',
text: '其他',
icon: 'EllipsisOutlined',
children: [] as ActionsType[],
children: [] as any[],
};
//
if (otherServers.includes('page'))
others.children?.push({
permission: true,
permission: [`${permission}:add`,`${permission}:update`],
key: 'page',
text: '集成菜单',
tooltip: {
@ -378,7 +378,7 @@ const table = {
if (otherServers.includes('apiServer'))
others.children?.push(
{
permission: true,
permission: [`${permission}:add`,`${permission}:update`],
key: 'empowerment',
text: '赋权',
tooltip: {
@ -394,7 +394,7 @@ const table = {
},
},
{
permission: true,
permission: [`${permission}:add`,`${permission}:update`],
key: 'viewApi',
text: '查看API',
tooltip: {

View File

@ -62,7 +62,7 @@
<j-form-item label="系统logo">
<div class="upload-image-warp-logo">
<div class="upload-image-border-logo">
<a-upload
<j-upload
name="file"
:action="action"
:headers="headers"
@ -114,7 +114,7 @@
/>
</div>
</div>
</a-upload>
</j-upload>
<div v-if="form.logoLoading">
<div
class="upload-loading-mask"
@ -152,7 +152,7 @@
</template>
<div class="upload-image-warp-logo">
<div class="upload-image-border-logo">
<a-upload
<j-upload
name="file"
:action="action"
:headers="headers"
@ -201,7 +201,7 @@
</div>
</div>
</div>
</a-upload>
</j-upload>
</div>
</div>
@ -215,7 +215,7 @@
<j-form-item label="登录背景图">
<div class="upload-image-warp-back">
<div class="upload-image-border-back">
<a-upload
<j-upload
name="file"
:action="action"
:headers="headers"
@ -256,7 +256,7 @@
</div>
</div>
</div>
</a-upload>
</j-upload>
</div>
</div>
<div class="upload-tips">

View File

@ -97,7 +97,7 @@
</template>
<template #action="slotProps">
<PermissionButton
:uhasPermission="`{permission}:delete`"
:hasPermission="`{permission}:delete`"
type="link"
:tooltip="{ title: '删除' }"
:popConfirm="{

View File

@ -20,7 +20,7 @@
<template #headerTitle>
<PermissionButton
type="primary"
:uhasPermission="`${permission}:add`"
:hasPermission="`${permission}:add`"
@click="table.openDialog({})"
>
<AIcon type="PlusOutlined" />新增
@ -47,7 +47,7 @@
<template #action="slotProps">
<j-space :size="16">
<PermissionButton
:uhasPermission="`${permission}:update`"
:hasPermission="`${permission}:update`"
type="link"
:tooltip="{
title: '编辑',
@ -57,7 +57,7 @@
<AIcon type="EditOutlined" />
</PermissionButton>
<PermissionButton
:uhasPermission="`${permission}:manage`"
:hasPermission="`${permission}:manage`"
type="link"
:tooltip="{
title:
@ -81,7 +81,7 @@
<AIcon type="icon-ziyuankuguanli" />
</PermissionButton>
<PermissionButton
:uhasPermission="`${permission}:action`"
:hasPermission="`${permission}:action`"
type="link"
:popConfirm="{
title: `确定要${
@ -109,7 +109,7 @@
</PermissionButton>
<PermissionButton
:uhasPermission="`${permission}:delete`"
:hasPermission="`${permission}:delete`"
type="link"
:tooltip="{
title: table.getRowStatus(slotProps)

View File

@ -20,7 +20,7 @@
<template #headerTitle>
<j-space>
<PermissionButton
:uhasPermission="`${permission}:assert`"
:hasPermission="`${permission}:assert`"
type="primary"
@click="table.clickAdd"
>
@ -32,7 +32,7 @@
<j-menu>
<j-menu-item>
<PermissionButton
:uhasPermission="`${permission}:bind`"
:hasPermission="`${permission}:bind`"
:popConfirm="{
title: `是否批量解除绑定`,
onConfirm: () =>
@ -46,7 +46,7 @@
</j-menu-item>
<j-menu-item>
<PermissionButton
:uhasPermission="`${permission}:assert`"
:hasPermission="`${permission}:assert`"
@click="table.clickEdit()"
>
<AIcon type="EditOutlined" />批量编辑
@ -117,14 +117,14 @@
</template>
<template #actions>
<PermissionButton
:uhasPermission="`${permission}:assert`"
:hasPermission="`${permission}:assert`"
@click="table.clickEdit(slotProps)"
>
<AIcon type="EditOutlined" />
</PermissionButton>
<PermissionButton
:uhasPermission="`${permission}:bind`"
:hasPermission="`${permission}:bind`"
:popConfirm="{
title: `是否解除绑定`,
onConfirm: () => table.clickUnBind(slotProps),
@ -157,7 +157,7 @@
<j-space :size="16">
<PermissionButton
v-for="i in table.getActions(slotProps, 'table')"
:uhasPermission="i.permission"
:hasPermission="i.permission"
type="link"
:tooltip="i?.tooltip"
:pop-confirm="i.popConfirm"

View File

@ -20,7 +20,7 @@
<template #headerTitle>
<j-space>
<PermissionButton
:uhasPermission="`${permission}:assert`"
:hasPermission="`${permission}:assert`"
type="primary"
@click="dialogs.addShow = true"
>
@ -32,7 +32,7 @@
<j-menu>
<j-menu-item>
<PermissionButton
:uhasPermission="`${permission}:bind`"
:hasPermission="`${permission}:bind`"
:popConfirm="{
title: `是否批量解除绑定`,
onConfirm: () =>
@ -46,7 +46,7 @@
</j-menu-item>
<j-menu-item>
<PermissionButton
:uhasPermission="`${permission}:assert`"
:hasPermission="`${permission}:assert`"
@click="() => table.clickEdit()"
>
<AIcon type="EditOutlined" />批量编辑

View File

@ -203,7 +203,7 @@
<PermissionButton
type="primary"
:uhasPermission="`${permission}:update`"
:hasPermission="`${permission}:update`"
@click="form.clickSave"
>
保存

View File

@ -10,7 +10,7 @@
<template #headerTitle>
<PermissionButton
type="primary"
:uhasPermission="`${permission}:update`"
:hasPermission="`${permission}:update`"
@click="openDialog('新增', {})"
>
<AIcon type="PlusOutlined" />新增

View File

@ -18,7 +18,7 @@
<template #headerTitle>
<PermissionButton
type="primary"
:uhasPermission="`${permission}:add`"
:hasPermission="`${permission}:add`"
@click="table.toDetails({})"
>
<AIcon type="PlusOutlined" />新增
@ -51,7 +51,7 @@
<PermissionButton
type="link"
:uhasPermission="`${permission}:add`"
:hasPermission="`${permission}:add`"
:tooltip="{ title: '新增子菜单' }"
@click="table.addChildren(slotProps)"
>
@ -59,7 +59,7 @@
</PermissionButton>
<PermissionButton
type="link"
:uhasPermission="`${permission}:delete`"
:hasPermission="`${permission}:delete`"
:tooltip="{ title: '删除' }"
:popConfirm="{
title: `是否删除该菜单`,

View File

@ -28,7 +28,7 @@
<template #overlay>
<j-menu>
<j-menu-item>
<a-upload
<j-upload
name="file"
action="#"
accept=".json"
@ -45,7 +45,7 @@
>
导入
</PermissionButton>
</a-upload>
</j-upload>
</j-menu-item>
<j-menu-item>
<PermissionButton
@ -76,7 +76,7 @@
<template #action="slotProps">
<j-space :size="16">
<PermissionButton
:uhasPermission="`${permission}:update`"
:hasPermission="`${permission}:update`"
type="link"
:tooltip="{
title: '编辑',

View File

@ -66,7 +66,7 @@
<template v-else-if="column.key === 'action'">
<PermissionButton
type="link"
:uhasPermission="`{permission}:delete`"
:hasPermission="`{permission}:delete`"
:popConfirm="{
title: `确定删除`,
onConfirm: () =>

View File

@ -64,7 +64,7 @@ const confirm = () => {
// @ts-ignore
window?.onSaveSuccess(resp.result.id);
window.close();
} else jumpPage(`system/Role/detail`, { id: resp.result.id });
} else jumpPage(`system/Role/Detail`, { id: resp.result.id });
}
})
.finally(() => (loading.value = false));

View File

@ -23,7 +23,7 @@
<template #headerTitle>
<PermissionButton
type="primary"
:uhasPermission="`${permission}:add`"
:hasPermission="`${permission}:add`"
@click="dialogVisible = true"
>
<AIcon type="PlusOutlined" />新增
@ -33,7 +33,7 @@
<template #action="slotProps">
<j-space :size="16">
<PermissionButton
:uhasPermission="`${permission}:update`"
:hasPermission="`${permission}:update`"
type="link"
:tooltip="{
title: '编辑',
@ -44,7 +44,7 @@
</PermissionButton>
<PermissionButton
type="link"
:uhasPermission="`${permission}:delete`"
:hasPermission="`${permission}:delete`"
:tooltip="{ title: '删除' }"
:popConfirm="{
title: `确定要删除吗`,

11855
yarn.lock

File diff suppressed because it is too large Load Diff