<script lang="ts" setup>
|
import type { ScrollActionType } from '@jnpf/ui';
|
|
import { inject, nextTick, reactive, ref, toRefs, unref } from 'vue';
|
import { useRouter } from 'vue-router';
|
|
import { useMessage } from '@jnpf/hooks';
|
import { ScrollContainer } from '@jnpf/ui';
|
import { BasicDrawer, useDrawerInner } from '@jnpf/ui/drawer';
|
import { useModal } from '@jnpf/ui/modal';
|
import { encryptByBase64, toDateValue } from '@jnpf/utils';
|
|
import { useDebounceFn } from '@vueuse/core';
|
import { Badge } from 'ant-design-vue';
|
|
import { getMessageList, readAllMsg, readInfo as readMsgInfo } from '#/api/system/message';
|
import { getScheduleDetail } from '#/api/teamwork/schedule';
|
import { useWebSocket } from '#/hooks/web/useWebSocket';
|
import { $t } from '#/locales';
|
import { useBaseStore } from '#/store';
|
import Detail from '#/views/msgCenter/notice/Detail.vue';
|
import ScheduleDetail from '#/views/teamwork/schedule/Detail.vue';
|
|
interface State {
|
finish: boolean;
|
loading: boolean;
|
listQuery: any;
|
list: any[];
|
isNoRead: boolean;
|
messageType: any[];
|
}
|
const emit = defineEmits(['register', 'readMsg']);
|
const emitter: any = inject('emitter');
|
const router = useRouter();
|
const { createMessage, createConfirm } = useMessage();
|
const [registerDetail, { openModal: openDetailModal }] = useModal();
|
const [registerScheduleDetail, { openModal: openScheduleDetailModal }] = useModal();
|
const [registerDrawer, { closeDrawer, getOpen }] = useDrawerInner(init);
|
const infiniteBody = ref<Nullable<ScrollActionType>>(null);
|
const debounceInitData = useDebounceFn(initData, 300);
|
const state = reactive<State>({
|
finish: false,
|
loading: false,
|
listQuery: {
|
keyword: '',
|
currentPage: 1,
|
pageSize: 20,
|
sort: 'desc',
|
type: '',
|
isRead: 0,
|
},
|
list: [],
|
isNoRead: true,
|
messageType: [],
|
});
|
const { list, listQuery, isNoRead, messageType, loading } = toRefs(state);
|
const baseStore = useBaseStore();
|
const { onWebSocket } = useWebSocket();
|
|
onWebSocket((data: any) => {
|
// 消息推送(消息公告用的)
|
if (data.method == 'messagePush' && unref(getOpen)) debounceInitData();
|
});
|
|
function init() {
|
state.listQuery.isRead = 0;
|
state.listQuery.keyword = '';
|
state.listQuery.type = '';
|
state.isNoRead = true;
|
initData();
|
nextTick(() => {
|
bindScroll();
|
initMessageType();
|
});
|
}
|
function initData() {
|
state.finish = false;
|
state.listQuery.currentPage = 1;
|
state.list = [];
|
getMsgList();
|
}
|
async function initMessageType() {
|
const all = { id: '', fullName: '全部', enCode: '' };
|
const list = ((await baseStore.getDictionaryData('msgSourceType')) as any[]) || [];
|
state.messageType = [all, ...list];
|
}
|
function bindScroll() {
|
const bodyRef = infiniteBody.value;
|
const vBody = bodyRef?.getScrollWrap();
|
vBody?.addEventListener('scroll', () => {
|
if (vBody.scrollHeight - vBody.clientHeight - vBody.scrollTop <= 200 && !state.loading && !state.finish) {
|
state.listQuery.currentPage += 1;
|
getMsgList();
|
}
|
});
|
}
|
function getMsgList() {
|
state.loading = true;
|
getMessageList(state.listQuery).then((res) => {
|
if (res.data.list.length < state.listQuery.pageSize) state.finish = true;
|
state.list = [...state.list, ...res.data.list];
|
state.loading = false;
|
});
|
}
|
function readInfo(item) {
|
readMsgInfo(item.id).then((res) => {
|
if (item.isRead == '0') {
|
item.isRead = '1';
|
emit('readMsg');
|
}
|
if (item.type == 4) {
|
const bodyText = res.data.bodyText ? JSON.parse(res.data.bodyText) : {};
|
if (bodyText.type == 3) return;
|
getScheduleDetail(bodyText.groupId, bodyText.id).then(() => {
|
openScheduleDetailModal(true, { id: bodyText.id, groupId: bodyText.groupId });
|
});
|
} else if (item.type == 2 && item.flowType == 2) {
|
const bodyText = JSON.parse(res.data.bodyText);
|
if (bodyText.type == 0) return;
|
closeDrawer();
|
emitter.emit('openProfileModal', { activeKey: 'entrust', subActiveKey: bodyText.type });
|
} else {
|
if (item.type == 1 || item.type == 3) {
|
openDetailModal(true, { id: item.id, type: 1 });
|
} else {
|
if (!res.data.bodyText) return;
|
closeDrawer();
|
router.push(`/workFlowDetail?config=${encodeURIComponent(encryptByBase64(res.data.bodyText))}`);
|
}
|
}
|
});
|
}
|
function onTabChange() {
|
state.listQuery.keyword = '';
|
initData();
|
}
|
function onIsReadChange(val) {
|
state.listQuery.isRead = val ? 0 : '';
|
initData();
|
}
|
function handleSearch() {
|
initData();
|
}
|
async function handleClose() {
|
const bodyRef = infiniteBody.value;
|
const vBody = bodyRef?.getScrollWrap();
|
vBody?.removeEventListener('scroll', () => {});
|
return true;
|
}
|
function gotoCenter() {
|
closeDrawer();
|
router.push('/messageRecord');
|
}
|
function readAll() {
|
createConfirm({
|
iconType: 'warning',
|
title: '提示',
|
content: `您确定全部标识为已读状态, 是否继续?`,
|
onOk: () => {
|
const query = {
|
keyword: state.listQuery.keyword,
|
type: state.listQuery.type,
|
isRead: state.isNoRead ? 0 : '',
|
};
|
readAllMsg(query).then((res) => {
|
createMessage.success(res.msg);
|
emit('readMsg', true);
|
if (state.isNoRead) {
|
initData();
|
} else {
|
for (let i = 0; i < state.list.length; i++) {
|
state.list[i].isRead = '1';
|
}
|
}
|
});
|
},
|
});
|
}
|
</script>
|
<template>
|
<BasicDrawer v-bind="$attrs" :close-func="handleClose" class="full-drawer message-drawer" title="站内消息" width="340px" @register="registerDrawer">
|
<div class="tool">
|
<a-input-search v-model:value="listQuery.keyword" :placeholder="$t('common.drawerSearchText')" allow-clear @search="handleSearch" />
|
</div>
|
<a-tabs v-model:active-key="listQuery.type" size="small" :tab-bar-gutter="20" @change="onTabChange">
|
<a-tab-pane :key="item.enCode" :tab="item.fullName" v-for="item in messageType" />
|
</a-tabs>
|
<ScrollContainer ref="infiniteBody" class="msg-list" v-loading="loading && listQuery.currentPage === 1">
|
<template v-if="list.length">
|
<div v-for="(item, i) in list" :key="i" :title="item.title" class="msg-list-item" @click="readInfo(item)">
|
<div class="msg-list-item-main">
|
<Badge :dot="item.isRead != '1'" status="warning">
|
<i v-if="item.type == 1" class="icon-ym icon-ym-xitong item-icon"></i>
|
<i v-else-if="item.type == 3" class="icon-ym icon-ym-generator-notice item-icon notice-icon"></i>
|
<i v-else-if="item.type == 4" class="icon-ym icon-ym-portal-schedule item-icon schedule-icon"></i>
|
<i v-else class="icon-ym icon-ym icon-ym-generator-flow item-icon flow-icon"></i>
|
</Badge>
|
<div class="msg-list-txt">
|
<p class="title">
|
<span class="title-left">{{ item.title }}</span>
|
</p>
|
<p class="name">
|
<span class="content">{{ item.releaseUser }}</span>
|
<span class="time">{{ toDateValue(item.releaseTime) }}</span>
|
</p>
|
</div>
|
</div>
|
</div>
|
</template>
|
<jnpf-empty v-if="!list.length" />
|
</ScrollContainer>
|
<div class="bottom-box">
|
<div class="bottom-box-item bottom-box-item-left">
|
<a-switch v-model:checked="isNoRead" size="small" @change="onIsReadChange" />
|
只显示未读
|
</div>
|
<div class="bottom-box-item">
|
<a-tooltip title="全部已读">
|
<a-button class="bottom-box-item-btn" type="text" @click="readAll">
|
<i class="icon-ym icon-ym-msg-read-all"></i>
|
</a-button>
|
</a-tooltip>
|
<a-tooltip title="站内消息">
|
<a-button class="bottom-box-item-btn" type="text" @click="gotoCenter">
|
<i class="icon-ym icon-ym-more"></i>
|
</a-button>
|
</a-tooltip>
|
</div>
|
</div>
|
<Detail @register="registerDetail" />
|
<ScheduleDetail @register="registerScheduleDetail" />
|
</BasicDrawer>
|
</template>
|
<style lang="scss">
|
.message-drawer {
|
.ant-drawer-title {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
|
.link-text {
|
font-size: 14px;
|
}
|
}
|
|
.ant-input-search {
|
.ant-input-affix-wrapper {
|
border: none !important;
|
border-radius: 0;
|
|
&.ant-input-affix-wrapper-focused {
|
box-shadow: unset;
|
}
|
}
|
|
.ant-btn {
|
height: 31px;
|
border: none !important;
|
border-radius: 0;
|
}
|
}
|
|
.tool {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
height: 40px;
|
border-bottom: 1px solid var(--border-color-base1);
|
}
|
|
.ant-tabs {
|
.ant-tabs-nav {
|
padding-left: 20px;
|
margin-bottom: 0;
|
}
|
}
|
|
.bottom-box {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
height: 50px;
|
padding: 0 20px;
|
font-size: 14px;
|
border-top: 1px solid var(--border-color-base1);
|
|
.bottom-box-item {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
cursor: pointer;
|
|
&.bottom-box-item-left {
|
color: var(--text-color-label);
|
}
|
|
.ant-switch {
|
margin-right: 10px;
|
}
|
|
.bottom-box-item-btn {
|
width: 30px !important;
|
padding: 0 !important;
|
margin-left: 10px;
|
text-align: center;
|
}
|
}
|
}
|
|
.msg-list {
|
height: calc(100% - 128px);
|
}
|
|
.msg-list-item {
|
position: relative;
|
display: block;
|
padding: 0 20px;
|
background-color: var(--component-background);
|
|
&:hover {
|
background-color: var(--hover-background);
|
}
|
|
.msg-list-item-main {
|
display: flex;
|
align-items: center;
|
height: 60px;
|
cursor: pointer;
|
border-bottom: 1px solid var(--border-color-base1);
|
}
|
|
.item-icon {
|
display: inline-block;
|
width: 36px;
|
height: 36px;
|
font-size: 22px;
|
line-height: 36px;
|
color: #fff;
|
text-align: center;
|
background-color: #1890ff;
|
border-radius: 50%;
|
|
&.flow-icon {
|
background-color: #33cc51;
|
}
|
|
&.notice-icon {
|
background-color: #e09f0c;
|
}
|
|
&.schedule-icon {
|
background-color: #77f;
|
}
|
}
|
|
.msg-list-txt {
|
flex: 1;
|
min-width: 0;
|
padding-top: 1px;
|
margin-left: 14px;
|
overflow: hidden;
|
|
.title {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
height: 20px;
|
margin-bottom: 5px;
|
overflow: auto;
|
overflow: hidden;
|
font-size: 14px;
|
line-height: 20px;
|
|
.title-left {
|
flex: 1;
|
min-width: 0;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
}
|
|
.name {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
height: 18px;
|
font-size: 12px;
|
color: var(--text-color-secondary);
|
|
.content {
|
display: inline-block;
|
flex: 1;
|
min-width: 0;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}
|
|
.time {
|
flex-shrink: 0;
|
margin-left: 5px;
|
}
|
}
|
}
|
}
|
}
|
</style>
|