fix: 通知订阅
This commit is contained in:
parent
0edeb9804a
commit
7b6684a9f4
|
@ -25,7 +25,7 @@
|
|||
"event-source-polyfill": "^1.0.31",
|
||||
"global": "^4.4.0",
|
||||
"jetlinks-store": "^0.0.3",
|
||||
"jetlinks-ui-components": "^1.0.23",
|
||||
"jetlinks-ui-components": "^1.0.24",
|
||||
"js-cookie": "^3.0.1",
|
||||
"less": "^4.1.3",
|
||||
"less-loader": "^11.1.0",
|
||||
|
|
|
@ -1,24 +1,26 @@
|
|||
import server from '@/utils/request'
|
||||
|
||||
// 获取记录列表
|
||||
export const getList_api = (data: object): any => server.post(`/notifications/_query`, data)
|
||||
export const getList_api = (data: any): any => server.post(`/notifications/_query`, data)
|
||||
// 获取未读记录列表
|
||||
export const getListByUnRead_api = (data: object): any => server.post(`/notifications/_query`, data)
|
||||
// export const getListByUnRead_api = (data: any): any => server.post(`/notifications/_query`, data)
|
||||
// 修改记录状态
|
||||
export const changeStatus_api = (type: '_read' | '_unread', data: string[]): any => server.post(`/notifications/${type}`, data)
|
||||
|
||||
// 查询告警记录详情
|
||||
export const getDetail = (id: string): any => server.get(`/alarm/record/${id}`)
|
||||
|
||||
const encodeParams = (params: Record<string, any>) => {
|
||||
let result = {}
|
||||
for (const key in params) {
|
||||
if (Object.prototype.hasOwnProperty.call(params, key)) {
|
||||
const value = params[key];
|
||||
if (key === 'terms') {
|
||||
result['terms[0].column:'] = 0
|
||||
result['terms[0].value'] = JSON.stringify(value[0])
|
||||
} else result[key] = value
|
||||
}
|
||||
}
|
||||
// const encodeParams = (params: Record<string, any>) => {
|
||||
// let result = {}
|
||||
// for (const key in params) {
|
||||
// if (Object.prototype.hasOwnProperty.call(params, key)) {
|
||||
// const value = params[key];
|
||||
// if (key === 'terms') {
|
||||
// result['terms[0].column:'] = 0
|
||||
// result['terms[0].value'] = JSON.stringify(value[0])
|
||||
// } else result[key] = value
|
||||
// }
|
||||
// }
|
||||
|
||||
return result
|
||||
};
|
||||
// return result
|
||||
// };
|
|
@ -20,3 +20,22 @@ export const getAlarmList_api = () => server.post(`/alarm/config/_query/no-pagin
|
|||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
paging: false,
|
||||
});
|
||||
|
||||
// 判断获取当前用户绑定信
|
||||
export const getIsBindThird = () => server.get(`/user/third-party/me`);
|
||||
|
||||
// 生成OAuth2授权URL
|
||||
export const getWechatOAuth2 = (configId: string, templateId: string, url: string) => server.get(`/notifier/wechat/corp/${configId}/${templateId}/oauth2/binding-user-url?redirectUri=${url}`);
|
||||
|
||||
export const getDingTalkOAuth2 = (configId: string, url: string) => server.get(`/notifier/dingtalk/corp/${configId}/oauth2/binding-user-url?authCode=${url}`);
|
||||
|
||||
// 获取oauth2授权的用户绑定码
|
||||
|
||||
export const getUserBind = (type: 'wechat' | 'dingtalk', params: any) => server.get(`/notifier/${type}/corp/oauth2/user-bind-code`, params);
|
||||
|
||||
// 根据绑定码绑定当前用户
|
||||
export const bindThirdParty = (type: string, provider: string, bindCode: string) => server.post(`/user/third-party/me/${type}/${provider}/${bindCode}/_bind`);
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -25,4 +25,5 @@ export default {
|
|||
// 短信获取签名
|
||||
getSigns: (id: any) => get(`/notifier/sms/aliyun/${id}/signs`),
|
||||
getListByConfigId: (id: string, data: any): any => post(`/notifier/template/${id}/_query`, data),
|
||||
getListVariableByConfigId: (id: string, data?: any): any => post(`/notifier/template/${id}/detail/_query`, data),
|
||||
}
|
|
@ -3,18 +3,15 @@
|
|||
<j-dropdown
|
||||
v-model:visible="visible"
|
||||
:trigger="['click']"
|
||||
:destroyPopupOnHide="true"
|
||||
@visible-change="visibleChange"
|
||||
>
|
||||
<!-- <div class="icon-content">
|
||||
<AIcon type="BellOutlined" style="font-size: 16px" />
|
||||
<span class="unread" v-show="total > 0">{{ total }}</span>
|
||||
</div> -->
|
||||
<j-badge :count="total" :offset="[3, -3]">
|
||||
<AIcon type="BellOutlined" style="font-size: 16px" />
|
||||
</j-badge>
|
||||
<template #overlay>
|
||||
<div>
|
||||
<NoticeInfo :data="list" @on-action="handleRead" />
|
||||
<NoticeInfo @action="handleRead" />
|
||||
</div>
|
||||
</template>
|
||||
</j-dropdown>
|
||||
|
@ -22,7 +19,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { getListByUnRead_api } from '@/api/account/notificationRecord';
|
||||
import { getList_api } from '@/api/account/notificationRecord';
|
||||
import NoticeInfo from './NoticeInfo.vue';
|
||||
import { getWebSocket } from '@/utils/websocket';
|
||||
import { notification, Button } from 'jetlinks-ui-components';
|
||||
|
@ -31,12 +28,13 @@ import { useUserInfo } from '@/store/userInfo';
|
|||
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
|
||||
const { jumpPage } = useMenuStore();
|
||||
const updateCount = computed(() => useUserInfo().$state.alarmUpdateCount);
|
||||
const updateCount = computed(() => useUserInfo().alarmUpdateCount);
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
const total = ref(0);
|
||||
const list = ref<any[]>([]);
|
||||
// const list = ref<any[]>([]);
|
||||
const loading = ref(false);
|
||||
const visible = ref(false);
|
||||
|
||||
const subscribeNotice = () => {
|
||||
getWebSocket('notification', '/notifications', {})
|
||||
|
@ -63,7 +61,6 @@ const subscribeNotice = () => {
|
|||
)
|
||||
,
|
||||
onClick: () => {
|
||||
// changeStatus_api('_read', [resp.id])
|
||||
read('', resp);
|
||||
},
|
||||
key: resp.payload.id,
|
||||
|
@ -93,15 +90,17 @@ const read = (type: string, data: any) => {
|
|||
notification.close(data.payload.id);
|
||||
getList();
|
||||
if (type !== '_read') {
|
||||
jumpPage('account/NotificationRecord', {
|
||||
menuStory.routerPush('account/center', {
|
||||
tabKey: 'StationMessage',
|
||||
row: data.payload.detail,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 查询未读数量
|
||||
const getList = () => {
|
||||
loading.value = true;
|
||||
loading.value = true;
|
||||
const params = {
|
||||
sorts: [{
|
||||
name: 'notifyTime',
|
||||
|
@ -120,25 +119,28 @@ const getList = () => {
|
|||
},
|
||||
],
|
||||
};
|
||||
getListByUnRead_api(params)
|
||||
getList_api(params)
|
||||
.then((resp: any) => {
|
||||
list.value = resp.result.data;
|
||||
total.value = resp.result.total;
|
||||
})
|
||||
.finally(() => (loading.value = false));
|
||||
};
|
||||
subscribeNotice();
|
||||
getList();
|
||||
watch(updateCount, () => getList());
|
||||
|
||||
const visibleChange = (bool: boolean) => {
|
||||
bool && getList();
|
||||
};
|
||||
|
||||
const visible = ref(false);
|
||||
const handleRead = () => {
|
||||
visible.value = false;
|
||||
getList();
|
||||
};
|
||||
|
||||
watch(updateCount, () => getList());
|
||||
|
||||
onMounted(() => {
|
||||
subscribeNotice();
|
||||
getList();
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -1,57 +1,138 @@
|
|||
<template>
|
||||
<div class="notice-info-container">
|
||||
<j-tabs :activeKey="'default'">
|
||||
<j-tab-pane key="default" tab="未读消息">
|
||||
<div class="no-data" v-if="props.data.length === 0">
|
||||
<img
|
||||
src="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else class="content">
|
||||
<j-scrollbar class="list" max-height="400">
|
||||
<div
|
||||
class="list-item"
|
||||
v-for="item in props.data"
|
||||
@click.stop="read(item.id)"
|
||||
>
|
||||
<h5>{{ item.topicName }}</h5>
|
||||
<p>{{ item.message }}</p>
|
||||
<j-tabs
|
||||
v-model:activeKey="activeKey"
|
||||
:destroyInactiveTabPane="true"
|
||||
@change="onChange"
|
||||
>
|
||||
<j-tab-pane v-for="item in tab" :key="item.key">
|
||||
<template #tab>
|
||||
<NoticeTab :refresh="refreshObj[item.key]" :tab="item?.tab" :type="item.type" />
|
||||
</template>
|
||||
<j-spin :spinning="loading">
|
||||
<div class="content">
|
||||
<j-scrollbar class="list" max-height="400" v-if="total">
|
||||
<template v-for="i in list" :key="i.id">
|
||||
<NoticeItem
|
||||
:data="i"
|
||||
@action="emits('action')"
|
||||
@refresh="onRefresh(item.key)"
|
||||
/>
|
||||
</template>
|
||||
</j-scrollbar>
|
||||
<div class="no-data" v-else>
|
||||
<j-empty />
|
||||
</div>
|
||||
<div class="btns">
|
||||
<span @click="onMore">查看更多</span>
|
||||
</div>
|
||||
</j-scrollbar>
|
||||
<div class="btns">
|
||||
<span @click="read()">当前标记为已读</span>
|
||||
<span @click="jumpPage('account/NotificationRecord')"
|
||||
>查看更多</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</j-spin>
|
||||
</j-tab-pane>
|
||||
</j-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { changeStatus_api } from '@/api/account/notificationRecord';
|
||||
import { getList_api } from '@/api/account/notificationRecord';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import NoticeItem from './NoticeItem.vue';
|
||||
import NoticeTab from './NoticeTab.vue';
|
||||
|
||||
const emits = defineEmits(['onAction']);
|
||||
const props = defineProps<{
|
||||
data: any[];
|
||||
}>();
|
||||
const { jumpPage } = useMenuStore();
|
||||
const emits = defineEmits(['action']);
|
||||
|
||||
const read = (id?: string) => {
|
||||
const ids = id ? [id] : props.data.map((item) => item.id);
|
||||
changeStatus_api('_read', ids).then((resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
jumpPage('account/NotificationRecord', {
|
||||
row: props.data.find((f: any) => f.id === id),
|
||||
});
|
||||
emits('onAction');
|
||||
}
|
||||
type DataType = 'alarm' | 'system-monitor' | 'system-business';
|
||||
|
||||
const tab = [
|
||||
{
|
||||
key: 'alarm',
|
||||
tab: '告警',
|
||||
type: [
|
||||
'alarm-product',
|
||||
'alarm-device',
|
||||
'alarm-other',
|
||||
'alarm-org',
|
||||
'alarm',
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'system-monitor',
|
||||
tab: '系统运维',
|
||||
type: ['system-event'],
|
||||
},
|
||||
{
|
||||
key: 'system-business',
|
||||
tab: '业务监控',
|
||||
type: ['device-transparent-codec'],
|
||||
},
|
||||
];
|
||||
|
||||
const refreshObj = ref({
|
||||
'alarm': true,
|
||||
'system-monitor': true,
|
||||
'system-business': true
|
||||
})
|
||||
|
||||
const loading = ref(false);
|
||||
const total = ref(0);
|
||||
const list = ref<any[]>([]);
|
||||
const activeKey = ref<DataType>('alarm');
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
const getData = (type: string[]) => {
|
||||
loading.value = true;
|
||||
const params = {
|
||||
sorts: [
|
||||
{
|
||||
name: 'notifyTime',
|
||||
order: 'desc',
|
||||
},
|
||||
],
|
||||
pageSize: 12,
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
type: 'or',
|
||||
value: type,
|
||||
termType: 'in',
|
||||
column: 'topicProvider',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
getList_api(params)
|
||||
.then((resp: any) => {
|
||||
total.value = resp.result.total;
|
||||
list.value = resp.result?.data || [];
|
||||
})
|
||||
.finally(() => (loading.value = false));
|
||||
};
|
||||
|
||||
const onChange = (_key: string) => {
|
||||
const type = tab.find((item) => item.key === _key)?.type || [];
|
||||
getData(type);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
onChange('alarm');
|
||||
});
|
||||
|
||||
const onRefresh = (id: string) => {
|
||||
const flag = cloneDeep(refreshObj.value[id])
|
||||
refreshObj.value = {
|
||||
...refreshObj.value,
|
||||
[id]: !flag
|
||||
}
|
||||
}
|
||||
|
||||
const onMore = () => {
|
||||
menuStory.routerPush('account/center', {
|
||||
tabKey: 'StationMessage',
|
||||
});
|
||||
emits('action')
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -89,26 +170,6 @@ const read = (id?: string) => {
|
|||
//隐藏或取消滚动条
|
||||
display: none;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
padding: 12px 24px;
|
||||
list-style: none;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
cursor: pointer;
|
||||
h5 {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
}
|
||||
p {
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #f0f5ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
.btns {
|
||||
display: flex;
|
||||
|
@ -116,13 +177,13 @@ const read = (id?: string) => {
|
|||
line-height: 46px;
|
||||
span {
|
||||
display: block;
|
||||
width: 50%;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
|
||||
&:first-child {
|
||||
border-right: 1px solid #f0f0f0;
|
||||
}
|
||||
// &:first-child {
|
||||
// border-right: 1px solid #f0f0f0;
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
<template>
|
||||
<div class="list-items">
|
||||
<div
|
||||
class="list-item"
|
||||
@click="onMove"
|
||||
:style="{
|
||||
transform: `translate(${num}px, 0)`,
|
||||
}"
|
||||
>
|
||||
<div class="list-item-left">
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
<div>{{ props.data?.topicName }}</div>
|
||||
<span :style="{color: state === 'unread' ? 'red' : '#AAAAAA'}">{{ state === 'unread' ? '未读' : '已读' }}</span>
|
||||
</div>
|
||||
<div class="time">
|
||||
{{
|
||||
dayjs(props.data?.notifyTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<p>{{ props.data?.message }}</p>
|
||||
</div>
|
||||
<div class="list-item-right">
|
||||
<j-button @click.stop="detail">查看详情</j-button>
|
||||
<j-button v-if="state === 'unread'" @click.stop="read('_read')">标为已读</j-button>
|
||||
<j-button v-else @click.stop="read('_unread')">标为未读</j-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import dayjs from 'dayjs';
|
||||
import { changeStatus_api } from '@/api/account/notificationRecord';
|
||||
import { useMenuStore } from '@/store/menu';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
import { onlyMessage } from '@/utils/comm';
|
||||
|
||||
const menuStory = useMenuStore();
|
||||
const route = useRoute();
|
||||
|
||||
const userInfo = useUserInfo();
|
||||
|
||||
const emits = defineEmits(['action', 'refresh']);
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
const num = ref<-100 | 0>(0);
|
||||
|
||||
const state = ref(props.data.state?.value)
|
||||
|
||||
watchEffect(() => {
|
||||
state.value = props.data.state?.value
|
||||
})
|
||||
|
||||
const onMove = () => {
|
||||
num.value = num.value === 0 ? -100 : 0;
|
||||
};
|
||||
|
||||
const detail = () => {
|
||||
// 判断当前是否为/account/center
|
||||
if (route.path === '/account/center') {
|
||||
userInfo.tabKey = 'StationMessage';
|
||||
userInfo.messageInfo = props.data;
|
||||
} else {
|
||||
menuStory.routerPush('account/center', {
|
||||
row: props.data,
|
||||
tabKey: 'StationMessage',
|
||||
});
|
||||
}
|
||||
emits('action');
|
||||
};
|
||||
|
||||
const read = (type: '_read' | '_unread') => {
|
||||
changeStatus_api(type, [props.data.id]).then((resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
if(type === '_read') {
|
||||
userInfo.alarmUpdateCount -= 1;
|
||||
} else {
|
||||
userInfo.alarmUpdateCount += 1;
|
||||
}
|
||||
num.value = 0;
|
||||
state.value = type === '_read' ? 'read' : 'unread'
|
||||
onlyMessage('操作成功!');
|
||||
emits('refresh')
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.list-items {
|
||||
width: 312px;
|
||||
overflow: hidden;
|
||||
height: 100px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
margin: 0 24px;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
.list-item {
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
width: 412px;
|
||||
transition: all 0.3s;
|
||||
gap: 24px;
|
||||
|
||||
.list-item-left {
|
||||
padding: 12px 0;
|
||||
width: 312px;
|
||||
height: 100px;
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
div {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 10px;
|
||||
}
|
||||
span {
|
||||
color: red;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
.time {
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.list-item-right {
|
||||
width: 100px;
|
||||
padding: 12px 12px 12px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,69 @@
|
|||
<template>
|
||||
<j-badge :count="total" :offset="[3, -3]">
|
||||
{{ tab }}
|
||||
</j-badge>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getList_api } from '@/api/account/notificationRecord';
|
||||
import { PropType } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
tab: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
},
|
||||
refresh: {
|
||||
type: Boolean
|
||||
}
|
||||
});
|
||||
|
||||
const total = ref<number>(0);
|
||||
|
||||
const getData = (type: string[]) => {
|
||||
const params = {
|
||||
sorts: [
|
||||
{
|
||||
name: 'notifyTime',
|
||||
order: 'desc',
|
||||
},
|
||||
],
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
type: 'and',
|
||||
value: type,
|
||||
termType: 'in',
|
||||
column: 'topicProvider',
|
||||
},
|
||||
{
|
||||
type: 'and',
|
||||
value: 'unread',
|
||||
termType: 'eq',
|
||||
column: 'state',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
getList_api(params).then((resp: any) => {
|
||||
total.value = resp.result.total;
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.refresh,
|
||||
() => {
|
||||
getData(props.type);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
</script>
|
|
@ -2,12 +2,13 @@
|
|||
<div>
|
||||
<j-dropdown placement="bottomRight">
|
||||
<div style="cursor: pointer;height: 100%;">
|
||||
<img
|
||||
:src="userInfo.avatar"
|
||||
<j-avatar
|
||||
:src="userInfo.userInfos?.avatar"
|
||||
alt=""
|
||||
style="width: 24px; margin-right: 12px"
|
||||
:size="24"
|
||||
style="margin-right: 12px"
|
||||
/>
|
||||
<span>{{ userInfo.name }}</span>
|
||||
<span>{{ userInfo.userInfos?.name }}</span>
|
||||
</div>
|
||||
<template #overlay>
|
||||
<j-menu>
|
||||
|
@ -32,8 +33,7 @@ import { LoginPath } from '@/router/menu'
|
|||
|
||||
const {push} = useRouter();
|
||||
|
||||
const userInfo = useUserInfo().$state.userInfos as any;
|
||||
|
||||
const userInfo = useUserInfo() as any;
|
||||
|
||||
const logOut = () => {
|
||||
loginout_api().then(() => {
|
||||
|
|
|
@ -90,6 +90,12 @@ export const useMenuStore = defineStore({
|
|||
console.warn(`没有找到对应的页面: ${name}`)
|
||||
}
|
||||
},
|
||||
routerPush(name: string, params?: Record<string, any>, query?: Record<string, any>) {
|
||||
this.params = { [name]: params || {}}
|
||||
router.push({
|
||||
name, params, query, state: { params }
|
||||
})
|
||||
},
|
||||
queryMenuTree(isCommunity = false): Promise<any[]> {
|
||||
return new Promise(async (res) => {
|
||||
//过滤非集成的菜单
|
||||
|
|
|
@ -23,9 +23,12 @@ export const useUserInfo = defineStore('userInfo', {
|
|||
orgList: [],
|
||||
roleList: [],
|
||||
telephone: '',
|
||||
email: ''
|
||||
email: '',
|
||||
avatar: ''
|
||||
},
|
||||
alarmUpdateCount: 0
|
||||
alarmUpdateCount: 0,
|
||||
tabKey: 'HomeView', // 个人中心的tabKey,
|
||||
messageInfo: {}, // 站内信的row
|
||||
}),
|
||||
|
||||
actions: {
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<j-empty style="margin: 200px 0;" />
|
||||
<j-empty v-else style="margin: 200px 0;" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
<script setup lang="ts">
|
||||
import { updateMeInfo_api } from '@/api/account/center';
|
||||
import { onlyMessage } from '@/utils/comm';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
const emits = defineEmits(['save', 'close']);
|
||||
const props = defineProps({
|
||||
|
@ -110,9 +111,10 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
const loading = ref(false);
|
||||
const form = ref(props.data);
|
||||
const form = ref<any>(cloneDeep(props.data));
|
||||
const formRef = ref<any>();
|
||||
|
||||
|
||||
const handleOk = () => {
|
||||
formRef.value?.validate().then(() => {
|
||||
loading.value = true;
|
||||
|
|
|
@ -7,47 +7,71 @@
|
|||
@cancel="emits('update:visible', false)"
|
||||
class="view-dialog-container"
|
||||
>
|
||||
<j-row v-if="data?.targetType === 'device'">
|
||||
<j-col :span="4" class="label">告警设备</j-col>
|
||||
<j-col :span="8" class="value">
|
||||
{{ data?.targetName || '' }}
|
||||
</j-col>
|
||||
<j-col :span="4" class="label">设备ID</j-col>
|
||||
<j-col :span="8" class="value">
|
||||
{{ data?.targetId || '' }}
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-row>
|
||||
<j-col :span="4" class="label">告警名称</j-col>
|
||||
<j-col :span="8" class="value">
|
||||
{{ data?.alarmName || data?.alarmConfigName || '' }}
|
||||
</j-col>
|
||||
<j-col :span="4" class="label">告警时间</j-col>
|
||||
<j-col :span="8" class="value">
|
||||
{{ moment(data?.alarmTime).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
</j-col>
|
||||
<template v-if="type === 'alarm'">
|
||||
<j-row v-if="data?.topicProvider === 'alarm-device'">
|
||||
<j-col :span="4" class="label">告警设备</j-col>
|
||||
<j-col :span="8" class="value">
|
||||
{{ data?.targetName || '' }}
|
||||
</j-col>
|
||||
<j-col :span="4" class="label">设备ID</j-col>
|
||||
<j-col :span="8" class="value">
|
||||
{{ data?.targetId || '' }}
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-row>
|
||||
<j-col :span="4" class="label">告警名称</j-col>
|
||||
<j-col :span="8" class="value">
|
||||
{{ data?.alarmName || data?.alarmConfigName || '' }}
|
||||
</j-col>
|
||||
<j-col :span="4" class="label">告警时间</j-col>
|
||||
<j-col :span="8" class="value">
|
||||
{{ dayjs(data?.alarmTime).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
</j-col>
|
||||
|
||||
<j-col :span="4" class="label">告警级别</j-col>
|
||||
<j-col :span="8" class="value">
|
||||
{{ (levelList.length > 0 && getLevelLabel(data.level)) || '' }}
|
||||
</j-col>
|
||||
<j-col :span="4" class="label">告警说明</j-col>
|
||||
<j-col :span="8" class="value">{{ data?.description || '' }}</j-col>
|
||||
<j-col :span="4" class="label">告警级别</j-col>
|
||||
<j-col :span="8" class="value">
|
||||
{{
|
||||
(levelList.length > 0 && getLevelLabel(data.level)) ||
|
||||
''
|
||||
}}
|
||||
</j-col>
|
||||
<j-col :span="4" class="label">告警说明</j-col>
|
||||
<j-col :span="8" class="value">{{
|
||||
data?.description || ''
|
||||
}}</j-col>
|
||||
|
||||
<j-col
|
||||
:span="4"
|
||||
class="label"
|
||||
style="display: flex; height: 440px; align-items: center"
|
||||
>告警流水</j-col
|
||||
>
|
||||
<j-col
|
||||
:span="20"
|
||||
class="value"
|
||||
style="max-height: 440px; overflow: auto"
|
||||
>
|
||||
<JsonViewer :value="JSON.parse(data?.alarmInfo || '{}')" />
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-col
|
||||
:span="4"
|
||||
class="label"
|
||||
style="display: flex; height: 440px; align-items: center"
|
||||
>告警流水</j-col
|
||||
>
|
||||
<j-col
|
||||
:span="20"
|
||||
class="value"
|
||||
style="max-height: 440px; overflow: auto"
|
||||
>
|
||||
<JsonViewer :value="JSON.parse(data?.alarmInfo || '{}')" />
|
||||
</j-col>
|
||||
</j-row>
|
||||
</template>
|
||||
<template v-else>
|
||||
<j-row>
|
||||
<j-col
|
||||
:span="4"
|
||||
class="label"
|
||||
style="display: flex; height: 440px; align-items: center"
|
||||
>通知流水</j-col
|
||||
>
|
||||
<j-col
|
||||
:span="20"
|
||||
class="value"
|
||||
style="max-height: 440px; overflow: auto"
|
||||
>
|
||||
<JsonViewer :value="JSON.parse(data?.alarmInfo || '{}')" />
|
||||
</j-col>
|
||||
</j-row>
|
||||
</template>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
|
@ -55,14 +79,17 @@
|
|||
import { JsonViewer } from 'vue3-json-viewer';
|
||||
import 'vue3-json-viewer/dist/index.css';
|
||||
import { queryLevel as queryLevel_api } from '@/api/rule-engine/config';
|
||||
import moment from 'moment';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const emits = defineEmits(['update:visible']);
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
data: any;
|
||||
type: string;
|
||||
}>();
|
||||
|
||||
const levelList = ref<any[]>([]);
|
||||
|
||||
const data = computed(() => {
|
||||
if (props.data.detailJson) return JSON.parse(props.data.detailJson);
|
||||
else return props.data?.detail || props.data;
|
||||
|
|
|
@ -14,12 +14,8 @@
|
|||
:request="getList_api"
|
||||
model="TABLE"
|
||||
:params="queryParams"
|
||||
:bodyStyle="{padding: 0}"
|
||||
:defaultParams="{
|
||||
sorts: [{
|
||||
name: 'notifyTime', order: 'desc'
|
||||
}]
|
||||
}"
|
||||
:bodyStyle="{ padding: 0 }"
|
||||
:defaultParams="defaultParams"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<j-button type="primary">全部已读</j-button>
|
||||
|
@ -29,7 +25,7 @@
|
|||
</template>
|
||||
<template #notifyTime="slotProps">
|
||||
{{
|
||||
moment(slotProps.notifyTime).format(
|
||||
dayjs(slotProps.notifyTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)
|
||||
}}
|
||||
|
@ -54,7 +50,7 @@
|
|||
? '未读'
|
||||
: '已读'
|
||||
}`,
|
||||
onConfirm: () => table.changeStatus(slotProps),
|
||||
onConfirm: () => changeStatus(slotProps),
|
||||
}"
|
||||
:tooltip="{
|
||||
title:
|
||||
|
@ -70,18 +66,18 @@
|
|||
:tooltip="{
|
||||
title: '查看',
|
||||
}"
|
||||
@click="table.view(slotProps)"
|
||||
@click="view(slotProps)"
|
||||
>
|
||||
<AIcon type="SearchOutlined" />
|
||||
</PermissionButton>
|
||||
</j-space>
|
||||
</template>
|
||||
</j-pro-table>
|
||||
|
||||
<ViewDialog
|
||||
v-if="viewVisible"
|
||||
v-model:visible="viewVisible"
|
||||
:data="viewItem"
|
||||
:type="type"
|
||||
/>
|
||||
</div>
|
||||
</page-container>
|
||||
|
@ -94,16 +90,37 @@ import {
|
|||
getList_api,
|
||||
changeStatus_api,
|
||||
} from '@/api/account/notificationRecord';
|
||||
import { getTypeList_api } from '@/api/account/notificationSubscription';
|
||||
import { optionItem } from '@/views/rule-engine/Scene/typings';
|
||||
import { dictItemType } from '@/views/system/DataSource/typing';
|
||||
import moment from 'moment';
|
||||
import dayjs from 'dayjs';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
import { useRouterParams } from '@/utils/hooks/useParams';
|
||||
import dayjs from 'dayjs'
|
||||
import { getTypeList_api } from '@/api/account/notificationSubscription';
|
||||
|
||||
const user = useUserInfo();
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const getType = computed(() => {
|
||||
if (props.type === 'system-business') {
|
||||
return ['device-transparent-codec'];
|
||||
} else if (props.type === 'system-monitor') {
|
||||
return ['system-event'];
|
||||
} else {
|
||||
return [
|
||||
'alarm-product',
|
||||
'alarm-device',
|
||||
'alarm-other',
|
||||
'alarm-org',
|
||||
'alarm',
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
const { updateAlarm } = useUserInfo();
|
||||
const columns = [
|
||||
{
|
||||
title: '类型',
|
||||
|
@ -114,11 +131,13 @@ const columns = [
|
|||
options: () =>
|
||||
getTypeList_api().then((resp: any) =>
|
||||
resp.result
|
||||
.map((item: dictItemType) => ({
|
||||
.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))
|
||||
.filter((item: optionItem) => item.value === 'alarm'),
|
||||
.filter((item: any) =>
|
||||
[...getType.value].includes(item?.value),
|
||||
),
|
||||
),
|
||||
},
|
||||
scopedSlots: true,
|
||||
|
@ -139,7 +158,7 @@ const columns = [
|
|||
dataIndex: 'notifyTime',
|
||||
key: 'notifyTime',
|
||||
search: {
|
||||
type: 'date'
|
||||
type: 'date',
|
||||
},
|
||||
scopedSlots: true,
|
||||
ellipsis: true,
|
||||
|
@ -173,37 +192,59 @@ const columns = [
|
|||
width: '200px',
|
||||
},
|
||||
];
|
||||
const queryParams = ref({});
|
||||
|
||||
const tableRef = ref();
|
||||
const table = {
|
||||
changeStatus: (row: any) => {
|
||||
const type = row.state.value === 'read' ? '_unread' : '_read';
|
||||
changeStatus_api(type, [row.id]).then((resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
table.refresh();
|
||||
updateAlarm();
|
||||
}
|
||||
});
|
||||
},
|
||||
view: (row: any) => {
|
||||
console.log('row: ', row);
|
||||
viewItem.value = row;
|
||||
viewVisible.value = true;
|
||||
},
|
||||
refresh: () => {
|
||||
tableRef.value && tableRef.value.reload();
|
||||
},
|
||||
};
|
||||
|
||||
const viewVisible = ref<boolean>(false);
|
||||
const viewItem = ref<any>({});
|
||||
|
||||
const routerParams = useRouterParams();
|
||||
|
||||
const defaultParams = {
|
||||
sorts: [{ name: 'notifyTime', order: 'desc' }],
|
||||
terms: [
|
||||
{
|
||||
terms: [
|
||||
{
|
||||
column: 'topicProvider',
|
||||
value: getType.value,
|
||||
termType: 'in',
|
||||
},
|
||||
],
|
||||
type: 'and',
|
||||
},
|
||||
],
|
||||
};
|
||||
const queryParams = ref({});
|
||||
|
||||
const tableRef = ref();
|
||||
|
||||
const view = (row: any) => {
|
||||
viewItem.value = row;
|
||||
viewVisible.value = true;
|
||||
};
|
||||
const refresh = () => {
|
||||
tableRef.value && tableRef.value.reload();
|
||||
};
|
||||
|
||||
const changeStatus = (row: any) => {
|
||||
const type = row.state.value === 'read' ? '_unread' : '_read';
|
||||
changeStatus_api(type, [row.id]).then((resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
refresh();
|
||||
user.updateAlarm();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
if(user.messageInfo?.id) {
|
||||
view(user.messageInfo)
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (routerParams.params?.value.row) {
|
||||
table.view(routerParams.params?.value.row);
|
||||
view(routerParams.params?.value.row);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,155 +0,0 @@
|
|||
<template>
|
||||
<j-modal
|
||||
visible
|
||||
:title="props.data.id ? '编辑' : '新增'"
|
||||
width="865px"
|
||||
:confirmLoading="loading"
|
||||
@ok="confirm"
|
||||
@cancel="emits('update:visible', false)"
|
||||
>
|
||||
<j-form :model="form" layout="vertical" ref="formRef">
|
||||
<j-form-item
|
||||
label="名称"
|
||||
name="subscribeName"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入名称' },
|
||||
{
|
||||
max: 64,
|
||||
message: '最多可输入64个字符',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<j-input
|
||||
v-model:value="form.subscribeName"
|
||||
placeholder="请输入名称"
|
||||
/>
|
||||
</j-form-item>
|
||||
|
||||
<j-row :gutter="24">
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
label="类型"
|
||||
name="topicProvider"
|
||||
:rules="[{ required: true, message: '请选择类型' }]"
|
||||
>
|
||||
<j-select
|
||||
v-model:value="form.topicProvider"
|
||||
placeholder="请选择类型"
|
||||
:options="typeList"
|
||||
/>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
<j-col :span="12">
|
||||
<j-form-item
|
||||
label="告警规则"
|
||||
:name="['topicConfig', 'alarmConfigId']"
|
||||
:rules="[{ required: true, message: '请选择告警规则' }]"
|
||||
>
|
||||
<j-select
|
||||
:value="form.topicConfig?.alarmConfigId?.split(',')"
|
||||
:options="alarmList"
|
||||
placeholder="请选择告警规则"
|
||||
mode="multiple"
|
||||
@change="onSelect"
|
||||
></j-select>
|
||||
</j-form-item>
|
||||
</j-col>
|
||||
</j-row>
|
||||
<j-form-item
|
||||
name="notice"
|
||||
label="通知方式"
|
||||
:rules="[{ required: true, message: '请选择通知方式' }]"
|
||||
>
|
||||
<j-checkbox-group
|
||||
v-model:value="form.notice"
|
||||
name="checkboxgroup"
|
||||
:options="[
|
||||
{
|
||||
label: '站内通知',
|
||||
value: 1,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</j-form-item>
|
||||
</j-form>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { rowType } from '../typing';
|
||||
import {
|
||||
getTypeList_api,
|
||||
getAlarmList_api,
|
||||
save_api,
|
||||
} from '@/api/account/notificationSubscription';
|
||||
import { optionsType } from '@/views/system/Department/typing';
|
||||
import { dictItemType } from '@/views/system/DataSource/typing';
|
||||
import { optionItem } from '@/views/rule-engine/Scene/typings';
|
||||
import { FormInstance, message } from 'ant-design-vue';
|
||||
|
||||
const emits = defineEmits(['ok', 'update:visible']);
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
data: rowType;
|
||||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const initForm = {
|
||||
subscribeName: '',
|
||||
topicConfig: {},
|
||||
notice: [1],
|
||||
};
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
const form = ref({
|
||||
...initForm,
|
||||
...props.data,
|
||||
});
|
||||
const confirm = () => {
|
||||
formRef.value &&
|
||||
formRef.value.validate().then(() => {
|
||||
loading.value = true;
|
||||
save_api(form.value)
|
||||
.then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功');
|
||||
emits('ok');
|
||||
emits('update:visible', false);
|
||||
}
|
||||
})
|
||||
.finally(() => (loading.value = false));
|
||||
});
|
||||
};
|
||||
|
||||
const typeList = ref<optionsType>([]);
|
||||
const alarmList = ref<optionsType>([]);
|
||||
|
||||
init();
|
||||
function init() {
|
||||
getTypeList_api().then((resp: any) => {
|
||||
if (resp.status === 200)
|
||||
typeList.value = resp.result
|
||||
.map((item: dictItemType) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}))
|
||||
.filter((item: optionItem) => item.value === 'alarm');
|
||||
});
|
||||
getAlarmList_api().then((resp: any) => {
|
||||
if (resp.status === 200)
|
||||
alarmList.value = resp.result.map((item: dictItemType) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
function onSelect(keys: string[], items: optionsType) {
|
||||
form.value.topicConfig = {
|
||||
alarmConfigId: keys.length ? keys.join(',') : undefined,
|
||||
alarmConfigName: items.length ? items.map((item) => item.label).join(',') : undefined,
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -1,203 +0,0 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<div class="notification-subscription-container">
|
||||
<pro-search
|
||||
:columns="columns"
|
||||
target="category"
|
||||
@search="(params:any)=>queryParams = {...params}"
|
||||
/>
|
||||
<j-pro-table
|
||||
ref="tableRef"
|
||||
:columns="columns"
|
||||
:request="getNoticeList_api"
|
||||
model="TABLE"
|
||||
:params="queryParams"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'notifyTime', order: 'desc' }],
|
||||
}"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<PermissionButton type="primary" @click="table.edit()">
|
||||
<AIcon type="PlusOutlined" />新增
|
||||
</PermissionButton>
|
||||
</template>
|
||||
|
||||
<template #alarmConfigName="slotProps">
|
||||
{{ slotProps.topicConfig.alarmConfigName }}
|
||||
</template>
|
||||
<template #state="slotProps">
|
||||
<BadgeStatus
|
||||
:status="slotProps.state.value"
|
||||
:text="slotProps.state.text"
|
||||
:statusNames="{
|
||||
enabled: 'success',
|
||||
disabled: 'error',
|
||||
}"
|
||||
></BadgeStatus>
|
||||
</template>
|
||||
|
||||
<template #action="slotProps">
|
||||
<j-space :size="16">
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title: '编辑',
|
||||
}"
|
||||
@click="table.edit(slotProps)"
|
||||
>
|
||||
<AIcon type="EditOutlined" />
|
||||
</PermissionButton>
|
||||
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:popConfirm="{
|
||||
title: `确定${
|
||||
slotProps.state.value === 'enabled'
|
||||
? '禁用'
|
||||
: '启用'
|
||||
}`,
|
||||
onConfirm: () => table.changeStatus(slotProps),
|
||||
}"
|
||||
:tooltip="{
|
||||
title:
|
||||
slotProps.state.value === 'enabled'
|
||||
? '禁用'
|
||||
: '启用',
|
||||
}"
|
||||
>
|
||||
<AIcon
|
||||
:type="
|
||||
slotProps.state.value === 'enabled'
|
||||
? 'StopOutlined'
|
||||
: 'PlayCircleOutlined'
|
||||
"
|
||||
/>
|
||||
</PermissionButton>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:tooltip="{
|
||||
title:
|
||||
slotProps.state.value === 'enabled'
|
||||
? '请先禁用,再删除'
|
||||
: '删除',
|
||||
}"
|
||||
:popConfirm="{
|
||||
title: `确认删除?`,
|
||||
onConfirm: () => table.delete(slotProps),
|
||||
}"
|
||||
:disabled="slotProps.state.value === 'enabled'"
|
||||
>
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</PermissionButton>
|
||||
</j-space>
|
||||
</template>
|
||||
</j-pro-table>
|
||||
|
||||
<EditDialog
|
||||
v-if="dialogVisible"
|
||||
v-model:visible="dialogVisible"
|
||||
:data="table.seletctRow"
|
||||
@ok="table.refresh"
|
||||
/>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="NotificationSubscription">
|
||||
import PermissionButton from '@/components/PermissionButton/index.vue';
|
||||
import EditDialog from './components/EditDialog.vue';
|
||||
import {
|
||||
getNoticeList_api,
|
||||
changeStatus_api,
|
||||
remove_api,
|
||||
} from '@/api/account/notificationSubscription';
|
||||
import { rowType } from './typing';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'subscribeName',
|
||||
key: 'subscribeName',
|
||||
ellipsis: true,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'topicName',
|
||||
key: 'topicName',
|
||||
scopedSlots: true,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '告警规则',
|
||||
dataIndex: 'alarmConfigName',
|
||||
key: 'alarmConfigName',
|
||||
scopedSlots: true,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
ellipsis: true,
|
||||
scopedSlots: true,
|
||||
width: '200px',
|
||||
},
|
||||
];
|
||||
const queryParams = ref({});
|
||||
const dialogVisible = ref<boolean>(false);
|
||||
const tableRef = ref();
|
||||
const table = {
|
||||
seletctRow: ref<any>({}),
|
||||
edit: (row?: rowType) => {
|
||||
table.seletctRow = {
|
||||
...(row || ({} as any)),
|
||||
};
|
||||
dialogVisible.value = true;
|
||||
},
|
||||
changeStatus: (row: rowType) => {
|
||||
const status = row.state.value === 'enabled' ? '_disabled' : '_enabled';
|
||||
changeStatus_api(row.id as string, status).then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
table.refresh();
|
||||
} else message.warning('操作失败!');
|
||||
});
|
||||
},
|
||||
delete: (row: rowType) => {
|
||||
remove_api(row.id as string).then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
table.refresh();
|
||||
} else message.warning('操作失败!');
|
||||
});
|
||||
},
|
||||
refresh: () => {
|
||||
tableRef.value && tableRef.value.reload();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.notification-subscription-container {
|
||||
:deep(.ant-table-tbody) {
|
||||
.ant-table-cell {
|
||||
.ant-space-item {
|
||||
.ant-btn-link {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,14 +0,0 @@
|
|||
export type rowType = {
|
||||
id?: string;
|
||||
locale: string;
|
||||
state: { text: string, value: "enabled" | 'disabled' };
|
||||
subscribeName: string;
|
||||
subscriber: string;
|
||||
subscriberType: string;
|
||||
topicConfig: { alarmConfigId?: string, alarmConfigName?: string };
|
||||
alarmConfigId: string;
|
||||
alarmConfigName: stirng;
|
||||
topicName: string;
|
||||
topicProvider: string| undefined;
|
||||
notice?:any[]
|
||||
}
|
|
@ -1,29 +1,59 @@
|
|||
<template>
|
||||
<div style="margin-top: 24px;">
|
||||
<j-tabs tab-position="left" v-if="tabs.length">
|
||||
<j-tab-pane v-for="item in tabs" :key="item.key" :tab="item.tab">
|
||||
<NotificationRecord />
|
||||
<div style="margin-top: 24px">
|
||||
<j-tabs
|
||||
tab-position="left"
|
||||
v-if="tabs.length"
|
||||
:destroyInactiveTabPane="true"
|
||||
>
|
||||
<j-tab-pane v-for="item in tabs" :key="item.provider" :tab="item.name">
|
||||
<NotificationRecord :type="item.provider" />
|
||||
</j-tab-pane>
|
||||
</j-tabs>
|
||||
<j-empty v-else />
|
||||
<j-empty v-else style="margin: 200px 0" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import NotificationRecord from './components/NotificationRecord/index.vue'
|
||||
import NotificationRecord from './components/NotificationRecord/index.vue';
|
||||
import { initData } from '../data';
|
||||
import { getAllNotice } from '@/api/account/center';
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
key: '1',
|
||||
tab: '告警'
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
tab: '系统运维'
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
tab: '业务监控'
|
||||
}
|
||||
]
|
||||
const tabs = ref<any[]>([]);
|
||||
|
||||
const queryTypeList = () => {
|
||||
getAllNotice().then((resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
const arr = initData
|
||||
.map((item: any) => {
|
||||
const _child = item.children.map((i: any) => {
|
||||
const _item = (resp.result || []).find(
|
||||
(t: any) => t?.provider === i?.provider,
|
||||
);
|
||||
return {
|
||||
...i,
|
||||
..._item,
|
||||
};
|
||||
});
|
||||
return {
|
||||
...item,
|
||||
children: _child,
|
||||
};
|
||||
})
|
||||
.filter((it: any) => {
|
||||
return it.children.filter((lt: any) => lt?.id)?.length;
|
||||
})
|
||||
.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
children: item.children.filter((lt: any) => lt?.id),
|
||||
};
|
||||
});
|
||||
tabs.value = arr
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
queryTypeList();
|
||||
});
|
||||
</script>
|
|
@ -18,49 +18,85 @@
|
|||
<template #card="slotProps">
|
||||
<div class="box-item">
|
||||
<div class="box-item-img">
|
||||
<j-popover
|
||||
trigger="click"
|
||||
:visible="show?.[slotProps?.id]"
|
||||
@visibleChange="onVisibleChange(slotProps)"
|
||||
>
|
||||
<img
|
||||
:src="
|
||||
getImage(
|
||||
`/notice/${noticeType.get(
|
||||
<j-dropdown placement="top" :trigger="['click']">
|
||||
<!-- :visible="show?.[slotProps?.id]"
|
||||
@visibleChange="onVisibleChange(slotProps)" -->
|
||||
<div>
|
||||
<img
|
||||
:src="
|
||||
iconMap.get(
|
||||
slotProps?.channelProvider,
|
||||
)}.png`,
|
||||
)
|
||||
"
|
||||
/>
|
||||
<template
|
||||
#content
|
||||
v-if="
|
||||
)
|
||||
"
|
||||
/>
|
||||
<div
|
||||
:class="{
|
||||
disabled: !notifyChannels?.includes(
|
||||
slotProps?.id,
|
||||
),
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<!-- v-if="
|
||||
notifyChannels?.includes(
|
||||
slotProps?.id,
|
||||
) &&
|
||||
slotProps?.channelProvider !==
|
||||
'inside-mail'
|
||||
"
|
||||
>
|
||||
<div>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:hasPermission="true"
|
||||
@click="onUnSubscribe(slotProps)"
|
||||
" -->
|
||||
<template #overlay>
|
||||
<j-menu>
|
||||
<j-menu-item
|
||||
v-if="
|
||||
!notifyChannels?.includes(
|
||||
slotProps?.id,
|
||||
)
|
||||
"
|
||||
>
|
||||
取消订阅
|
||||
</PermissionButton>
|
||||
</div>
|
||||
<div>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:hasPermission="true"
|
||||
>
|
||||
更换接收账号
|
||||
</PermissionButton>
|
||||
</div>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:hasPermission="true"
|
||||
@click="
|
||||
onCheckChange(slotProps)
|
||||
"
|
||||
>
|
||||
订阅
|
||||
</PermissionButton>
|
||||
</j-menu-item>
|
||||
<template v-else>
|
||||
<j-menu-item>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:hasPermission="true"
|
||||
@click="
|
||||
onUnSubscribe(slotProps)
|
||||
"
|
||||
>
|
||||
取消订阅
|
||||
</PermissionButton>
|
||||
</j-menu-item>
|
||||
<j-menu-item
|
||||
v-if="
|
||||
slotProps.channelProvider !==
|
||||
'inside-mail'
|
||||
"
|
||||
>
|
||||
<PermissionButton
|
||||
type="link"
|
||||
:hasPermission="true"
|
||||
@click="
|
||||
onAccountChange(
|
||||
slotProps,
|
||||
)
|
||||
"
|
||||
>
|
||||
更换接收账号
|
||||
</PermissionButton>
|
||||
</j-menu-item>
|
||||
</template>
|
||||
</j-menu>
|
||||
</template>
|
||||
</j-popover>
|
||||
</j-dropdown>
|
||||
<div class="box-item-checked">
|
||||
<j-checkbox
|
||||
:checked="
|
||||
|
@ -68,13 +104,6 @@
|
|||
"
|
||||
></j-checkbox>
|
||||
</div>
|
||||
<div
|
||||
:class="{
|
||||
disabled: !notifyChannels?.includes(
|
||||
slotProps?.id,
|
||||
),
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<div class="box-item-text">
|
||||
{{ slotProps?.name }}
|
||||
|
@ -91,26 +120,36 @@
|
|||
:current="current"
|
||||
@close="visible = false"
|
||||
/>
|
||||
<EditInfo
|
||||
v-if="editInfoVisible"
|
||||
:data="user.userInfos"
|
||||
@close="editInfoVisible = false"
|
||||
@save="onSave"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getImage, onlyMessage } from '@/utils/comm';
|
||||
import MCarousel from '@/components/MCarousel/index.vue';
|
||||
import Unsubscribe from './Unsubscribe.vue';
|
||||
import { remove_api, save_api } from '@/api/account/notificationSubscription';
|
||||
import {
|
||||
getIsBindThird,
|
||||
save_api,
|
||||
} from '@/api/account/notificationSubscription';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
import EditInfo from '../../EditInfo/index.vue';
|
||||
|
||||
const noticeType = new Map();
|
||||
noticeType.set('notifier-dingTalk', 'dingtalk');
|
||||
noticeType.set('notifier-weixin', 'wechat');
|
||||
noticeType.set('notifier-email', 'email');
|
||||
noticeType.set('notifier-voice', 'voice');
|
||||
noticeType.set('notifier-sms', 'sms');
|
||||
noticeType.set('inside-mail', 'inside-mail');
|
||||
const iconMap = new Map();
|
||||
iconMap.set('notifier-dingTalk', getImage('/notice/dingtalk.png'));
|
||||
iconMap.set('notifier-weixin', getImage('/notice/wechat.png'));
|
||||
iconMap.set('notifier-email', getImage('/notice/email.png'));
|
||||
iconMap.set('notifier-voice', getImage('/notice/voice.png'));
|
||||
iconMap.set('notifier-sms', getImage('/notice/sms.png'));
|
||||
iconMap.set('inside-mail', getImage('/notice/inside-mail.png'));
|
||||
|
||||
const current = ref<any>({});
|
||||
const visible = ref<boolean>(false);
|
||||
const show = ref<any>()
|
||||
const editInfoVisible = ref<boolean>(false);
|
||||
|
||||
const user = useUserInfo();
|
||||
|
||||
|
@ -154,8 +193,8 @@ const onUnSubscribe = async (obj: any) => {
|
|||
// onlyMessage('操作成功');
|
||||
// emits('refresh');
|
||||
// }
|
||||
const _set = new Set(props.subscribe?.notifyChannels || [])
|
||||
_set.delete(obj?.id)
|
||||
const _set = new Set(props.subscribe?.notifyChannels || []);
|
||||
_set.delete(obj?.id);
|
||||
const _obj = {
|
||||
subscribeName: obj.name,
|
||||
topicProvider: props.data?.provider,
|
||||
|
@ -188,6 +227,18 @@ const onCheckChange = async (_data: any) => {
|
|||
}
|
||||
} else {
|
||||
// 钉钉和微信
|
||||
const resp: any = await getIsBindThird();
|
||||
if (resp.status === 200) {
|
||||
const _item = (resp?.result || []).find((item: any) => {
|
||||
return (
|
||||
_data?.channelConfiguration?.notifierId ===
|
||||
item?.provider
|
||||
);
|
||||
});
|
||||
if (_item) {
|
||||
_bind = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_data?.channelProvider === 'inside-mail' || _bind) {
|
||||
|
@ -198,18 +249,25 @@ const onCheckChange = async (_data: any) => {
|
|||
}
|
||||
};
|
||||
|
||||
const onVisibleChange = (_data: any) => {
|
||||
show.value = {}
|
||||
if (notifyChannels.value?.includes(_data?.id)) {
|
||||
if (_data?.channelProvider === 'inside-mail') {
|
||||
onUnSubscribe(_data);
|
||||
} else {
|
||||
show.value[_data.id] = true
|
||||
}
|
||||
// 更换绑定账号
|
||||
const onAccountChange = (_data: any) => {
|
||||
current.value = _data;
|
||||
if (
|
||||
['notifier-voice', 'notifier-sms', 'notifier-email'].includes(
|
||||
_data?.channelProvider,
|
||||
)
|
||||
) {
|
||||
editInfoVisible.value = true;
|
||||
} else {
|
||||
onCheckChange(_data)
|
||||
visible.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
editInfoVisible.value = false;
|
||||
// 重新绑定
|
||||
onUnSubscribe(current.value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -1,10 +1,34 @@
|
|||
<template>
|
||||
<j-modal visible @cancel="emit('close')">
|
||||
<j-modal width="900px" visible @cancel="emit('close')">
|
||||
<template v-if="getType === 'notifier-dingTalk'">
|
||||
<div class="tip">请先绑定钉钉账号</div>
|
||||
<!-- <div class="tip">请先绑定钉钉账号</div> -->
|
||||
<j-spin :spinning="loading">
|
||||
<div class="code">
|
||||
<iframe
|
||||
id="notifier_iframe"
|
||||
class="code-item"
|
||||
width="100%"
|
||||
height="100%"
|
||||
:src="url"
|
||||
v-if="!loading"
|
||||
></iframe>
|
||||
</div>
|
||||
</j-spin>
|
||||
</template>
|
||||
<template v-else-if="getType === 'notifier-weixin'">
|
||||
<div class="tip">请先绑定企业微信账号</div>
|
||||
<!-- <div class="tip">请先绑定企业微信账号</div> -->
|
||||
<j-spin :spinning="loading">
|
||||
<div class="code">
|
||||
<iframe
|
||||
id="notifier_iframe"
|
||||
class="code-item"
|
||||
width="100%"
|
||||
height="100%"
|
||||
:src="url"
|
||||
v-if="!loading"
|
||||
></iframe>
|
||||
</div>
|
||||
</j-spin>
|
||||
</template>
|
||||
<template v-else-if="getType === 'notifier-email'">
|
||||
<div class="tip">请先绑定邮箱</div>
|
||||
|
@ -14,45 +38,184 @@
|
|||
</template>
|
||||
<template #footer>
|
||||
<j-button @click="emit('close')">取消</j-button>
|
||||
<j-button @click="onBind" type="primary" v-if="['notifier-email', 'notifier-voice', 'notifier-sms'].includes(getType)">立即绑定</j-button>
|
||||
<j-button
|
||||
@click="onBind"
|
||||
type="primary"
|
||||
v-if="
|
||||
[
|
||||
'notifier-email',
|
||||
'notifier-voice',
|
||||
'notifier-sms',
|
||||
].includes(getType)
|
||||
"
|
||||
>立即绑定</j-button
|
||||
>
|
||||
<j-button v-else @click="emit('close')">确定</j-button>
|
||||
</template>
|
||||
<EditInfo v-if="editInfoVisible" :data="user.userInfos" @close="editInfoVisible = false" @save="onSave" />
|
||||
<EditInfo
|
||||
v-if="editInfoVisible"
|
||||
:data="user.userInfos"
|
||||
@close="editInfoVisible = false"
|
||||
@save="onSave"
|
||||
/>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
bindThirdParty,
|
||||
getDingTalkOAuth2,
|
||||
getUserBind,
|
||||
getWechatOAuth2,
|
||||
} from '@/api/account/notificationSubscription';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
import EditInfo from '../../EditInfo/index.vue'
|
||||
import EditInfo from '../../EditInfo/index.vue';
|
||||
|
||||
const user = useUserInfo();
|
||||
const emit = defineEmits(['close', 'save']);
|
||||
const props = defineProps({
|
||||
data: { // 外层数据
|
||||
data: {
|
||||
// 外层数据
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
current: { // 当前的通道
|
||||
current: {
|
||||
// 当前的通道
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
const editInfoVisible = ref<boolean>(false)
|
||||
const editInfoVisible = ref<boolean>(false);
|
||||
const url = ref<string>('');
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
const getType = computed(() => {
|
||||
return props.current?.channelProvider
|
||||
})
|
||||
return props.current?.channelProvider;
|
||||
});
|
||||
|
||||
const onBind = () => {
|
||||
editInfoVisible.value = true
|
||||
}
|
||||
editInfoVisible.value = true;
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
editInfoVisible.value = false
|
||||
emit('save', props.current)
|
||||
emit('close')
|
||||
}
|
||||
editInfoVisible.value = false;
|
||||
emit('save', props.current);
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const onBindHandle = (
|
||||
type: 'notifier-weixin' | 'notifier-dingTalk',
|
||||
code: string,
|
||||
) => {
|
||||
getUserBind(type === 'notifier-dingTalk' ? 'dingtalk' : 'wechat', {
|
||||
authCode: code,
|
||||
configId: props.current?.channelConfiguration.notifierId,
|
||||
})
|
||||
.then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
const _bindCode = (resp?.result || '') as string;
|
||||
if (_bindCode) {
|
||||
bindThirdParty(
|
||||
type,
|
||||
props.current?.channelConfiguration.notifierId,
|
||||
_bindCode,
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
// 订阅
|
||||
emit('save', props.current);
|
||||
emit('close');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const updateIframeStyle = () => {
|
||||
const iframe = document.querySelector(
|
||||
'#notifier_iframe',
|
||||
) as HTMLIFrameElement;
|
||||
iframe.onload = () => {
|
||||
const currentUrl = iframe?.contentWindow?.location?.search || '';
|
||||
let authCode = '';
|
||||
if (currentUrl.startsWith('?')) {
|
||||
currentUrl
|
||||
.substring(1)
|
||||
.split('&')
|
||||
.map((item) => {
|
||||
if (
|
||||
props.current?.channelProvider === 'notifier-dingTalk'
|
||||
) {
|
||||
if (item.split('=')?.[0] === 'authCode') {
|
||||
authCode = item.split('=')?.[1] || '';
|
||||
}
|
||||
} else {
|
||||
if (item.split('=')?.[0] === 'code') {
|
||||
authCode = item.split('=')?.[1] || '';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (authCode) {
|
||||
loading.value = true;
|
||||
onBindHandle(props.current?.channelProvider, authCode);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const handleSearch = async () => {
|
||||
if (props.current?.channelProvider === 'notifier-weixin') {
|
||||
loading.value = true;
|
||||
const resp = await getWechatOAuth2(
|
||||
props.current?.channelConfiguration.notifierId,
|
||||
props.current?.channelConfiguration.templateId,
|
||||
location.href
|
||||
).finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
url.value = resp.result as string;
|
||||
nextTick(() => {
|
||||
updateIframeStyle();
|
||||
});
|
||||
}
|
||||
}
|
||||
if (props.current?.channelProvider === 'notifier-dingTalk') {
|
||||
loading.value = true;
|
||||
const resp = await getDingTalkOAuth2(
|
||||
props.current?.channelConfiguration.notifierId,
|
||||
location.href,
|
||||
).finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
if (resp.status === 200) {
|
||||
url.value = resp.result as string;
|
||||
nextTick(() => {
|
||||
updateIframeStyle();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.current,
|
||||
() => {
|
||||
handleSearch();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@ -61,6 +224,17 @@ const onSave = () => {
|
|||
margin: 80px 0;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #7F7F7F;
|
||||
color: #7f7f7f;
|
||||
}
|
||||
|
||||
.code {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.code-item {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -56,60 +56,9 @@
|
|||
<script lang="ts" setup>
|
||||
import { getAllNotice } from '@/api/account/center';
|
||||
import { getNoticeList_api } from '@/api/account/notificationSubscription';
|
||||
import { initData } from '../data';
|
||||
import Item from './components/Item.vue';
|
||||
|
||||
const initData = [
|
||||
{
|
||||
provider: 'alarm',
|
||||
name: '告警',
|
||||
children: [
|
||||
{
|
||||
provider: 'alarm-product',
|
||||
name: '产品告警',
|
||||
description:
|
||||
'当产品类型的告警被触发时,你将在已订阅的方式中收到通知',
|
||||
},
|
||||
{
|
||||
provider: 'alarm-device',
|
||||
name: '设备告警',
|
||||
description:
|
||||
'当设备类型的告警被触发时,你将在已订阅的方式中收到通知',
|
||||
},
|
||||
{
|
||||
provider: 'alarm-org',
|
||||
name: '部门告警',
|
||||
description:
|
||||
'当部门类型的告警被触发时,你将在已订阅的方式中收到通知',
|
||||
},
|
||||
{
|
||||
provider: 'alarm-other',
|
||||
name: '其他告警',
|
||||
description:
|
||||
'当其他类型的告警被触发时,你将在已订阅的方式中收到通知',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
provider: 'system-monitor',
|
||||
name: '系统监控',
|
||||
children: [
|
||||
{
|
||||
provider: 'system-event',
|
||||
name: '系统运行异常',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
provider: 'system-business',
|
||||
name: '业务监控',
|
||||
children: [
|
||||
{
|
||||
provider: 'device-transparent-codec',
|
||||
name: '透传消息解析异常',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const subscribe = ref<any[]>([]);
|
||||
const dataSource = ref<any[]>([]);
|
||||
const activeKey = ref<string[]>(['alarm', 'system-monitor', 'system-business']);
|
||||
|
@ -120,8 +69,8 @@ const handleSearch = () => {
|
|||
getAllNotice().then((resp: any) => {
|
||||
if (resp.status === 200) {
|
||||
const arr = initData
|
||||
.map((item) => {
|
||||
const _child = item.children.map((i) => {
|
||||
.map((item: any) => {
|
||||
const _child = item.children.map((i: any) => {
|
||||
const _item = (resp.result || []).find(
|
||||
(t: any) => t?.provider === i?.provider,
|
||||
);
|
||||
|
|
|
@ -0,0 +1,246 @@
|
|||
<template>
|
||||
<div class="upload-image-warp">
|
||||
<div class="upload-image-border" :style="borderStyle">
|
||||
<j-upload
|
||||
name="file"
|
||||
list-type="picture-card"
|
||||
class="avatar-uploader"
|
||||
:show-upload-list="false"
|
||||
:before-upload="beforeUpload"
|
||||
@change="handleChange"
|
||||
:action="FILE_UPLOAD"
|
||||
:headers="{
|
||||
'X-Access-Token': LocalStore.get(TOKEN_KEY),
|
||||
}"
|
||||
v-bind="props"
|
||||
>
|
||||
<div class="upload-image-content">
|
||||
<template v-if="imageUrl">
|
||||
<img
|
||||
:src="imageUrl"
|
||||
height="100%"
|
||||
class="upload-image"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<AIcon type="UserOutlined" style="font-size: 20px" />
|
||||
</template>
|
||||
<div class="upload-image-mask">更换</div>
|
||||
</div>
|
||||
</j-upload>
|
||||
<div class="upload-loading-mask" v-if="props.disabled"></div>
|
||||
<div class="upload-loading-mask" v-if="imageUrl && loading">
|
||||
<AIcon type="LoadingOutlined" style="font-size: 20px" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ImageCropper
|
||||
v-if="cropperVisible"
|
||||
:img="cropperImg"
|
||||
@cancel="cropperVisible = false"
|
||||
@ok="saveImage"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name='JProUpload'>
|
||||
import { UploadChangeParam, UploadProps } from 'ant-design-vue';
|
||||
import { message } from 'jetlinks-ui-components';
|
||||
import { FILE_UPLOAD } from '@/api/comm';
|
||||
import { TOKEN_KEY } from '@/utils/variable';
|
||||
import { getBase64, LocalStore } from '@/utils/comm';
|
||||
import { CSSProperties } from 'vue';
|
||||
import ImageCropper from '@/components/Upload/Cropper.vue';
|
||||
|
||||
type Emits = {
|
||||
(e: 'update:modelValue', data: string): void;
|
||||
(e: 'change', data: string): void;
|
||||
};
|
||||
interface JUploadProps extends UploadProps {
|
||||
modelValue: string;
|
||||
disabled?: boolean;
|
||||
types?: string[];
|
||||
errorMessage?: string;
|
||||
size?: number;
|
||||
borderStyle?: CSSProperties;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const props: JUploadProps = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
accept: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
borderStyle: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
const imageUrl = ref<string>(props?.modelValue || '');
|
||||
const imageTypes = props.types ? props.types : ['image/jpeg', 'image/png'];
|
||||
|
||||
const cropperImg = ref();
|
||||
const cropperVisible = ref(false);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newValue) => {
|
||||
imageUrl.value = newValue;
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
const handleChange = (info: UploadChangeParam) => {
|
||||
if (info.file.status === 'uploading') {
|
||||
loading.value = true;
|
||||
}
|
||||
if (info.file.status === 'done') {
|
||||
imageUrl.value = info.file.response?.result;
|
||||
loading.value = false;
|
||||
emit('update:modelValue', info.file.response?.result);
|
||||
emit('change', info.file.response?.result);
|
||||
}
|
||||
if (info.file.status === 'error') {
|
||||
loading.value = false;
|
||||
message.error('上传失败');
|
||||
}
|
||||
};
|
||||
|
||||
const beforeUpload = (file: UploadProps['fileList'][number]) => {
|
||||
const isType = imageTypes.includes(file.type);
|
||||
const maxSize = props.size || 2; // 最大值
|
||||
if (!isType) {
|
||||
if (props.errorMessage) {
|
||||
message.error(props.errorMessage);
|
||||
} else {
|
||||
message.error(`请上传正确格式的图片`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const isSize = file.size / 1024 / 1024 < maxSize;
|
||||
if (!isSize) {
|
||||
message.error(`图片大小必须小于${maxSize}M`);
|
||||
}
|
||||
|
||||
getBase64(file, (base64Url) => {
|
||||
cropperImg.value = base64Url;
|
||||
cropperVisible.value = true;
|
||||
});
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const saveImage = (url: string) => {
|
||||
cropperVisible.value = false;
|
||||
imageUrl.value = url;
|
||||
emit('update:modelValue', url);
|
||||
emit('change', url);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@border: 1px dashed @border-color-base;
|
||||
@mask-color: rgba(#000, 0.35);
|
||||
@with: 66px;
|
||||
@height: 66px;
|
||||
|
||||
.flex-center() {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.upload-image-warp {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
|
||||
.upload-image-border {
|
||||
position: relative;
|
||||
width: @with;
|
||||
height: @height;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s;
|
||||
|
||||
:deep(.ant-upload-picture-card-wrapper) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
:deep(.ant-upload) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-upload-select-picture-card) {
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.upload-image-content {
|
||||
.flex-center();
|
||||
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(#000, 0.06);
|
||||
cursor: pointer;
|
||||
// padding: 8px;
|
||||
|
||||
.upload-image-mask {
|
||||
.flex-center();
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
font-size: 16px;
|
||||
background-color: @mask-color;
|
||||
}
|
||||
|
||||
.upload-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
&:hover .upload-image-mask {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-loading-mask {
|
||||
.flex-center();
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #fff;
|
||||
background-color: @mask-color;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,54 @@
|
|||
const initData: any[] = [
|
||||
{
|
||||
provider: 'alarm',
|
||||
name: '告警',
|
||||
children: [
|
||||
{
|
||||
provider: 'alarm-product',
|
||||
name: '产品告警',
|
||||
description:
|
||||
'当产品类型的告警被触发时,你将在已订阅的方式中收到通知',
|
||||
},
|
||||
{
|
||||
provider: 'alarm-device',
|
||||
name: '设备告警',
|
||||
description:
|
||||
'当设备类型的告警被触发时,你将在已订阅的方式中收到通知',
|
||||
},
|
||||
{
|
||||
provider: 'alarm-org',
|
||||
name: '部门告警',
|
||||
description:
|
||||
'当部门类型的告警被触发时,你将在已订阅的方式中收到通知',
|
||||
},
|
||||
{
|
||||
provider: 'alarm-other',
|
||||
name: '其他告警',
|
||||
description:
|
||||
'当其他类型的告警被触发时,你将在已订阅的方式中收到通知',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
provider: 'system-monitor',
|
||||
name: '系统监控',
|
||||
children: [
|
||||
{
|
||||
provider: 'system-event',
|
||||
name: '系统运行异常',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
provider: 'system-business',
|
||||
name: '业务监控',
|
||||
children: [
|
||||
{
|
||||
provider: 'device-transparent-codec',
|
||||
name: '透传消息解析异常',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export { initData };
|
|
@ -138,7 +138,7 @@
|
|||
<div class="card" v-if="isNoCommunity">
|
||||
<h3>绑定三方账号</h3>
|
||||
<div class="content">
|
||||
<div class="account-card" v-for="item in bindList">
|
||||
<div class="account-card" v-for="item in bindList" :key="item.id">
|
||||
<img
|
||||
:src="item.logoUrl || getImage(bindIcon[item.provider])"
|
||||
style="height: 50px;width: 50px"
|
||||
|
|
|
@ -4,19 +4,23 @@
|
|||
<div class="person-header-item">
|
||||
<div class="person-header-item-info">
|
||||
<div class="person-header-item-info-left">
|
||||
<j-avatar :size="64">
|
||||
<template #icon
|
||||
><AIcon type="UserOutlined"
|
||||
/></template>
|
||||
</j-avatar>
|
||||
<UploadAvatar
|
||||
:accept="
|
||||
imageTypes && imageTypes.length
|
||||
? imageTypes.toString()
|
||||
: ''
|
||||
"
|
||||
:modelValue="user.userInfos?.avatar"
|
||||
@change="onAvatarChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="person-header-item-info-right">
|
||||
<div class="person-header-item-info-right-top">
|
||||
<span>xx部门 · xx角色</span>
|
||||
<span>{{ _org }}部门 · {{ _role }}角色</span>
|
||||
</div>
|
||||
<div class="person-header-item-info-right-info">
|
||||
<div>用户名 {{user.userInfos?.username}}</div>
|
||||
<div>账号ID {{user.userInfos?.id}}</div>
|
||||
<div>用户名 {{ user.userInfos?.username }}</div>
|
||||
<div>账号ID {{ user.userInfos?.id }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -27,7 +31,7 @@
|
|||
@click="onActivated(item.key)"
|
||||
v-for="item in list"
|
||||
:type="
|
||||
activeKey === item.key
|
||||
user.tabKey === item.key
|
||||
? 'primary'
|
||||
: 'default'
|
||||
"
|
||||
|
@ -39,21 +43,25 @@
|
|||
<div class="person-header-item-action-right">
|
||||
<j-space :size="24">
|
||||
<j-tooltip title="查看详情"
|
||||
><j-button @click="visible = true" shape="circle"
|
||||
><j-button
|
||||
@click="visible = true"
|
||||
shape="circle"
|
||||
><AIcon
|
||||
style="font-size: 24px"
|
||||
type="FileSearchOutlined" /></j-button
|
||||
></j-tooltip>
|
||||
<j-tooltip title="编辑资料"
|
||||
><j-button shape="circle"
|
||||
@click="editInfoVisible = true"
|
||||
><j-button
|
||||
shape="circle"
|
||||
@click="editInfoVisible = true"
|
||||
><AIcon
|
||||
style="font-size: 24px"
|
||||
type="FormOutlined" /></j-button
|
||||
></j-tooltip>
|
||||
<j-tooltip title="修改密码"
|
||||
><j-button shape="circle"
|
||||
@click="editPasswordVisible = true"
|
||||
><j-button
|
||||
shape="circle"
|
||||
@click="editPasswordVisible = true"
|
||||
><AIcon
|
||||
style="font-size: 24px"
|
||||
type="LockOutlined" /></j-button
|
||||
|
@ -67,14 +75,23 @@
|
|||
<div class="person-content-item">
|
||||
<FullPage>
|
||||
<div class="person-content-item-content">
|
||||
<component :is="tabs[activeKey]" />
|
||||
<component :is="tabs[user.tabKey]" />
|
||||
</div>
|
||||
</FullPage>
|
||||
</div>
|
||||
</div>
|
||||
<Detail v-if="visible" @close="visible = false"/>
|
||||
<EditInfo v-if="editInfoVisible" :data="user.userInfos" @close="editInfoVisible = false" @save="onSave" />
|
||||
<EditPassword v-if="editPasswordVisible" @close="editPasswordVisible = false" @save="onPasswordSave" />
|
||||
<Detail v-if="visible" @close="visible = false" />
|
||||
<EditInfo
|
||||
v-if="editInfoVisible"
|
||||
:data="user.userInfos"
|
||||
@close="editInfoVisible = false"
|
||||
@save="onSave"
|
||||
/>
|
||||
<EditPassword
|
||||
v-if="editPasswordVisible"
|
||||
@close="editPasswordVisible = false"
|
||||
@save="onPasswordSave"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -87,6 +104,19 @@ import Detail from './components/Detail/index.vue';
|
|||
import EditInfo from './components/EditInfo/index.vue';
|
||||
import EditPassword from './components/EditPassword/index.vue';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
import UploadAvatar from './components/UploadAvatar/index.vue';
|
||||
import { updateMeInfo_api } from '@/api/account/center';
|
||||
import { onlyMessage } from '@/utils/comm';
|
||||
import { useRouterParams } from '@/utils/hooks/useParams';
|
||||
|
||||
const imageTypes = reactive([
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/jpg',
|
||||
'image/jfif',
|
||||
'image/pjp',
|
||||
'image/pjpeg',
|
||||
]);
|
||||
|
||||
const user = useUserInfo();
|
||||
|
||||
|
@ -117,23 +147,59 @@ const tabs = {
|
|||
StationMessage,
|
||||
};
|
||||
|
||||
const activeKey = ref<KeyType>('HomeView');
|
||||
const router = useRouterParams()
|
||||
|
||||
// const activeKey = ref<KeyType>('HomeView');
|
||||
const visible = ref<boolean>(false);
|
||||
const editInfoVisible = ref<boolean>(false);
|
||||
const editPasswordVisible = ref<boolean>(false);
|
||||
|
||||
const onActivated = (_key: KeyType) => {
|
||||
activeKey.value = _key;
|
||||
user.tabKey = _key;
|
||||
};
|
||||
|
||||
const _org = computed(() => {
|
||||
return user.userInfos?.orgList
|
||||
?.map((item: any) => {
|
||||
return item?.name;
|
||||
})
|
||||
.join(',');
|
||||
});
|
||||
|
||||
const _role = computed(() => {
|
||||
return user.userInfos?.roleList
|
||||
?.map((item: any) => {
|
||||
return item?.name;
|
||||
})
|
||||
.join(',');
|
||||
});
|
||||
|
||||
const onSave = () => {
|
||||
user.getUserInfo()
|
||||
editInfoVisible.value = false
|
||||
}
|
||||
user.getUserInfo();
|
||||
editInfoVisible.value = false;
|
||||
};
|
||||
|
||||
const onPasswordSave = () => {
|
||||
editPasswordVisible.value = false
|
||||
}
|
||||
editPasswordVisible.value = false;
|
||||
};
|
||||
|
||||
const onAvatarChange = (url: string) => {
|
||||
updateMeInfo_api({
|
||||
...user.userInfos,
|
||||
avatar: url,
|
||||
}).then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
onlyMessage('操作成功', 'success');
|
||||
user.getUserInfo();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
if(router.params.value?.tabKey) {
|
||||
user.tabKey = router.params.value?.tabKey
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -3924,91 +3924,101 @@ export default [
|
|||
},
|
||||
{
|
||||
code: 'system/NoticeRule',
|
||||
name: '通知规则',
|
||||
name: '订阅管理',
|
||||
owner: 'iot',
|
||||
id: 'system/NoticeRule',
|
||||
sortIndex: 11,
|
||||
id: '522f790d4422a608d491bc9e2fa12b4e',
|
||||
sortIndex: 12,
|
||||
url: '/system/NoticeRule',
|
||||
icon: 'icon-yingyongguanli',
|
||||
showPage: ['application'],
|
||||
icon: 'CopyOutlined',
|
||||
showPage: ['notify-channel'],
|
||||
permissions: [],
|
||||
buttons: [
|
||||
{
|
||||
id: 'delete',
|
||||
name: '删除',
|
||||
permissions: [
|
||||
// {
|
||||
// permission: 'application',
|
||||
// actions: ['query', 'delete'],
|
||||
// },
|
||||
{
|
||||
permission: 'role',
|
||||
actions: ['query'],
|
||||
},
|
||||
{
|
||||
permission: 'notify-channel',
|
||||
actions: ['save', 'delete'],
|
||||
},
|
||||
{
|
||||
permission: 'notifier',
|
||||
actions: ['query'],
|
||||
},
|
||||
{
|
||||
permission: 'template',
|
||||
actions: ['query'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'add',
|
||||
name: '新增',
|
||||
permissions: [
|
||||
// {
|
||||
// permission: 'role',
|
||||
// actions: ['query'],
|
||||
// },
|
||||
// {
|
||||
// permission: 'menu',
|
||||
// actions: ['query'],
|
||||
// },
|
||||
// {
|
||||
// permission: 'application',
|
||||
// actions: ['query', 'save'],
|
||||
// },
|
||||
// {
|
||||
// permission: 'open-api',
|
||||
// actions: ['query', 'save', 'delete'],
|
||||
// },
|
||||
{
|
||||
permission: 'role',
|
||||
actions: ['query'],
|
||||
},
|
||||
{
|
||||
permission: 'notify-channel',
|
||||
actions: ['save'],
|
||||
},
|
||||
{
|
||||
permission: 'notifier',
|
||||
actions: ['query'],
|
||||
},
|
||||
{
|
||||
permission: 'template',
|
||||
actions: ['query'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'update',
|
||||
name: '编辑',
|
||||
permissions: [
|
||||
// {
|
||||
// permission: 'role',
|
||||
// actions: ['query'],
|
||||
// },
|
||||
// {
|
||||
// permission: 'menu',
|
||||
// actions: ['query'],
|
||||
// },
|
||||
// {
|
||||
// permission: 'application',
|
||||
// actions: ['query', 'save'],
|
||||
// },
|
||||
// {
|
||||
// permission: 'open-api',
|
||||
// actions: ['query', 'save', 'delete'],
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'view',
|
||||
name: '查看',
|
||||
permissions: [
|
||||
// {
|
||||
// permission: 'application',
|
||||
// actions: ['query'],
|
||||
// },
|
||||
// {
|
||||
// permission: 'role',
|
||||
// actions: ['query'],
|
||||
// },
|
||||
{
|
||||
permission: 'role',
|
||||
actions: ['query'],
|
||||
},
|
||||
{
|
||||
permission: 'notify-channel',
|
||||
actions: ['save', 'query'],
|
||||
},
|
||||
{
|
||||
permission: 'notifier',
|
||||
actions: ['query'],
|
||||
},
|
||||
{
|
||||
permission: 'template',
|
||||
actions: ['query'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'action',
|
||||
name: '启/禁用',
|
||||
permissions: [
|
||||
// {
|
||||
// permission: 'application',
|
||||
// actions: ['save'],
|
||||
// },
|
||||
{
|
||||
permission: 'role',
|
||||
actions: ['query'],
|
||||
},
|
||||
{
|
||||
permission: 'notify-channel',
|
||||
actions: ['save'],
|
||||
},
|
||||
{
|
||||
permission: 'notifier',
|
||||
actions: ['query'],
|
||||
},
|
||||
{
|
||||
permission: 'template',
|
||||
actions: ['query'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
@ -5,7 +5,13 @@
|
|||
{{ data?.name }}
|
||||
</div>
|
||||
<div>
|
||||
<j-switch @change="onSwitchChange" :checked="checked" />
|
||||
<j-tooltip :title="!action ? '暂无权限,请联系管理员' : ''">
|
||||
<j-switch
|
||||
:disabled="!action"
|
||||
@change="onSwitchChange"
|
||||
:checked="checked"
|
||||
/>
|
||||
</j-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="child-item-right" v-if="checked">
|
||||
|
@ -17,15 +23,16 @@
|
|||
<img
|
||||
style="width: 100%"
|
||||
:src="
|
||||
getImage(
|
||||
`/notice/${noticeType.get(
|
||||
slotProps?.channelProvider,
|
||||
)}.png`,
|
||||
)
|
||||
iconMap.get(slotProps?.channelProvider)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<template #overlay v-if="slotProps?.channelProvider !== 'inside-mail'">
|
||||
<template
|
||||
#overlay
|
||||
v-if="
|
||||
slotProps?.channelProvider !== 'inside-mail'
|
||||
"
|
||||
>
|
||||
<j-menu mode="">
|
||||
<j-menu-item>
|
||||
<PermissionButton
|
||||
|
@ -40,7 +47,9 @@
|
|||
<PermissionButton
|
||||
@click="onEdit(slotProps)"
|
||||
type="link"
|
||||
:hasPermission="true"
|
||||
:hasPermission="[
|
||||
'system/NoticeRule:update',
|
||||
]"
|
||||
>
|
||||
编辑
|
||||
</PermissionButton>
|
||||
|
@ -50,7 +59,9 @@
|
|||
@click="onDelete(slotProps.id)"
|
||||
danger
|
||||
type="link"
|
||||
:hasPermission="true"
|
||||
:hasPermission="[
|
||||
'system/NoticeRule:delete',
|
||||
]"
|
||||
>
|
||||
删除
|
||||
</PermissionButton>
|
||||
|
@ -65,29 +76,51 @@
|
|||
</template>
|
||||
<template #add>
|
||||
<div class="box-item">
|
||||
<div @click="onAdd" class="box-item-img">
|
||||
<AIcon
|
||||
style="font-size: 20px"
|
||||
type="PlusOutlined"
|
||||
/>
|
||||
<div class="box-item-img">
|
||||
<j-tooltip
|
||||
:title="!add ? '暂无权限,请联系管理员' : ''"
|
||||
>
|
||||
<j-button
|
||||
:disabled="!add"
|
||||
type="text"
|
||||
@click="onAdd"
|
||||
>
|
||||
<AIcon
|
||||
style="font-size: 20px"
|
||||
type="PlusOutlined"
|
||||
/>
|
||||
</j-button>
|
||||
</j-tooltip>
|
||||
</div>
|
||||
<div class="box-item-text"></div>
|
||||
</div>
|
||||
</template>
|
||||
</MCarousel>
|
||||
|
||||
<div
|
||||
class="child-item-right-auth"
|
||||
:class="{ active: auth.length }"
|
||||
@click="onAuth"
|
||||
>
|
||||
<AIcon type="UserOutlined" />
|
||||
<span>权限控制</span>
|
||||
<div class="child-item-right-auth" :class="{ active: auth.length }">
|
||||
<j-tooltip :title="!update ? '暂无权限,请联系管理员' : ''">
|
||||
<j-button :disabled="!update" type="text" @click="onAuth">
|
||||
<div class="child-item-right-auth-btn">
|
||||
<AIcon type="UserOutlined" />
|
||||
<span>权限控制</span>
|
||||
</div>
|
||||
</j-button>
|
||||
</j-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Save :data="current" v-if="visible" @close="visible = false" @save="onSave" />
|
||||
<Detail :data="current" v-if="detailVisible" @close="detailVisible = false" />
|
||||
<Save
|
||||
:data="current"
|
||||
v-if="visible"
|
||||
@close="visible = false"
|
||||
@save="onSave"
|
||||
:loading="loading"
|
||||
/>
|
||||
<Detail
|
||||
:data="current"
|
||||
v-if="detailVisible"
|
||||
@close="detailVisible = false"
|
||||
/>
|
||||
<Auth
|
||||
v-if="authVisible"
|
||||
:data="data?.grant?.role?.idList"
|
||||
|
@ -101,7 +134,7 @@ import MCarousel from '@/components/MCarousel/index.vue';
|
|||
import Save from '../Save/index.vue';
|
||||
import Detail from '../Detail/index.vue';
|
||||
import Auth from '../Auth/index.vue';
|
||||
import { noticeType } from '../../data';
|
||||
import { iconMap } from '../../data';
|
||||
import { getImage, onlyMessage } from '@/utils/comm';
|
||||
import {
|
||||
saveChannelConfig,
|
||||
|
@ -111,6 +144,7 @@ import {
|
|||
updateChannelConfig,
|
||||
} from '@/api/system/noticeRule';
|
||||
import { Modal } from 'jetlinks-ui-components';
|
||||
import { usePermissionStore } from '@/store/permission';
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
|
@ -128,6 +162,13 @@ const current = ref<any>({});
|
|||
|
||||
const checked = ref<boolean>(false);
|
||||
const auth = ref<string[]>([]);
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
const permission = usePermissionStore();
|
||||
|
||||
const action = permission.hasPermission('system/NoticeRule:action');
|
||||
const add = permission.hasPermission('system/NoticeRule:add');
|
||||
const update = permission.hasPermission('system/NoticeRule:update');
|
||||
|
||||
watchEffect(() => {
|
||||
checked.value = props.data?.state?.value === 'enabled';
|
||||
|
@ -137,17 +178,17 @@ watchEffect(() => {
|
|||
const onAdd = () => {
|
||||
visible.value = true;
|
||||
current.value = {
|
||||
providerId: props.data.id
|
||||
}
|
||||
providerId: props.data.id,
|
||||
};
|
||||
};
|
||||
|
||||
const onView = (dt: any) => {
|
||||
current.value = dt
|
||||
current.value = dt;
|
||||
detailVisible.value = true;
|
||||
};
|
||||
|
||||
const onEdit = (dt: any) => {
|
||||
current.value = dt
|
||||
current.value = dt;
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
|
@ -241,13 +282,16 @@ const onSwitchChange = (e: boolean) => {
|
|||
};
|
||||
|
||||
const onSave = (_data: any) => {
|
||||
updateChannelConfig(props.data.id, {...props.data, ..._data}).then((resp) => {
|
||||
loading.value = true
|
||||
updateChannelConfig(props.data.id, [_data]).then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
onlyMessage('操作成功!', 'success');
|
||||
visible.value = false;
|
||||
emits('refresh');
|
||||
}
|
||||
});
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -297,17 +341,19 @@ const onSave = (_data: any) => {
|
|||
|
||||
.child-item-right-auth {
|
||||
margin-left: 20px;
|
||||
height: 78px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: @primary-color-hover;
|
||||
}
|
||||
&.active {
|
||||
color: @primary-color;
|
||||
.child-item-right-auth-btn {
|
||||
height: 78px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: @primary-color-hover;
|
||||
}
|
||||
&.active {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,17 @@
|
|||
@search="handleSearch"
|
||||
class="action-search"
|
||||
/>
|
||||
<div class="alert">
|
||||
<AIcon type="InfoCircleOutlined" />
|
||||
已规定固定收信人的模板在当前页面将被过滤
|
||||
</div>
|
||||
<div style="height: 400px; overflow-y: auto">
|
||||
<JProTable
|
||||
:columns="columns"
|
||||
:request="(e) => handleData(e)"
|
||||
model="CARD"
|
||||
:bodyStyle="{
|
||||
padding: 0
|
||||
padding: 0,
|
||||
}"
|
||||
:params="params"
|
||||
:gridColumn="2"
|
||||
|
@ -74,6 +78,7 @@
|
|||
<script lang="ts" setup>
|
||||
import TemplateApi from '@/api/notice/template';
|
||||
import { MSG_TYPE, NOTICE_METHOD } from '@/views/notice/const';
|
||||
import { _variableMap } from '../../../data';
|
||||
const props = defineProps({
|
||||
notifierId: {
|
||||
type: String,
|
||||
|
@ -152,14 +157,19 @@ const handleData = async (e: any) => {
|
|||
{ name: 'id', value: props.value },
|
||||
{ name: 'createTime', order: 'desc' },
|
||||
];
|
||||
const resp = await TemplateApi.getListByConfigId(props.notifierId, {
|
||||
const resp = await TemplateApi.getListVariableByConfigId(props.notifierId, {
|
||||
...e,
|
||||
sorts: sorts,
|
||||
});
|
||||
const result = (resp?.result || []).filter((item: any) => {
|
||||
const _variable = _variableMap.get(item.type);
|
||||
const arr = item?.variableDefinitions?.map((i: any) => i?.id) || [];
|
||||
return arr.includes(_variable);
|
||||
});
|
||||
return {
|
||||
code: resp.message,
|
||||
result: {
|
||||
data: resp.result ? resp.result : [],
|
||||
data: result,
|
||||
pageIndex: 0,
|
||||
pageSize: resp.result.length,
|
||||
total: resp.result.length,
|
||||
|
@ -193,4 +203,12 @@ watch(
|
|||
width: 88px;
|
||||
height: 88px;
|
||||
}
|
||||
.alert {
|
||||
height: 40px;
|
||||
padding-left: 10px;
|
||||
margin-bottom: 10px;
|
||||
color: rgba(0, 0, 0, 0.55);
|
||||
line-height: 40px;
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
</style>
|
|
@ -72,7 +72,6 @@ const formRef = ref();
|
|||
const modelRef = reactive({});
|
||||
|
||||
watchEffect(() => {
|
||||
console.log(props.notify, '123')
|
||||
Object.assign(modelRef, props?.value);
|
||||
});
|
||||
|
||||
|
|
|
@ -110,7 +110,13 @@ watch(
|
|||
if (v === 'upper') {
|
||||
queryConfigVariables(props.providerId).then(resp => {
|
||||
if (resp.status === 200) {
|
||||
builtInList.value = (resp.result as any[]).map(item => {
|
||||
// 避免数据id相同,去重
|
||||
const _set = new Set((resp.result as any[]).map(item => item?.id))
|
||||
const arr = [..._set.values()].map(item => {
|
||||
const _arr = (resp.result as any[]).reverse()
|
||||
return _arr.find(i => i.id === item)
|
||||
})
|
||||
builtInList.value = arr.map(item => {
|
||||
return {
|
||||
...item,
|
||||
id: 'detail.' + item.id // 为了方便传到后端
|
||||
|
|
|
@ -43,7 +43,7 @@ watch(
|
|||
);
|
||||
|
||||
watch(
|
||||
() => props.notify.notifierId,
|
||||
() => props.notify.channelConfiguration?.notifierId,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
getDepartment(newVal);
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
v-if="current !== stepList.length - 1"
|
||||
>下一步</j-button
|
||||
>
|
||||
<j-button type="primary" @click="onSave" v-else>确认</j-button>
|
||||
<j-button :loading="loading" type="primary" @click="onSave" v-else>确认</j-button>
|
||||
</j-space>
|
||||
</template>
|
||||
</j-modal>
|
||||
|
@ -87,6 +87,10 @@ const props = defineProps({
|
|||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const stepList = [
|
||||
|
|
|
@ -6,11 +6,11 @@ iconMap.set('notifier-weixin', getImage('/notice/wechat.png'));
|
|||
iconMap.set('notifier-email', getImage('/notice/email.png'));
|
||||
iconMap.set('notifier-voice', getImage('/notice/voice.png'));
|
||||
iconMap.set('notifier-sms', getImage('/notice/sms.png'));
|
||||
iconMap.set('inside-mail', getImage('/notice/sms.png'));
|
||||
iconMap.set('inside-mail', getImage('/notice/inside-mail.png'));
|
||||
|
||||
const noticeType = new Map();
|
||||
noticeType.set('notifier-dingTalk', 'dingtalk');
|
||||
noticeType.set('notifier-weixin', 'wechat');
|
||||
noticeType.set('notifier-dingTalk', 'dingTalk');
|
||||
noticeType.set('notifier-weixin', 'weixin');
|
||||
noticeType.set('notifier-email', 'email');
|
||||
noticeType.set('notifier-voice', 'voice');
|
||||
noticeType.set('notifier-sms', 'sms');
|
||||
|
@ -23,4 +23,11 @@ variableMap.set('notifier-email', 'sendTo');
|
|||
variableMap.set('notifier-voice', 'calledNumber');
|
||||
variableMap.set('notifier-sms', 'phoneNumber');
|
||||
|
||||
export { iconMap, noticeType, variableMap }
|
||||
const _variableMap = new Map();
|
||||
_variableMap.set('dingTalk', 'userIdList');
|
||||
_variableMap.set('weixin', 'toUser');
|
||||
_variableMap.set('email', 'sendTo');
|
||||
_variableMap.set('voice', 'calledNumber');
|
||||
_variableMap.set('sms', 'phoneNumber');
|
||||
|
||||
export { iconMap, noticeType, variableMap, _variableMap }
|
21
yarn.lock
21
yarn.lock
|
@ -3823,10 +3823,10 @@ jetlinks-store@^0.0.3:
|
|||
resolved "https://registry.npmjs.org/jetlinks-store/-/jetlinks-store-0.0.3.tgz"
|
||||
integrity sha512-AZf/soh1hmmwjBZ00fr1emuMEydeReaI6IBTGByQYhTmK1Zd5pQAxC7WLek2snRAn/HHDgJfVz2hjditKThl6Q==
|
||||
|
||||
jetlinks-ui-components@^1.0.23:
|
||||
version "1.0.23"
|
||||
resolved "https://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.23.tgz#029f45a61316e3bf3b4c75959d41d7d76068fcbe"
|
||||
integrity sha512-6OGDn8/kAmjlHMoeIp5B4L6EeucbqKFxqnYys4MqmtEvco/d5Pr8rzmJEav6bZmGkN21YWNHpwkhX3yrQt2F+g==
|
||||
jetlinks-ui-components@^1.0.24:
|
||||
version "1.0.24"
|
||||
resolved "http://registry.jetlinks.cn/jetlinks-ui-components/-/jetlinks-ui-components-1.0.24.tgz#97580bed720526b50b3244440c7ae16d3d0a26c0"
|
||||
integrity sha512-7ccv/eu9moZZFzCRuBa8Pe4NLd/knDARWwJaivH+qgkPSIIdij0Wax27zFwoRqivsDzbOAs2iRButcwSNvR9AQ==
|
||||
dependencies:
|
||||
"@vueuse/core" "^9.12.0"
|
||||
"@vueuse/router" "^9.13.0"
|
||||
|
@ -3834,6 +3834,7 @@ jetlinks-ui-components@^1.0.23:
|
|||
colorpicker-v3 "^2.10.2"
|
||||
lodash-es "^4.17.21"
|
||||
monaco-editor "^0.35.0"
|
||||
vuedraggable "^4.1.0"
|
||||
|
||||
js-cookie@^3.0.1:
|
||||
version "3.0.1"
|
||||
|
@ -6162,6 +6163,11 @@ socks@^2.6.2:
|
|||
ip "^2.0.0"
|
||||
smart-buffer "^4.2.0"
|
||||
|
||||
sortablejs@1.14.0:
|
||||
version "1.14.0"
|
||||
resolved "http://registry.jetlinks.cn/sortablejs/-/sortablejs-1.14.0.tgz#6d2e17ccbdb25f464734df621d4f35d4ab35b3d8"
|
||||
integrity sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==
|
||||
|
||||
source-map-js@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz"
|
||||
|
@ -7048,6 +7054,13 @@ vue@^3.2.37, vue@^3.2.45:
|
|||
"@vue/server-renderer" "3.2.45"
|
||||
"@vue/shared" "3.2.45"
|
||||
|
||||
vuedraggable@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "http://registry.jetlinks.cn/vuedraggable/-/vuedraggable-4.1.0.tgz#edece68adb8a4d9e06accff9dfc9040e66852270"
|
||||
integrity sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==
|
||||
dependencies:
|
||||
sortablejs "1.14.0"
|
||||
|
||||
walk-up-path@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.jetlinks.cn/walk-up-path/-/walk-up-path-1.0.0.tgz"
|
||||
|
|
Loading…
Reference in New Issue