feat: 头部新增消息按钮
This commit is contained in:
parent
3c2f4e45ef
commit
c7d850b93a
1
src/assets/svg-icon/bell.svg
Normal file
1
src/assets/svg-icon/bell.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg data-v-0a0a4a97="" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bell-icon size-4"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"></path><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"></path></svg>
|
After Width: | Height: | Size: 353 B |
@ -3,5 +3,6 @@ export enum SetupStoreId {
|
|||||||
Theme = 'theme-store',
|
Theme = 'theme-store',
|
||||||
Auth = 'auth-store',
|
Auth = 'auth-store',
|
||||||
Route = 'route-store',
|
Route = 'route-store',
|
||||||
Tab = 'tab-store'
|
Tab = 'tab-store',
|
||||||
|
Notice = 'notice-store'
|
||||||
}
|
}
|
||||||
|
152
src/layouts/modules/global-header/components/message-button.vue
Normal file
152
src/layouts/modules/global-header/components/message-button.vue
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useNoticeStore } from '@/store/modules/notice';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'MessgaeButton'
|
||||||
|
});
|
||||||
|
|
||||||
|
const show = ref(false);
|
||||||
|
const noticeStore = useNoticeStore();
|
||||||
|
const { state } = storeToRefs(noticeStore);
|
||||||
|
|
||||||
|
const noticeNum = computed(() => {
|
||||||
|
return state.value.notices.filter(notice => !notice.read).length || 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const toGitee = () => {
|
||||||
|
window.open('https://gitee.com/xlsea/ruoyi-plus-soybean', '_blank');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NPopover v-model:show="show" trigger="click" arrow-point-to-center raw class="border-rounded-6px">
|
||||||
|
<template #trigger>
|
||||||
|
<NTooltip :disabled="show">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton quaternary class="bell-button h-36px text-icon" :focusable="false">
|
||||||
|
<NBadge :value="noticeNum" :max="99" :offset="[2, -2]">
|
||||||
|
<div class="bell-icon flex-center gap-8px">
|
||||||
|
<SvgIcon local-icon="bell" />
|
||||||
|
</div>
|
||||||
|
</NBadge>
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
消息
|
||||||
|
</NTooltip>
|
||||||
|
</template>
|
||||||
|
<NCard
|
||||||
|
size="small"
|
||||||
|
:bordered="false"
|
||||||
|
class="w-340px"
|
||||||
|
header-class="p-0"
|
||||||
|
:segmented="{ content: true, footer: 'soft' }"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<span>通知公告</span>
|
||||||
|
</template>
|
||||||
|
<template #header-extra>
|
||||||
|
<NTooltip placement="left" :z-index="98">
|
||||||
|
<template #trigger>
|
||||||
|
<NPopconfirm @positive-click="() => noticeStore.readAll()">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton quaternary>
|
||||||
|
<div class="flex-center gap-8px">
|
||||||
|
<SvgIcon icon="lucide:mail-check" class="text-16px" />
|
||||||
|
</div>
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
确定全部已读吗?
|
||||||
|
</NPopconfirm>
|
||||||
|
</template>
|
||||||
|
一键已读
|
||||||
|
</NTooltip>
|
||||||
|
</template>
|
||||||
|
<div>
|
||||||
|
<template v-if="state?.notices?.length">
|
||||||
|
<template v-for="(message, index) in state?.notices" :key="index">
|
||||||
|
<NDivider v-show="index !== 0" />
|
||||||
|
<div class="flex cursor-pointer" @click="() => noticeStore.readNotice(message)">
|
||||||
|
<div class="flex-col justify-between gap-3px">
|
||||||
|
<NEllipsis class="w-260px">{{ message.message }}</NEllipsis>
|
||||||
|
<span class="text-#898989">
|
||||||
|
{{ message.time }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<NTag :type="message.read ? 'success' : 'error'">{{ message.read ? '已读' : '未读' }}</NTag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<NEmpty v-else class="h-180px flex-center" />
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex items-center justify-end">
|
||||||
|
<NButton type="primary" size="small" @click="toGitee">前往 Gitee</NButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NCard>
|
||||||
|
</NPopover>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.n-divider) {
|
||||||
|
margin: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-thing-header) {
|
||||||
|
margin-bottom: 1px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-thing-main__content) {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.messgae-popover) {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-badge-sup) {
|
||||||
|
padding: 0 5px !important;
|
||||||
|
font-size: 10px !important;
|
||||||
|
height: 15px !important;
|
||||||
|
line-height: 15px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bell-button {
|
||||||
|
&:hover {
|
||||||
|
.bell-icon {
|
||||||
|
animation: bell-ring 1s both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bell-ring {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform-origin: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
15% {
|
||||||
|
transform: rotateZ(10deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
30% {
|
||||||
|
transform: rotateZ(-10deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
45% {
|
||||||
|
transform: rotateZ(5deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
60% {
|
||||||
|
transform: rotateZ(-5deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: rotateZ(2deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -10,6 +10,7 @@ import GlobalBreadcrumb from '../global-breadcrumb/index.vue';
|
|||||||
import GlobalSearch from '../global-search/index.vue';
|
import GlobalSearch from '../global-search/index.vue';
|
||||||
import ThemeButton from './components/theme-button.vue';
|
import ThemeButton from './components/theme-button.vue';
|
||||||
import UserAvatar from './components/user-avatar.vue';
|
import UserAvatar from './components/user-avatar.vue';
|
||||||
|
import MessageButton from './components/message-button.vue';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'GlobalHeader'
|
name: 'GlobalHeader'
|
||||||
@ -45,6 +46,7 @@ const tenantId = ref<CommonType.IdType>(authStore.userInfo?.user?.tenantId || '0
|
|||||||
<div class="h-full flex-y-center justify-end">
|
<div class="h-full flex-y-center justify-end">
|
||||||
<TenantSelect v-if="!appStore.isMobile" v-model:value="tenantId" class="mr-12px w-150px" />
|
<TenantSelect v-if="!appStore.isMobile" v-model:value="tenantId" class="mr-12px w-150px" />
|
||||||
<GlobalSearch />
|
<GlobalSearch />
|
||||||
|
<MessageButton />
|
||||||
<FullScreen v-if="!appStore.isMobile" :full="isFullscreen" @click="toggle" />
|
<FullScreen v-if="!appStore.isMobile" :full="isFullscreen" @click="toggle" />
|
||||||
<LangSwitch
|
<LangSwitch
|
||||||
v-if="themeStore.header.multilingual.visible"
|
v-if="themeStore.header.multilingual.visible"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { reactive } from 'vue';
|
import { reactive } from 'vue';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
import { SetupStoreId } from '@/enum';
|
||||||
|
|
||||||
interface NoticeItem {
|
interface NoticeItem {
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -8,7 +9,7 @@ interface NoticeItem {
|
|||||||
time: string;
|
time: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNoticeStore = defineStore('notice', () => {
|
export const useNoticeStore = defineStore(SetupStoreId.Notice, () => {
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
notices: [] as NoticeItem[]
|
notices: [] as NoticeItem[]
|
||||||
});
|
});
|
||||||
@ -21,6 +22,10 @@ export const useNoticeStore = defineStore('notice', () => {
|
|||||||
state.notices.splice(state.notices.indexOf(notice), 1);
|
state.notices.splice(state.notices.indexOf(notice), 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const readNotice = (notice: NoticeItem) => {
|
||||||
|
state.notices[state.notices.indexOf(notice)].read = true;
|
||||||
|
};
|
||||||
|
|
||||||
// 实现全部已读
|
// 实现全部已读
|
||||||
const readAll = () => {
|
const readAll = () => {
|
||||||
state.notices.forEach((item: any) => {
|
state.notices.forEach((item: any) => {
|
||||||
@ -31,10 +36,12 @@ export const useNoticeStore = defineStore('notice', () => {
|
|||||||
const clearNotice = () => {
|
const clearNotice = () => {
|
||||||
state.notices = [];
|
state.notices = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state,
|
state,
|
||||||
addNotice,
|
addNotice,
|
||||||
removeNotice,
|
removeNotice,
|
||||||
|
readNotice,
|
||||||
readAll,
|
readAll,
|
||||||
clearNotice
|
clearNotice
|
||||||
};
|
};
|
||||||
|
2
src/typings/components.d.ts
vendored
2
src/typings/components.d.ts
vendored
@ -62,6 +62,7 @@ declare module 'vue' {
|
|||||||
NA: typeof import('naive-ui')['NA']
|
NA: typeof import('naive-ui')['NA']
|
||||||
NAlert: typeof import('naive-ui')['NAlert']
|
NAlert: typeof import('naive-ui')['NAlert']
|
||||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||||
|
NBadge: typeof import('naive-ui')['NBadge']
|
||||||
NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
|
NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
|
||||||
NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
|
NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
|
||||||
NButton: typeof import('naive-ui')['NButton']
|
NButton: typeof import('naive-ui')['NButton']
|
||||||
@ -81,6 +82,7 @@ declare module 'vue' {
|
|||||||
NDrawerContent: typeof import('naive-ui')['NDrawerContent']
|
NDrawerContent: typeof import('naive-ui')['NDrawerContent']
|
||||||
NDropdown: typeof import('naive-ui')['NDropdown']
|
NDropdown: typeof import('naive-ui')['NDropdown']
|
||||||
NDynamicInput: typeof import('naive-ui')['NDynamicInput']
|
NDynamicInput: typeof import('naive-ui')['NDynamicInput']
|
||||||
|
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||||
NEmpty: typeof import('naive-ui')['NEmpty']
|
NEmpty: typeof import('naive-ui')['NEmpty']
|
||||||
NForm: typeof import('naive-ui')['NForm']
|
NForm: typeof import('naive-ui')['NForm']
|
||||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||||
|
Loading…
Reference in New Issue
Block a user