UNPKG

@ithinkdt/naive

Version:

iThinkDT Naive UI

344 lines (322 loc) 17.3 kB
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, }) } }