feat: header上的通知及个人站内信功能
parent
8b3b842655
commit
0e44af89b9
|
@ -28,5 +28,5 @@ export function getUnreadNotifyMessageList() {
|
||||||
|
|
||||||
// 获得当前用户的未读站内信数量
|
// 获得当前用户的未读站内信数量
|
||||||
export function getUnreadNotifyMessageCount() {
|
export function getUnreadNotifyMessageCount() {
|
||||||
return defHttp.get({ url: '/system/notify-message/get-unread-count' })
|
return defHttp.get<number>({ url: '/system/notify-message/get-unread-count' })
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,5 +6,6 @@ export enum PageEnum {
|
||||||
// error page path
|
// error page path
|
||||||
ERROR_PAGE = '/exception',
|
ERROR_PAGE = '/exception',
|
||||||
// error log page path
|
// error log page path
|
||||||
ERROR_LOG_PAGE = '/error-log/list'
|
ERROR_LOG_PAGE = '/error-log/list',
|
||||||
|
MESSAGE_PAGE = '/profile/notify-message'
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,80 +1,35 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="prefixCls">
|
<div>
|
||||||
<Popover title="" trigger="click" :overlayClassName="`${prefixCls}__overlay`">
|
<Tooltip :title="tips">
|
||||||
<Badge :count="count" dot :numberStyle="numberStyle">
|
<Badge :count="unreadCount" :offset="[0, 15]" size="small" @click="go({ path: PageEnum.MESSAGE_PAGE })">
|
||||||
<BellOutlined />
|
<BellOutlined />
|
||||||
</Badge>
|
</Badge>
|
||||||
<template #content>
|
</Tooltip>
|
||||||
<Tabs>
|
|
||||||
<template v-for="item in listData" :key="item.key">
|
|
||||||
<TabPane>
|
|
||||||
<template #tab>
|
|
||||||
{{ item.name }}
|
|
||||||
<span v-if="item.list.length !== 0">({{ item.list.length }})</span>
|
|
||||||
</template>
|
|
||||||
<!-- 绑定title-click事件的通知列表中标题是“可点击”的-->
|
|
||||||
<NoticeList :list="item.list" v-if="item.key === '1'" @title-click="onNoticeClick" />
|
|
||||||
<NoticeList :list="item.list" v-else />
|
|
||||||
</TabPane>
|
|
||||||
</template>
|
|
||||||
</Tabs>
|
|
||||||
</template>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue'
|
import { onMounted, computed } from 'vue'
|
||||||
import { Popover, Tabs, Badge } from 'ant-design-vue'
|
import { Badge, Tooltip } from 'ant-design-vue'
|
||||||
import { BellOutlined } from '@ant-design/icons-vue'
|
import { BellOutlined } from '@ant-design/icons-vue'
|
||||||
import { tabListData, ListItem } from './data'
|
import { useGo } from '@/hooks/web/usePage'
|
||||||
import NoticeList from './NoticeList.vue'
|
import { PageEnum } from '@/enums/pageEnum'
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
import { useUserMessageStore } from '@/store/modules/userMessage'
|
||||||
import { useMessage } from '@/hooks/web/useMessage'
|
import { storeToRefs } from 'pinia'
|
||||||
|
|
||||||
const TabPane = Tabs.TabPane
|
const go = useGo()
|
||||||
const numberStyle = ref({})
|
|
||||||
const { prefixCls } = useDesign('header-notify')
|
|
||||||
const { createMessage } = useMessage()
|
|
||||||
const listData = ref(tabListData)
|
|
||||||
|
|
||||||
const count = computed(() => {
|
const store = useUserMessageStore()
|
||||||
let count = 0
|
const { unreadCount } = storeToRefs(store)
|
||||||
for (let i = 0; i < tabListData.length; i++) {
|
const tips = computed<string>(() => {
|
||||||
count += tabListData[i].list.length
|
if (unreadCount.value === 0) {
|
||||||
|
return '查看站内信'
|
||||||
}
|
}
|
||||||
return count
|
return `查看站内信: 未读 ${unreadCount.value} 条`
|
||||||
})
|
})
|
||||||
|
|
||||||
function onNoticeClick(record: ListItem) {
|
onMounted(async () => {
|
||||||
createMessage.success('你点击了通知,ID=' + record.id)
|
// 通过store进行更新
|
||||||
// 可以直接将其标记为已读(为标题添加删除线),此处演示的代码会切换删除线状态
|
store.updateUnreadCount()
|
||||||
record.titleDelete = !record.titleDelete
|
})
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="less">
|
<style lang="less"></style>
|
||||||
@prefix-cls: ~'@{namespace}-header-notify';
|
|
||||||
|
|
||||||
.@{prefix-cls} {
|
|
||||||
padding-top: 2px;
|
|
||||||
|
|
||||||
&__overlay {
|
|
||||||
max-width: 360px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-tabs-content {
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-badge {
|
|
||||||
font-size: 18px;
|
|
||||||
|
|
||||||
.ant-badge-multiple-words {
|
|
||||||
padding: 0 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: 0.9em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { getUnreadNotifyMessageCount } from '@/api/system/notify/message'
|
||||||
|
|
||||||
|
type MessageState = {
|
||||||
|
unreadCount: number // 未读消息数量
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUserMessageStore = defineStore('userMessage', {
|
||||||
|
state: (): MessageState => ({
|
||||||
|
unreadCount: 0
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
getUnreadCount(state) {
|
||||||
|
return state.unreadCount
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
// 更新未读消息的数量
|
||||||
|
async updateUnreadCount() {
|
||||||
|
const count = await getUnreadNotifyMessageCount()
|
||||||
|
this.unreadCount = count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -53,6 +53,7 @@ export function getDictOpts(dictType: string) {
|
||||||
export function getDictOptions(dictType: string, valueType?: 'string' | 'number' | 'boolean') {
|
export function getDictOptions(dictType: string, valueType?: 'string' | 'number' | 'boolean') {
|
||||||
const dictOption: DictDataType[] = []
|
const dictOption: DictDataType[] = []
|
||||||
const dictOptions: DictDataType[] = getDictDatas(dictType)
|
const dictOptions: DictDataType[] = getDictDatas(dictType)
|
||||||
|
console.log(dictOptions)
|
||||||
if (dictOptions && dictOptions.length > 0) {
|
if (dictOptions && dictOptions.length > 0) {
|
||||||
dictOptions.forEach((dict: DictDataType) => {
|
dictOptions.forEach((dict: DictDataType) => {
|
||||||
dictOption.push({
|
dictOption.push({
|
||||||
|
|
|
@ -93,6 +93,7 @@ function handleMaster(record: Recordable) {
|
||||||
async onOk() {
|
async onOk() {
|
||||||
await updateFileConfigMaster(record.id)
|
await updateFileConfigMaster(record.id)
|
||||||
createMessage.success('配置成功')
|
createMessage.success('配置成功')
|
||||||
|
reload()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<BasicModal title="详情" @register="innerRegister">
|
<BasicModal title="站内信详情" @register="innerRegister">
|
||||||
<Description @register="descriptionRegister" />
|
<Description @register="descriptionRegister" />
|
||||||
</BasicModal>
|
</BasicModal>
|
||||||
</template>
|
</template>
|
|
@ -0,0 +1,97 @@
|
||||||
|
import { useRender } from '@/components/Table'
|
||||||
|
import { DICT_TYPE } from '@/utils/dict'
|
||||||
|
import { JsonPreview } from '@/components/CodeEditor'
|
||||||
|
import { DescItem } from '@/components/Description/index'
|
||||||
|
import { h } from 'vue'
|
||||||
|
|
||||||
|
// 站内信详情modal
|
||||||
|
export const infoSchema: DescItem[] = [
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
label: '编号',
|
||||||
|
labelMinWidth: 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'readStatus',
|
||||||
|
label: '是否已读',
|
||||||
|
render: (value) => {
|
||||||
|
return useRender.renderDict(value, DICT_TYPE.INFRA_BOOLEAN_STRING)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'userType',
|
||||||
|
label: '用户类型',
|
||||||
|
render: (value) => {
|
||||||
|
console.log(value)
|
||||||
|
return useRender.renderDict(value, DICT_TYPE.USER_TYPE)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'userType',
|
||||||
|
label: '用户编号'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'templateId',
|
||||||
|
label: '模板编号'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'templateCode',
|
||||||
|
label: '模板编码'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'templateNickname',
|
||||||
|
label: '发送人名称'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'templateContent',
|
||||||
|
label: '模板内容'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'templateParams',
|
||||||
|
label: '模板参数',
|
||||||
|
render: (value) => {
|
||||||
|
return h(JsonPreview, { data: value })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'templateType',
|
||||||
|
label: '模板类型',
|
||||||
|
render: (value) => {
|
||||||
|
return useRender.renderDict(value, DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'readTime',
|
||||||
|
label: '阅读时间',
|
||||||
|
render: (value) => {
|
||||||
|
if (!value) {
|
||||||
|
return useRender.renderTag('未阅读')
|
||||||
|
}
|
||||||
|
return useRender.renderDate(value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'createTime',
|
||||||
|
label: '创建时间',
|
||||||
|
render: (value) => {
|
||||||
|
return useRender.renderDate(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 站内信详情
|
||||||
|
export interface MessageInfo {
|
||||||
|
userId: number
|
||||||
|
userType: number
|
||||||
|
templateId: number
|
||||||
|
templateCode: string
|
||||||
|
templateNickname: string
|
||||||
|
templateContent: string
|
||||||
|
templateType: number
|
||||||
|
templateParams: { [key: string]: string }
|
||||||
|
readStatus: boolean
|
||||||
|
readTime?: any
|
||||||
|
id: number
|
||||||
|
createTime: number
|
||||||
|
key: string
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ import { useI18n } from '@/hooks/web/useI18n'
|
||||||
import { BasicTable, useTable, TableAction } from '@/components/Table'
|
import { BasicTable, useTable, TableAction } from '@/components/Table'
|
||||||
import { getNotifyMessagePage } from '@/api/system/notify/message'
|
import { getNotifyMessagePage } from '@/api/system/notify/message'
|
||||||
import { columns, searchFormSchema } from './message.data'
|
import { columns, searchFormSchema } from './message.data'
|
||||||
import MessageInfoModal from './MessageInfoModal.vue'
|
import MessageInfoModal from '@/views/system/notify/components/MessageInfoModal.vue'
|
||||||
import { useModal } from '@/components/Modal'
|
import { useModal } from '@/components/Modal'
|
||||||
|
|
||||||
defineOptions({ name: 'SystemMessage' })
|
defineOptions({ name: 'SystemMessage' })
|
||||||
|
|
|
@ -1,16 +1,38 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<BasicTable @register="registerTable">
|
<BasicTable @register="registerTable" bordered>
|
||||||
<template #toolbar>
|
<template #toolbar>
|
||||||
<a-button type="primary" @click="handleUpdateList"> 标记已读 </a-button>
|
<a-button preIcon="solar:check-read-line-duotone" type="primary" @click="handleUpdateList" :disabled="readedDisabled">
|
||||||
<a-button type="primary" @click="handleUpdateAll"> 全部已读 </a-button>
|
标记已读
|
||||||
|
</a-button>
|
||||||
|
<a-button preIcon="solar:check-read-linear" type="primary" @click="handleUpdateAll"> 全部已读 </a-button>
|
||||||
</template>
|
</template>
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'action'">
|
<template v-if="column.key === 'action'">
|
||||||
<TableAction :actions="[{ icon: IconEnum.EDIT, label: '已读', onClick: handleUpdateSingle.bind(null, record) }]" />
|
<!--阻止事件冒泡 勾选框 -->
|
||||||
|
<TableAction
|
||||||
|
stopButtonPropagation
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
icon: IconEnum.EDIT,
|
||||||
|
label: '已读',
|
||||||
|
color: 'warning',
|
||||||
|
ifShow: () => {
|
||||||
|
return !record.readStatus
|
||||||
|
},
|
||||||
|
onClick: handleUpdateSingle.bind(null, record)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: IconEnum.LOG,
|
||||||
|
label: '详情',
|
||||||
|
onClick: handleInfo.bind(null, record)
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</BasicTable>
|
</BasicTable>
|
||||||
|
<MessageInfoModal @register="registerModal" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -20,18 +42,32 @@ import { IconEnum } from '@/enums/appEnum'
|
||||||
import { BasicTable, useTable, TableAction } from '@/components/Table'
|
import { BasicTable, useTable, TableAction } from '@/components/Table'
|
||||||
import { getMyNotifyMessagePage, updateAllNotifyMessageRead, updateNotifyMessageRead } from '@/api/system/notify/message'
|
import { getMyNotifyMessagePage, updateAllNotifyMessageRead, updateNotifyMessageRead } from '@/api/system/notify/message'
|
||||||
import { columns, searchFormSchema } from './my.data'
|
import { columns, searchFormSchema } from './my.data'
|
||||||
|
import MessageInfoModal from '@/views/system/notify/components/MessageInfoModal.vue'
|
||||||
|
import { useModal } from '@/components/Modal'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useUserMessageStore } from '@/store/modules/userMessage'
|
||||||
|
|
||||||
defineOptions({ name: 'SystemMyMessage' })
|
defineOptions({ name: 'SystemMyMessage' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { createMessage } = useMessage()
|
const { createMessage } = useMessage()
|
||||||
|
const [registerModal, { openModal }] = useModal()
|
||||||
|
const store = useUserMessageStore()
|
||||||
|
|
||||||
const [registerTable, { getSelectRowKeys, reload }] = useTable({
|
const [registerTable, { getSelectRowKeys, clearSelectedRowKeys, reload }] = useTable({
|
||||||
title: '我的站内信列表',
|
title: '我的站内信列表',
|
||||||
api: getMyNotifyMessagePage,
|
api: getMyNotifyMessagePage,
|
||||||
columns,
|
columns,
|
||||||
formConfig: { labelWidth: 120, schemas: searchFormSchema },
|
formConfig: { labelWidth: 130, schemas: searchFormSchema },
|
||||||
rowSelection: { type: 'checkbox' },
|
rowSelection: {
|
||||||
|
type: 'checkbox',
|
||||||
|
getCheckboxProps: (record: Recordable) => {
|
||||||
|
return {
|
||||||
|
// 已读的消息disabled 不可选
|
||||||
|
disabled: record.readStatus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
rowKey: 'id',
|
rowKey: 'id',
|
||||||
useSearchForm: true,
|
useSearchForm: true,
|
||||||
showTableSetting: true,
|
showTableSetting: true,
|
||||||
|
@ -44,6 +80,13 @@ const [registerTable, { getSelectRowKeys, reload }] = useTable({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已读按钮的disabled 未选中则disabled
|
||||||
|
*/
|
||||||
|
const readedDisabled = computed<boolean>(() => {
|
||||||
|
return getSelectRowKeys().length === 0
|
||||||
|
})
|
||||||
|
|
||||||
function handleUpdateList() {
|
function handleUpdateList() {
|
||||||
const ids = getSelectRowKeys()
|
const ids = getSelectRowKeys()
|
||||||
handleUpdate(ids)
|
handleUpdate(ids)
|
||||||
|
@ -53,15 +96,28 @@ async function handleUpdateSingle(record: Recordable) {
|
||||||
await handleUpdate([record.id])
|
await handleUpdate([record.id])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function afterRead(msg: string) {
|
||||||
|
createMessage.success(msg)
|
||||||
|
// 更新未读消息
|
||||||
|
store.updateUnreadCount()
|
||||||
|
// 重加载表格
|
||||||
|
reload()
|
||||||
|
// 清除选中的行
|
||||||
|
clearSelectedRowKeys()
|
||||||
|
}
|
||||||
|
|
||||||
async function handleUpdate(ids) {
|
async function handleUpdate(ids) {
|
||||||
await updateNotifyMessageRead(ids)
|
await updateNotifyMessageRead(ids)
|
||||||
createMessage.success('标记已读成功!')
|
afterRead('标记已读成功!')
|
||||||
reload()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleUpdateAll() {
|
async function handleUpdateAll() {
|
||||||
await updateAllNotifyMessageRead()
|
await updateAllNotifyMessageRead()
|
||||||
createMessage.success('全部已读成功!')
|
afterRead('全部已读成功!')
|
||||||
reload()
|
}
|
||||||
|
|
||||||
|
const handleInfo = (record: any) => {
|
||||||
|
console.log(JSON.stringify(record, Object.keys(record), 2))
|
||||||
|
openModal(true, record)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -31,7 +31,7 @@ export const columns: BasicColumn[] = [
|
||||||
{
|
{
|
||||||
title: '是否已读',
|
title: '是否已读',
|
||||||
dataIndex: 'readStatus',
|
dataIndex: 'readStatus',
|
||||||
width: 180,
|
width: 100,
|
||||||
customRender: ({ text }) => {
|
customRender: ({ text }) => {
|
||||||
return useRender.renderDict(text, DICT_TYPE.INFRA_BOOLEAN_STRING)
|
return useRender.renderDict(text, DICT_TYPE.INFRA_BOOLEAN_STRING)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue