@ithinkdt/naive
Version:
iThinkDT Naive UI
344 lines (322 loc) • 17.3 kB
JSX
import { defineComponent, ref, h, inject, computed } from 'vue'
import {
NBadge,
NButton,
NIcon,
NList,
NListItem,
NSpin,
NFlex,
NTab,
NTabs,
NText,
NThing,
NTooltip,
NPagination,
NEmpty,
} from 'ithinkdt-ui'
import { format } from 'date-fns'
import { until } from '@vueuse/core'
import { useAtomicBroadcast } from '@ithinkdt/common'
import { useI18n, useModal, msgHandler, router, $msg, openPage } from '@ithinkdt/core'
import { cE, cB, CSS_MOUNT_ANCHOR_META_NAME, CSS_STYLE_PREFIX as p } from '@ithinkdt/core/cssr'
import { IMsg, IInfo, IInfoOutline, ICheck, IClose } from '../assets.jsx'
export const DtMessage = defineComponent({
name: 'DtMessage',
setup() {
const cls = `${p}-msg`
createStyle(cls)
const auth = inject('__INJECTED_AUTH__')
const theme = inject('__INJECTED_THEME__')
const { t } = useI18n()
const current = ref('unread')
const totalUnread = ref(0)
const totalRead = ref(0)
const loading = ref(false)
const msgs = ref([])
const page = ref(1)
const PAGE_SIZE = 10
const loadMsgs = (status, pageSize = PAGE_SIZE, currentPage = 1) => {
if (!auth.logged || !auth.user?.username) return Promise.resolve()
loading.value = true
page.value = currentPage
return msgHandler
.loadMessages({ status, pageSize, currentPage })
.then((page) => {
if (status === 'unread') totalUnread.value = page.total
else totalRead.value = page.total
if (pageSize) msgs.value = page.records
})
.finally(() => {
loading.value = false
})
}
const { start, close } = useAtomicBroadcast({
channel: '__dt_mc_shared_channel',
timeout: theme.msgInterval || 10_000,
onMsg: (data) => {
totalUnread.value = data.totalUnread
totalRead.value = data.totalRead
},
getMsg: async () => {
if (!visible.value) {
await loadMsgs('unread', 0)
}
return {
totalUnread: totalUnread.value,
totalRead: totalRead.value,
}
},
})
until(computed(() => auth.logged && theme.hasTopbar))
.toBeTruthy()
.then(start)
const readMsg = (ids) =>
msgHandler.readMessages(ids).then(() => {
totalUnread.value -= ids.length
totalRead.value += ids.length
})
const deleteMsg = (ids) =>
msgHandler.deleteMessages(ids).then(() => {
$msg.success(t('sys.notify.deleteSuccess'))
})
const { visible, show } = useModal({
type: 'drawer',
content: {
header: () => (
<NTabs value={current.value} onUpdateValue={showMsg} size="small">
{{
prefix: () => <div style="padding-right: 8px">{t('sys.notify.title')}</div>,
default: () => (
<>
<NTab name="unread">{t('sys.notify.unread')} ({totalUnread.value})</NTab>
<NTab name="read">{t('sys.notify.read')} ({totalRead.value})</NTab>
</>
),
}}
</NTabs>
),
default: () => (
<NSpin show={loading.value}>
{msgs.value?.length ? (
<NList clickable hoverable style="min-height: 50vh">
{msgs.value.map((msg) => {
return (
<NListItem
onClick={() => {
if (msg.status === 'unread') {
readMsg([msg.id]).then(() => {
msg.status = 'read'
})
}
if (msg.link) {
if (
msg.link.startsWith(location.origin) &&
msg.link.slice(location.origin.length).startsWith(router.base)
) {
const to =
'/' +
msg.link.slice(Math.max(0, location.origin + router.base))
if (router.resolve(to)) {
openPage(to)
return
}
}
window.open(msg.link, '_target')
}
}}
>
<NThing
bordered={false}
closable
contentIndented
size="small"
onMouseenter={() => (msg.hover = true)}
onMouseleave={() => (msg.hover = false)}
style="padding: 0 4px"
>
{{
avatar: () => (
<NIcon
size={22}
color={
msg.status === 'unread'
? theme.vars.primaryColor
: undefined
}
>
{msg.status === 'read' ? <IInfoOutline /> : <IInfo />}
</NIcon>
),
header: () => msg.title,
'header-extra': () => (
<div style="width: 64px">
{msg.hover ? (
<NFlex
justify="end"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
}}
>
{msg.status === 'unread' ? (
<NTooltip>
{{
trigger: () => (
<NButton
text
type="primary"
onClick={() =>
readMsg([msg.id]).then(
() => {
msg.status = 'read'
},
)
}
>
<NIcon size={20}>
<ICheck />
</NIcon>
</NButton>
),
default: () => t('sys.notify.markRead'),
}}
</NTooltip>
) : undefined}
<NTooltip>
{{
trigger: () => (
<NButton
text
type="error"
onClick={() =>
deleteMsg([msg.id]).then(() => {
msgs.value.splice(
msgs.value.findIndex(
(it) =>
it.id == msg.id,
),
1,
)
})
}
>
<NIcon size={20}>
<IClose />
</NIcon>
</NButton>
),
default: () => t('sys.notify.remove'),
}}
</NTooltip>
</NFlex>
) : undefined}
</div>
),
default: () => h('div', { innerHTML: msg.content }),
footer: () => (
<NText depth="3">{format(msg.time, t('sys.notify.timeFormatter'))}</NText>
),
}}
</NThing>
</NListItem>
)
})}
</NList>
) : (
<NEmpty style="margin-top: 30vh" />
)}
</NSpin>
),
footer: () => (
<NFlex justify="space-between" style="width: 100%">
{current.value === 'unread' ? (
<NButton
text
type="primary"
disabled={msgs.value.length === 0}
onClick={() => {
const ids = msgs.value.filter((it) => it.status === 'unread').map((it) => it.id)
readMsg(ids).then(() => {
let pageNo = page.value
while (totalUnread.value - ids.length <= PAGE_SIZE * (pageNo - 1)) {
pageNo--
}
loadMsgs('unread', PAGE_SIZE, pageNo)
loadMsgs('read', 0)
})
}}
>
<NIcon>
<ICheck />
</NIcon>
{t('sys.notify.markPageRead')}
</NButton>
) : (
<span />
)}
<NPagination
simple
pageSize={10}
page={page.value}
itemCount={current.value === 'unread' ? totalUnread.value : totalRead.value}
onUpdatePage={(page) => loadMsgs(current.value, 10, page)}
/>
</NFlex>
),
},
width: '400px',
closable: true,
maskClosable: true,
bodyContentStyle: {
padding: '0',
},
})
function showMsg(status = 'unread') {
current.value = status
if (!visible.value) loadMsgs('read', 0)
show()
loadMsgs(status)
}
return () => (
<NTooltip>
{{
default: () => t('sys.notify.title'),
trigger: () => (
<NBadge
class={cls}
max={99}
value={totalUnread.value}
onClick={() => showMsg()}
offset={[-8, 10]}
>
<NButton class={`${cls}__btn`} quaternary circle size="large">
<IMsg />
</NButton>
</NBadge>
),
}}
</NTooltip>
)
},
})
let style
function createStyle(cls) {
if (!style) {
style = cB(
'msg',
{
cursor: 'pointer',
},
[
cE('btn', {
fontSize: '19px',
}),
],
)
style.mount({
id: cls,
anchorMetaName: CSS_MOUNT_ANCHOR_META_NAME,
})
}
}