feat(消息管理): 消息管理页面&接口联调

This commit is contained in:
WangXu10 2024-03-05 14:59:57 +08:00 committed by Craftsman
parent 96def90e47
commit 888687237a
5 changed files with 448 additions and 10 deletions

View File

@ -1,5 +1,7 @@
import MSR from '@/api/http/index';
import { CommonList, TableQueryParams } from '@/models/common';
export interface MessageRecord {
id: number;
type: string;
@ -11,6 +13,7 @@ export interface MessageRecord {
status: 0 | 1;
messageType?: number;
}
export type MessageListType = MessageRecord[];
export function queryMessageList() {
@ -36,3 +39,50 @@ export interface ChatRecord {
export function queryChatList() {
return MSR.post<ChatRecord[]>({ url: '/api/chat/list' });
}
interface MessageHistoryQueryParams extends TableQueryParams {
id: number[];
title: string;
createTime: string;
operator: string;
operation: string;
}
interface historyQueryParams extends Partial<MessageHistoryQueryParams> {
type?: string;
receiver?: string;
status?: string;
resourceType?: string;
}
export interface MessageHistoryItem {
id: number;
type: string;
receiver: string;
subject: string;
status: string;
createTime: string;
operator: string;
operation: string;
resourceId: string;
resourceType: string;
resourceName: string;
content: string;
}
export function queryMessageHistoryList(data: historyQueryParams) {
return MSR.post<CommonList<MessageHistoryItem>>({ url: '/notification/list/all/page', data });
}
export interface OptionItem {
id: string;
name: string;
}
export function queryMessageHistoryCount(data: historyQueryParams) {
return MSR.post<OptionItem[]>({ url: '/notification/count', data });
}
export function getMessageReadAll() {
return MSR.get<number>({ url: '/notification/read/all' });
}

View File

@ -0,0 +1,361 @@
<template>
<MsDrawer
v-model:visible="innerVisible"
:width="880"
:footer="false"
no-content-padding
unmount-on-close
class="ms-drawer"
>
<template #title>
<div class="flex">
<span class="text-[var(--color-text-1)]">{{ t('ms.message.management') }}</span>
<span class="text-[var(--color-text-4)]">{{ t('ms.message.extend') }}</span>
</div>
</template>
<div class="flex h-full w-full">
<div class="h-full w-[180px] bg-white">
<a-menu
class="mr-[16px] w-[180px] min-w-[180px] bg-white p-[4px]"
:default-selected-keys="defaultModule"
@menu-item-click="clickModule"
>
<a-menu-item :key="'all'">
<div class="flex items-center justify-between">
<span>{{ t('ms.message.all') }}</span>
<a-badge class="ml-[4px] text-[var(--color-text-4)]" :text="defaultCount" />
</div>
</a-menu-item>
<a-menu-item v-for="menu of moduleList" :key="menu.type">
<div class="flex items-center justify-between">
<span>{{ menu.name || '' }}</span>
<a-badge class="ml-1" :text="getModuleCount(menu.type)" />
</div>
</a-menu-item>
</a-menu>
</div>
<a-divider direction="vertical" margin="8px"></a-divider>
<div class="flex-1 justify-between p-[24px]">
<a-radio-group v-model="position" type="button" @change="changeShowType">
<a-radio value="all">{{ t('ms.message.list.all') }}</a-radio>
<a-radio value="mentioned_me">{{ t('ms.message.list.me', { var: '@' }) }}</a-radio>
<a-radio value="unRead">{{ t('ms.message.list.unRead') }}</a-radio>
<a-radio value="read">{{ t('ms.message.list.read') }}</a-radio>
</a-radio-group>
<a-button type="text" class="right-align" @click="prepositionEdit">
<MsIcon type="icon-icon_logs_outlined" class="mr-1 font-[16px] text-[rgb(var(--primary-5))]" />
{{ t('ms.message.make.as.read') }}
</a-button>
<div class="mt-[26px] flex h-[calc(100%-150px)]">
<MsList
v-model:data="messageHistoryList"
mode="remote"
item-key-field="id"
:item-border="false"
class="w-full rounded-[var(--border-radius-small)]"
:no-more-data="noMoreData"
raggable
@reach-bottom="handleReachBottom"
>
<template #item="{ item }">
<span class="p-[23px]">
<div v-if="item.type === 'MENTIONED_ME'">
<div class="flex items-center">
<MSAvatar :avatar="item.avatar" />
<div class="ml-[8px] flex">
<div class="font-medium text-[var(--color-text-1)]">{{ item.userName }}</div>
<div class="font-medium text-[rgb(var(--primary-5))]"
>&nbsp;&nbsp;{{ t('ms.message.me', { var: '@' }) }}</div
>
</div>
</div>
<div class="ml-[50px] flex items-center">
<div class="font-medium text-[var(--color-text-2)]">{{ item.userName }}&nbsp;&nbsp;</div>
<div class="font-medium text-[var(--color-text-2)]">{{ item.subject }}</div>
<MsButton @click="handleNameClick(item)">
<div class="one-line-text">
{{ item.resourceName }}
</div>
</MsButton>
</div>
<div class="ml-[50px] flex items-center">
{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}
</div>
</div>
<div v-else>
<div class="flex items-center">
<div class="font-medium text-[var(--color-text-1)]">{{ t('ms.message.notice.title') }}</div>
</div>
<div class="flex items-center">
<div class="font-medium text-[var(--color-text-2)]">{{ item.userName }}&nbsp;&nbsp;</div>
<div class="font-medium text-[var(--color-text-2)]">{{ item.subject }}</div>
<MsButton @click="handleNameClick(item)">
<div class="one-line-text">
{{ item.resourceName }}
</div>
</MsButton>
</div>
<div class="flex items-center">
{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}
</div>
</div>
</span>
</template>
</MsList>
</div>
</div>
</div>
</MsDrawer>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useVModel } from '@vueuse/core';
import dayjs from 'dayjs';
import MSAvatar from '@/components/pure/ms-avatar/index.vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsList from '@/components/pure/ms-list/index.vue';
import {
getMessageReadAll,
MessageHistoryItem,
OptionItem,
queryMessageHistoryCount,
queryMessageHistoryList,
} from '@/api/modules/message';
import { getMessageList } from '@/api/modules/project-management/messageManagement';
import { useI18n } from '@/hooks/useI18n';
import usePathMap from '@/hooks/usePathMap';
import { MessageItem } from '@/models/projectManagement/message';
import useAppStore from '../../../store/modules/app';
import useUserStore from '../../../store/modules/user';
const options = ref<OptionItem[]>([]);
const messageHistoryList = ref<MessageHistoryItem[]>([]);
const appStore = useAppStore();
const userStore = useUserStore();
const projectId = ref<string>(appStore.currentProjectId);
const props = defineProps<{
visible: boolean;
}>();
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void;
}>();
const { t } = useI18n();
const { jumpRouteByMapKey } = usePathMap();
const innerVisible = useVModel(props, 'visible', emit);
const position = ref('all');
const noMoreData = ref<boolean>(false);
const moduleList = ref<MessageItem[]>([]);
const defaultModule = ref<string>('all');
const defaultCount = ref<string>('0');
const currentResourceType = ref<string>('');
const pageNation = ref({
total: 0,
pageSize: 10,
current: 1,
});
//
async function prepositionEdit() {
await getMessageReadAll();
}
//
async function loadModuleList() {
const list = await getMessageList({ projectId: projectId.value });
moduleList.value = list;
}
//
async function loadMessageHistoryList(val: string, resourceType: string) {
let type = val;
let status = val;
if (val === 'all') {
type = '';
status = '';
} else if (val === 'mentioned_me') {
type = 'MENTIONED_ME';
status = '';
} else if (val === 'unRead') {
type = '';
status = 'UNREAD';
} else if (val === 'read') {
type = '';
status = 'READ';
} else {
type = '';
status = '';
}
const res = await queryMessageHistoryList({
type,
status,
resourceType,
receiver: userStore.id,
current: pageNation.value.current || 1,
pageSize: pageNation.value.pageSize,
});
res.list.forEach((item) => messageHistoryList.value.push(item));
pageNation.value.total = res.total;
}
// count
async function loadTotalCount(key: string) {
const res = await queryMessageHistoryCount({
resourceType: key,
receiver: userStore.id,
current: 1,
pageSize: 10,
});
options.value = res;
const find = options.value.find((item) => item.id === 'total');
if (find) {
const total = parseInt(find.name, 10);
if (total > 99) {
defaultCount.value = '+99';
} else {
defaultCount.value = find.name;
}
}
}
// count
async function loadModuleCount(key: string) {
const res = await queryMessageHistoryCount({
resourceType: key,
receiver: userStore.id,
current: 1,
pageSize: 10,
});
return res;
}
//
function clickModule(key: string) {
if (key === 'BUG_MANAGEMENT') {
key = 'BUG';
} else if (key === 'CASE_MANAGEMENT') {
key = 'CASE';
} else if (key === 'API_TEST_MANAGEMENT') {
key = 'API';
} else if (key === 'SCHEDULE_TASK_MANAGEMENT') {
key = 'SCHEDULE';
} else {
key = '';
}
position.value = 'all';
messageHistoryList.value = [];
pageNation.value.current = 1;
currentResourceType.value = key;
loadMessageHistoryList('all', key);
}
//
function changeShowType(value: string | number | boolean, ev: Event) {
messageHistoryList.value = [];
pageNation.value.current = 1;
loadMessageHistoryList(value as string, currentResourceType.value);
}
//
function handleReachBottom() {
pageNation.value.current += 1;
if (pageNation.value.current > Math.ceil(pageNation.value.total / pageNation.value.pageSize)) {
return;
}
loadMessageHistoryList(position.value, currentResourceType.value);
}
function getModuleCount(type: string) {
let count = '0';
if (type === 'BUG_MANAGEMENT') {
const module = options.value.find((item) => item.id === 'BUG');
if (module) {
count = module.name;
}
}
if (type === 'CASE_MANAGEMENT') {
const module = options.value.find((item) => item.id === 'CASE');
if (module) {
count = module.name;
}
}
if (type === 'API_TEST_MANAGEMENT') {
const module = options.value.find((item) => item.id === 'API');
if (module) {
count = module.name;
}
}
if (type === 'SCHEDULE_TASK_MANAGEMENT') {
const module = options.value.find((item) => item.id === 'SCHEDULE');
if (module) {
count = module.name;
}
}
const number = parseInt(count, 10);
if (number > 99) {
return '+99';
}
return count;
}
//
function handleNameClick(item: MessageHistoryItem) {
console.log('开始跳转了', item);
/**
* const routeQuery: Record<string, any> = {
* organizationId: item.organizationId,
* projectId: item.projectId,
* id: item.resourceId,
* };
* if (item.organizationId === 'SYSTEM') {
* delete routeQuery.organizationId;
* }
* if (item.projectId === 'SYSTEM' || item.projectId === 'ORGANIZATION') {
* delete routeQuery.projectId;
* }
* jumpRouteByMapKey(item.module, routeQuery, true);
*/
}
watch(
() => props.visible,
(val) => {
if (val) {
messageHistoryList.value = [];
pageNation.value.current = 1;
//
loadModuleList();
//
loadMessageHistoryList(position.value, '');
//
loadTotalCount('');
}
}
);
</script>
<style lang="less" scoped>
.right-align {
float: right;
}
:deep(.arco-list) {
display: flex;
flex-direction: column;
box-sizing: border-box;
width: 100%;
overflow-y: auto;
color: var(--color-text-1);
font-size: 14px;
line-height: 1.8715;
border-radius: var(--border-radius-medium);
}
</style>

View File

@ -0,0 +1,15 @@
export default {
'ms.message.management': 'Message management',
'ms.message.extend': '(Only display internal news within the past 3 months)',
'ms.message.all': 'All messages',
'ms.message.bug': 'Bug',
'ms.message.case': 'Case',
'ms.message.api': 'Api',
'ms.message.list.all': 'All',
'ms.message.list.me': '{var}My',
'ms.message.list.unRead': 'Unread',
'ms.message.list.read': 'Read',
'ms.message.make.as.read': 'Mark all as read',
'ms.message.notice.title': 'Notification Title',
'ms.message.me': ' {var}Me',
};

View File

@ -0,0 +1,15 @@
export default {
'ms.message.management': '消息管理',
'ms.message.extend': '(仅展示近3个月内站内消息)',
'ms.message.all': '全部消息',
'ms.message.bug': '缺陷管理',
'ms.message.case': '用例管理',
'ms.message.api': '接口测试',
'ms.message.list.all': '全部',
'ms.message.list.me': '{var}我的',
'ms.message.list.unRead': '未读',
'ms.message.list.read': '已读',
'ms.message.make.as.read': '全部标为已读',
'ms.message.notice.title': '通知标题',
'ms.message.me': ' {var}我',
};

View File

@ -53,7 +53,7 @@
<a-tooltip :content="t('settings.navbar.alerts')">
<div class="message-box-trigger">
<a-badge :count="9" dot>
<a-button type="secondary" @click="setPopoverVisible">
<a-button type="secondary" @click="goMessageCenter">
<template #icon>
<icon-notification />
</template>
@ -136,6 +136,7 @@
</ul>
</div>
<TaskCenterModal v-model:visible="taskCenterVisible" />
<MessageCenterDrawer v-model:visible="messageCenterVisible" />
</template>
<script lang="ts" setup>
@ -144,8 +145,8 @@
import { useClipboard } from '@vueuse/core';
import { Message } from '@arco-design/web-vue';
import MessageCenterDrawer from '@/components/business/ms-message/MessageCenterDrawer.vue';
import TopMenu from '@/components/business/ms-top-menu/index.vue';
import MessageBox from '../message-box/index.vue';
import TaskCenterModal from './taskCenterModal.vue';
import { switchProject } from '@/api/modules/project-management/project';
@ -231,19 +232,15 @@
const locales = [...LOCALE_OPTIONS];
const refBtn = ref();
const setPopoverVisible = () => {
const event = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true,
});
refBtn.value.dispatchEvent(event);
};
const messageCenterVisible = ref<boolean>(false);
const taskCenterVisible = ref<boolean>(false);
function goTaskCenter() {
taskCenterVisible.value = true;
}
function goMessageCenter() {
messageCenterVisible.value = true;
}
</script>
<style scoped lang="less">