Browse Source

feat(notifications): 增加我的消息管理

pull/1090/head
colin 1 year ago
parent
commit
6bd70b911c
  1. 4
      apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json
  2. 4
      apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json
  3. 20
      apps/vben5/apps/app-antd/src/router/routes/modules/abp.ts
  4. 15
      apps/vben5/apps/app-antd/src/views/notifications/my-notifilers/index.vue
  5. 2
      apps/vben5/packages/@abp/notifications/src/api/index.ts
  6. 57
      apps/vben5/packages/@abp/notifications/src/api/useMyNotifilersApi.ts
  7. 76
      apps/vben5/packages/@abp/notifications/src/api/useNotificationsApi.ts
  8. 1
      apps/vben5/packages/@abp/notifications/src/components/index.ts
  9. 337
      apps/vben5/packages/@abp/notifications/src/components/my-notifilers/MyNotificationTable.vue
  10. 25
      apps/vben5/packages/@abp/notifications/src/types/definitions.ts
  11. 1
      apps/vben5/packages/@abp/notifications/src/types/index.ts
  12. 48
      apps/vben5/packages/@abp/notifications/src/types/my-notifilers.ts
  13. 33
      apps/vben5/packages/@abp/notifications/src/types/notifications.ts

4
apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json

@ -31,6 +31,10 @@
"title": "Settings", "title": "Settings",
"definitions": "Definitions", "definitions": "Definitions",
"system": "System Settings" "system": "System Settings"
},
"notifications": {
"title": "Notifications",
"myNotifilers": "My Notifilers"
} }
}, },
"openiddict": { "openiddict": {

4
apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json

@ -31,6 +31,10 @@
"title": "设置管理", "title": "设置管理",
"definitions": "设置定义", "definitions": "设置定义",
"system": "系统设置" "system": "系统设置"
},
"notifications": {
"title": "通知管理",
"myNotifilers": "我的通知"
} }
}, },
"openiddict": { "openiddict": {

20
apps/vben5/apps/app-antd/src/router/routes/modules/abp.ts

@ -157,6 +157,26 @@ const routes: RouteRecordRaw[] = [
path: '/manage/audit-logs', path: '/manage/audit-logs',
component: () => import('#/views/auditing/audit-logs/index.vue'), component: () => import('#/views/auditing/audit-logs/index.vue'),
}, },
{
meta: {
title: $t('abp.manage.notifications.title'),
icon: 'tabler:notification',
},
name: 'Notifications',
path: '/manage/notifications',
children: [
{
meta: {
title: $t('abp.manage.notifications.myNotifilers'),
icon: 'ant-design:notification-outlined',
},
name: 'MyNotifications',
path: '/manage/notifications/my-notifilers',
component: () =>
import('#/views/notifications/my-notifilers/index.vue'),
},
],
},
], ],
}, },
{ {

15
apps/vben5/apps/app-antd/src/views/notifications/my-notifilers/index.vue

@ -0,0 +1,15 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import { MyNotificationTable } from '@abp/notifications';
defineOptions({
name: 'MyNotifications',
});
</script>
<template>
<Page>
<MyNotificationTable />
</Page>
</template>

2
apps/vben5/packages/@abp/notifications/src/api/index.ts

@ -0,0 +1,2 @@
export { useMyNotifilersApi } from './useMyNotifilersApi';
export { useNotificationsApi } from './useNotificationsApi';

57
apps/vben5/packages/@abp/notifications/src/api/useMyNotifilersApi.ts

@ -0,0 +1,57 @@
import type { PagedResultDto } from '@abp/core';
import type {
GetMyNotifilerPagedListInput,
MarkReadStateInput,
UserNotificationDto,
} from '../types/my-notifilers';
import { useRequest } from '@abp/request';
export function useMyNotifilersApi() {
const { cancel, request } = useRequest();
/**
*
* @param {GetMyNotifilerPagedListInput} input
* @returns {Promise<PagedResultDto<UserNotificationDto>>}
*/
function getMyNotifilersApi(
input?: GetMyNotifilerPagedListInput,
): Promise<PagedResultDto<UserNotificationDto>> {
return request<PagedResultDto<UserNotificationDto>>(
'/api/notifications/my-notifilers',
{
method: 'GET',
params: input,
},
);
}
/**
*
* @param {string} id id
* @returns {void}
*/
function deleteMyNotifilerApi(id: string): Promise<void> {
return request(`/api/notifications/my-notifilers/${id}`, {
method: 'DELETE',
});
}
/**
*
* @param {MarkReadStateInput} input
* @returns {void}
*/
function markReadStateApi(input: MarkReadStateInput): Promise<void> {
return request(`/api/notifications/my-notifilers/mark-read-state`, {
data: input,
method: 'PUT',
});
}
return {
cancel,
deleteMyNotifilerApi,
getMyNotifilersApi,
markReadStateApi,
};
}

76
apps/vben5/packages/@abp/notifications/src/api/useNotificationsApi.ts

@ -0,0 +1,76 @@
import type { ListResultDto } from '@abp/core';
import type {
NotificationGroupDto,
NotificationTemplateDto,
} from '../types/definitions';
import type {
NotificationSendInput,
NotificationTemplateSendInput,
} from '../types/notifications';
import { useRequest } from '@abp/request';
export function useNotificationsApi() {
const { cancel, request } = useRequest();
/**
*
* @returns {Promise<ListResultDto<NotificationGroupDto>>}
*/
function getAssignableNotifiersApi(): Promise<
ListResultDto<NotificationGroupDto>
> {
return request<ListResultDto<NotificationGroupDto>>(
'/api/notifications/assignables',
{
method: 'GET',
},
);
}
/**
*
* @returns {Promise<ListResultDto<NotificationTemplateDto>>}
*/
function getAssignableTemplatesApi(): Promise<
ListResultDto<NotificationTemplateDto>
> {
return request<ListResultDto<NotificationTemplateDto>>(
'/api/notifications/assignable-templates',
{
method: 'GET',
},
);
}
/**
*
* @param input
* @returns {Promise<void>}
*/
function sendNotiferApi(input: NotificationSendInput): Promise<void> {
return request('/api/notifications/send', {
data: input,
method: 'POST',
});
}
/**
*
* @param input
* @returns {Promise<void>}
*/
function sendTemplateNotiferApi(
input: NotificationTemplateSendInput,
): Promise<void> {
return request('/api/notifications/send/template', {
data: input,
method: 'POST',
});
}
return {
cancel,
getAssignableNotifiersApi,
getAssignableTemplatesApi,
sendNotiferApi,
sendTemplateNotiferApi,
};
}

1
apps/vben5/packages/@abp/notifications/src/components/index.ts

@ -0,0 +1 @@
export { default as MyNotificationTable } from './my-notifilers/MyNotificationTable.vue';

337
apps/vben5/packages/@abp/notifications/src/components/my-notifilers/MyNotificationTable.vue

@ -0,0 +1,337 @@
<script setup lang="ts">
import type { VbenFormProps, VxeGridListeners, VxeGridProps } from '@abp/ui';
import type { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import type { Notification } from '../../types/notifications';
import { h, ref } from 'vue';
import { createIconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { formatToDateTime } from '@abp/core';
import { useVbenVxeGrid } from '@abp/ui';
import { DeleteOutlined, DownOutlined } from '@ant-design/icons-vue';
import { Button, Dropdown, Menu, message, Modal } from 'ant-design-vue';
import { useMyNotifilersApi } from '../../api/useMyNotifilersApi';
import { useNotificationSerializer } from '../../hooks';
import {
NotificationReadState,
NotificationType,
} from '../../types/notifications';
defineOptions({
name: 'MyNotificationTable',
});
const { cancel, deleteMyNotifilerApi, getMyNotifilersApi, markReadStateApi } =
useMyNotifilersApi();
const { deserialize } = useNotificationSerializer();
const MenuItem = Menu.Item;
const ReadIcon = createIconifyIcon('ic:outline-mark-email-read');
const UnReadIcon = createIconifyIcon('ic:outline-mark-email-unread');
const BookMarkIcon = createIconifyIcon('material-symbols:bookmark-outline');
const selectedKeys = ref<string[]>([]);
const formOptions: VbenFormProps = {
//
collapsed: false,
schema: [
{
component: 'Select',
componentProps: {
allowClear: true,
options: [
{
label: $t('Notifications.Read'),
value: NotificationReadState.Read,
},
{
label: $t('Notifications.UnRead'),
value: NotificationReadState.UnRead,
},
],
},
defaultValue: NotificationReadState.UnRead,
fieldName: 'readState',
label: $t('Notifications.Notifications:State'),
},
{
component: 'Input',
componentProps: {
allowClear: true,
},
fieldName: 'filter',
formItemClass: 'col-span-2 items-baseline',
label: $t('AbpUi.Search'),
},
],
//
showCollapseButton: true,
//
submitOnEnter: true,
};
const gridOptions: VxeGridProps<Notification> = {
columns: [
{
align: 'center',
type: 'checkbox',
width: 40,
},
{
align: 'left',
field: 'type',
formatter({ cellValue }) {
const type = cellValue as NotificationType;
switch (type) {
case NotificationType.Application: {
return $t('Notifications.NotificationType:Application');
}
case NotificationType.ServiceCallback: {
return $t('Notifications.NotificationType:ServiceCallback');
}
case NotificationType.System: {
return $t('Notifications.NotificationType:System');
}
case NotificationType.User: {
return $t('Notifications.NotificationType:User');
}
}
},
minWidth: 50,
title: $t('Notifications.Notifications:Type'),
},
{
align: 'left',
field: 'creationTime',
formatter({ cellValue }) {
return cellValue ? formatToDateTime(cellValue) : '';
},
minWidth: 120,
title: $t('Notifications.Notifications:SendTime'),
},
{
align: 'left',
field: 'title',
minWidth: 150,
slots: { default: 'title' },
title: $t('Notifications.Notifications:Title'),
},
{
align: 'left',
field: 'message',
minWidth: 120,
slots: { default: 'message' },
title: $t('Notifications.Notifications:Content'),
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: $t('AbpUi.Actions'),
width: 200,
},
],
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
const { totalCount, items } = await getMyNotifilersApi({
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
});
return {
totalCount,
items: items.map((item) => {
const notification = deserialize(item);
return {
...notification,
id: item.id,
state: item.state,
};
}),
};
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
zoom: true,
},
};
const gridEvents: VxeGridListeners<Notification> = {
checkboxAll: (params) => {
selectedKeys.value = params.records.map((record) => record.id);
},
checkboxChange: (params) => {
selectedKeys.value = params.records.map((record) => record.id);
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridEvents,
gridOptions,
});
/** 点击行标记已读 */
async function onClickRead(row: Notification) {
await _onRead([row.id], NotificationReadState.Read);
}
/** 行标记阅读状态 */
async function onRowRead(row: Notification, info: MenuInfo) {
switch (info.key) {
case 'read': {
return await _onRead([row.id], NotificationReadState.Read);
}
case 'un-read': {
return await _onRead([row.id], NotificationReadState.UnRead);
}
}
}
/** 批量标记阅读状态 */
async function onBulkRead(info: MenuInfo) {
switch (info.key) {
case 'read': {
await _onRead(selectedKeys.value, NotificationReadState.Read);
break;
}
case 'un-read': {
await _onRead(selectedKeys.value, NotificationReadState.UnRead);
break;
}
}
selectedKeys.value = [];
}
async function _onRead(idList: string[], state: NotificationReadState) {
try {
gridApi.setLoading(true);
await markReadStateApi({
idList,
state,
});
await gridApi.reload();
} finally {
gridApi.setLoading(false);
}
}
const onDelete = (row: Notification) => {
Modal.confirm({
centered: true,
content: $t('AbpUi.ItemWillBeDeletedMessageWithFormat', [row.title]),
onCancel: () => {
cancel();
},
onOk: async () => {
await deleteMyNotifilerApi(row.id);
message.success($t('AbpUi.SuccessfullyDeleted'));
await gridApi.query();
},
title: $t('AbpUi.AreYouSure'),
});
};
</script>
<template>
<Grid :table-title="$t('Notifications.Notifications')">
<template #toolbar-tools>
<Dropdown v-if="selectedKeys.length > 0">
<template #overlay>
<Menu @click="(info) => onBulkRead(info)">
<MenuItem key="read">
<div class="flex flex-row items-center gap-[4px]">
<ReadIcon color="#00DD00" />
{{ $t('Notifications.Read') }}
</div>
</MenuItem>
<MenuItem key="un-read">
<div class="flex flex-row items-center gap-[4px]">
<UnReadIcon color="#FF7744" />
{{ $t('Notifications.UnRead') }}
</div>
</MenuItem>
</Menu>
</template>
<Button>
<div class="flex flex-row items-center gap-[4px]">
<BookMarkIcon />
{{ $t('Notifications.MarkAs') }}
<DownOutlined />
</div>
</Button>
</Dropdown>
</template>
<template #title="{ row }">
<div class="flex flex-row items-center gap-[4px]">
<ReadIcon
v-if="row.state === NotificationReadState.Read"
class="size-5"
color="#00DD00"
/>
<UnReadIcon v-else class="size-5" color="#FF7744" />
<a href="javascript:(0);" @click="onClickRead(row)">{{ row.title }}</a>
</div>
</template>
<template #message="{ row }">
<a href="javascript:(0);" @click="onClickRead(row)">{{ row.message }}</a>
</template>
<template #action="{ row }">
<div class="flex flex-row">
<Button
:icon="h(DeleteOutlined)"
danger
type="link"
@click="onDelete(row)"
>
{{ $t('AbpUi.Delete') }}
</Button>
<Dropdown>
<template #overlay>
<Menu @click="(info) => onRowRead(row, info)">
<MenuItem key="read">
<div class="flex flex-row items-center gap-[4px]">
<ReadIcon color="#00DD00" />
{{ $t('Notifications.Read') }}
</div>
</MenuItem>
<MenuItem key="un-read">
<div class="flex flex-row items-center gap-[4px]">
<UnReadIcon color="#FF7744" />
{{ $t('Notifications.UnRead') }}
</div>
</MenuItem>
</Menu>
</template>
<Button type="link">
<div class="flex flex-row items-center gap-[4px]">
<BookMarkIcon />
{{ $t('Notifications.MarkAs') }}
<DownOutlined />
</div>
</Button>
</Dropdown>
</div>
</template>
</Grid>
</template>
<style lang="scss" scoped></style>

25
apps/vben5/packages/@abp/notifications/src/types/definitions.ts

@ -0,0 +1,25 @@
import type { NotificationLifetime, NotificationType } from './notifications';
interface NotificationDto {
description: string;
displayName: string;
lifetime: NotificationLifetime;
name: string;
type: NotificationType;
}
interface NotificationGroupDto {
displayName: string;
name: string;
notifications: NotificationDto[];
}
interface NotificationTemplateDto {
content?: string;
culture?: string;
description?: string;
name: string;
title: string;
}
export type { NotificationDto, NotificationGroupDto, NotificationTemplateDto };

1
apps/vben5/packages/@abp/notifications/src/types/index.ts

@ -1 +1,2 @@
export * from './my-notifilers';
export * from './notifications'; export * from './notifications';

48
apps/vben5/packages/@abp/notifications/src/types/my-notifilers.ts

@ -0,0 +1,48 @@
import type { PagedAndSortedResultRequestDto } from '@abp/core';
import type {
NotificationContentType,
NotificationData,
NotificationLifetime,
NotificationReadState,
NotificationSeverity,
NotificationType,
} from './notifications';
interface GetMyNotifilerPagedListInput extends PagedAndSortedResultRequestDto {
filter?: string;
/** 已读状态 */
readState?: NotificationReadState;
}
interface MarkReadStateInput {
idList: string[];
state: NotificationReadState;
}
interface UserNotificationDto {
/** 内容类型 */
contentType: NotificationContentType;
/** 创建时间 */
creationTime: Date;
/** 数据 */
data: NotificationData;
/** Id */
id: string;
/** 生命周期 */
lifetime: NotificationLifetime;
/** 名称 */
name: string;
/** 紧急程度 */
severity: NotificationSeverity;
/** 阅读状态 */
state: NotificationReadState;
/** 类型 */
type: NotificationType;
}
export type {
GetMyNotifilerPagedListInput,
MarkReadStateInput,
UserNotificationDto,
};

33
apps/vben5/packages/@abp/notifications/src/types/notifications.ts

@ -43,39 +43,41 @@ interface UserIdentifier {
} }
interface NotificationSendInput { interface NotificationSendInput {
/** 当前文化(模板渲染时需要) */
culture?: string; culture?: string;
/** 通知数据 */
data: Dictionary<string, any>; data: Dictionary<string, any>;
/** 通知(模板通知时为模板名称)名称 */
name: string; name: string;
/** 紧急程度 */
severity?: NotificationSeverity; severity?: NotificationSeverity;
/** 接收人列表 */
toUsers?: UserIdentifier[]; toUsers?: UserIdentifier[];
} }
type NotificationTemplateSendInput = NotificationSendInput;
interface NotificationInfo { interface NotificationInfo {
/** 内容类型 */
contentType: NotificationContentType; contentType: NotificationContentType;
/** 创建时间 */
creationTime: Date; creationTime: Date;
/** 数据 */
data: NotificationData; data: NotificationData;
/** Id */
id: string; id: string;
/** 生命周期 */
lifetime: NotificationLifetime; lifetime: NotificationLifetime;
/** 名称 */
name: string; name: string;
/** 紧急程度 */
severity: NotificationSeverity; severity: NotificationSeverity;
/** 类型 */
type: NotificationType; type: NotificationType;
} }
interface NotificationDto {
description: string;
displayName: string;
lifetime: NotificationLifetime;
name: string;
type: NotificationType;
}
interface NotificationGroupDto {
displayName: string;
name: string;
notifications: NotificationDto[];
}
interface Notification { interface Notification {
[key: string]: any;
contentType: NotificationContentType; contentType: NotificationContentType;
creationTime: Date; creationTime: Date;
data: Record<string, any>; data: Record<string, any>;
@ -91,8 +93,7 @@ interface Notification {
export type { export type {
Notification, Notification,
NotificationData, NotificationData,
NotificationDto,
NotificationGroupDto,
NotificationInfo, NotificationInfo,
NotificationSendInput, NotificationSendInput,
NotificationTemplateSendInput,
}; };

Loading…
Cancel
Save