mirror of
https://github.com/vbenjs/vben-admin-thin-next.git
synced 2025-01-24 02:00:22 +08:00
feat: add notice (#47)
This commit is contained in:
parent
463aeabdce
commit
7a1e94c49d
@ -22,6 +22,7 @@ import { useModal } from '/@/components/Modal/index';
|
||||
import { errorStore } from '/@/store/modules/error';
|
||||
import { useGo } from '/@/hooks/web/usePage';
|
||||
import { useWindowSizeFn } from '/@/hooks/event/useWindowSize';
|
||||
import NoticeAction from './actions/notice/NoticeActionItem.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DefaultLayoutHeader',
|
||||
@ -85,7 +86,14 @@ export default defineComponent({
|
||||
const {
|
||||
useErrorHandle,
|
||||
showLogo,
|
||||
headerSetting: { theme: headerTheme, useLockPage, showRedo, showGithub, showFullScreen },
|
||||
headerSetting: {
|
||||
theme: headerTheme,
|
||||
useLockPage,
|
||||
showRedo,
|
||||
showGithub,
|
||||
showFullScreen,
|
||||
showNotice,
|
||||
},
|
||||
menuSetting: { mode, type: menuType, split: splitMenu, topMenuAlign },
|
||||
showBreadCrumb,
|
||||
} = getProjectConfig;
|
||||
@ -163,6 +171,20 @@ export default defineComponent({
|
||||
}}
|
||||
</Tooltip>
|
||||
)}
|
||||
{showNotice && (
|
||||
<div>
|
||||
<Tooltip>
|
||||
{{
|
||||
title: () => '消息中心',
|
||||
default: () => (
|
||||
<div class={`layout-header__action-item`}>
|
||||
<NoticeAction />
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
{showRedo && (
|
||||
<Tooltip>
|
||||
{{
|
||||
|
@ -1,68 +0,0 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import { Popover, Tabs } from 'ant-design-vue';
|
||||
|
||||
import NoticeList from './NoticeList';
|
||||
import { NoticeTabItem, NoticeListItem, noticeTabListData, noticeListData } from './data';
|
||||
import './index.less';
|
||||
|
||||
const prefixCls = 'notice-popover';
|
||||
export default defineComponent({
|
||||
name: 'NoticePopover',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props, { attrs }) {
|
||||
// 渲染卡片内容
|
||||
function renderContent() {
|
||||
return (
|
||||
<Tabs class={`${prefixCls}__tabs`}>
|
||||
{() => {
|
||||
return noticeTabListData.map((item: NoticeTabItem) => {
|
||||
const { key, name } = item;
|
||||
return (
|
||||
<Tabs.TabPane key={key} tab={renderTab(key, name)}>
|
||||
{() => <NoticeList list={getListData(key)} />}
|
||||
</Tabs.TabPane>
|
||||
);
|
||||
});
|
||||
}}
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
// tab标题渲染
|
||||
function renderTab(key: string, name: string) {
|
||||
const list = getListData(key);
|
||||
const unreadlist = list.filter((item: NoticeListItem) => !item.read);
|
||||
return (
|
||||
<div>
|
||||
{name}
|
||||
{unreadlist.length > 0 && <span>({unreadlist.length})</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
function getListData(type: string) {
|
||||
return noticeListData.filter((item: NoticeListItem) => item.type === type);
|
||||
}
|
||||
|
||||
return () => {
|
||||
const { visible } = props;
|
||||
return (
|
||||
<Popover
|
||||
title=""
|
||||
{...{
|
||||
...attrs,
|
||||
visible,
|
||||
}}
|
||||
content={renderContent}
|
||||
class={prefixCls}
|
||||
/>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
64
src/layouts/default/actions/notice/NoticeActionItem.vue
Normal file
64
src/layouts/default/actions/notice/NoticeActionItem.vue
Normal file
@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div>
|
||||
<Popover title="" trigger="click">
|
||||
<Badge :count="count" :numberStyle="numberStyle">
|
||||
<BellOutlined class="layout-header__action-icon" />
|
||||
</Badge>
|
||||
<template #content>
|
||||
<Tabs>
|
||||
<template v-for="item in tabListData" :key="item.key">
|
||||
<TabPane>
|
||||
<template #tab>
|
||||
{{ item.name }}
|
||||
<span v-if="item.list.length !== 0">({{ item.list.length }})</span>
|
||||
</template>
|
||||
<NoticeList :list="item.list" />
|
||||
</TabPane>
|
||||
</template>
|
||||
</Tabs>
|
||||
</template>
|
||||
</Popover>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { Popover, Tabs, Badge } from 'ant-design-vue';
|
||||
import { BellOutlined } from '@ant-design/icons-vue';
|
||||
import { tabListData } from './data';
|
||||
import NoticeList from './NoticeList.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { Popover, BellOutlined, Tabs, TabPane: Tabs.TabPane, Badge, NoticeList },
|
||||
setup() {
|
||||
let count = 0;
|
||||
for (let i = 0; i < tabListData.length; i++) {
|
||||
count += tabListData[i].list.length;
|
||||
}
|
||||
|
||||
return {
|
||||
tabListData,
|
||||
count,
|
||||
numberStyle: {},
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
/deep/ .ant-tabs-tab {
|
||||
padding-top: 8px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
/deep/ .ant-tabs-content {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
/deep/ .ant-badge {
|
||||
font-size: 18px;
|
||||
|
||||
.ant-badge-multiple-words {
|
||||
padding: 0 4px;
|
||||
transform: translate(26%, -48%);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,73 +0,0 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import { List, Avatar, Tag } from 'ant-design-vue';
|
||||
|
||||
import { NoticeListItem } from './data';
|
||||
import './index.less';
|
||||
|
||||
const prefixCls = 'notice-popover';
|
||||
export default defineComponent({
|
||||
name: 'NoticeList',
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
// 头像渲染
|
||||
function renderAvatar(avatar: string) {
|
||||
return avatar ? <Avatar class="avatar" src={avatar} /> : <span>{avatar}</span>;
|
||||
}
|
||||
|
||||
// 描述渲染
|
||||
function renderDescription(description: string, datetime: string) {
|
||||
return (
|
||||
<div>
|
||||
<div class="description">{description}</div>
|
||||
<div class="datetime">{datetime}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 标题渲染
|
||||
function renderTitle(title: string, extra?: string, color?: string) {
|
||||
return (
|
||||
<div class="title">
|
||||
{title}
|
||||
{extra && (
|
||||
<div class="extra">
|
||||
<Tag class="tag" color={color}>
|
||||
{() => extra}
|
||||
</Tag>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return () => {
|
||||
const { list } = props;
|
||||
return (
|
||||
<List dataSource={list} class={`${prefixCls}__list`}>
|
||||
{() => {
|
||||
return list.map((item: NoticeListItem) => {
|
||||
const { id, avatar, title, description, datetime, extra, read, color } = item;
|
||||
return (
|
||||
<List.Item key={id} class={`${prefixCls}__list-item ${read ? 'read' : ''}`}>
|
||||
{() => (
|
||||
<List.Item.Meta
|
||||
class="meta"
|
||||
avatar={renderAvatar(avatar)}
|
||||
title={renderTitle(title, extra, color)}
|
||||
description={renderDescription(description, datetime)}
|
||||
/>
|
||||
)}
|
||||
</List.Item>
|
||||
);
|
||||
});
|
||||
}}
|
||||
</List>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
102
src/layouts/default/actions/notice/NoticeList.vue
Normal file
102
src/layouts/default/actions/notice/NoticeList.vue
Normal file
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<List class="list">
|
||||
<template v-for="item in list" :key="item.id">
|
||||
<ListItem class="list__item">
|
||||
<ListItemMeta>
|
||||
<template #title>
|
||||
<div class="title">
|
||||
{{ item.title }}
|
||||
<div class="extra" v-if="item.extra">
|
||||
<Tag class="tag" :color="item.color">
|
||||
{{ item.extra }}
|
||||
</Tag>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #avatar>
|
||||
<Avatar v-if="item.avatar" class="avatar" :src="item.avatar" />
|
||||
<span v-else> {{ item.avatar }}</span>
|
||||
</template>
|
||||
<template #description>
|
||||
<div>
|
||||
<div class="description">{{ item.description }}</div>
|
||||
<div class="datetime">{{ item.datetime }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</ListItemMeta>
|
||||
</ListItem>
|
||||
</template>
|
||||
</List>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import { List, Avatar, Tag } from 'ant-design-vue';
|
||||
import { ListItem } from './data';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
list: {
|
||||
type: Array as PropType<Array<ListItem>>,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
components: {
|
||||
List,
|
||||
ListItem: List.Item,
|
||||
ListItemMeta: List.Item.Meta,
|
||||
Avatar,
|
||||
Tag,
|
||||
},
|
||||
setup(props) {
|
||||
const { list = [] } = props;
|
||||
return {
|
||||
list,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.list {
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__item {
|
||||
padding: 6px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
.title {
|
||||
margin-bottom: 8px;
|
||||
font-weight: normal;
|
||||
|
||||
.extra {
|
||||
float: right;
|
||||
margin-top: -1.5px;
|
||||
margin-right: 0;
|
||||
font-weight: normal;
|
||||
|
||||
.tag {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.datetime {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
148
src/layouts/default/actions/notice/data.ts
Normal file
148
src/layouts/default/actions/notice/data.ts
Normal file
@ -0,0 +1,148 @@
|
||||
export interface ListItem {
|
||||
id: string;
|
||||
avatar: string;
|
||||
title: string;
|
||||
datetime: string;
|
||||
type: string;
|
||||
read?: boolean;
|
||||
description: string;
|
||||
clickClose?: boolean;
|
||||
extra?: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export interface TabItem {
|
||||
key: string;
|
||||
name: string;
|
||||
list: ListItem[];
|
||||
unreadlist?: ListItem[];
|
||||
}
|
||||
|
||||
export const tabListData: TabItem[] = [
|
||||
{
|
||||
key: '1',
|
||||
name: '通知',
|
||||
list: [
|
||||
{
|
||||
id: '000000001',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
|
||||
title: '你收到了 14 份新周报',
|
||||
description: '',
|
||||
datetime: '2017-08-09',
|
||||
type: '1',
|
||||
},
|
||||
{
|
||||
id: '000000002',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
|
||||
title: '你推荐的 曲妮妮 已通过第三轮面试',
|
||||
description: '',
|
||||
datetime: '2017-08-08',
|
||||
type: '1',
|
||||
},
|
||||
{
|
||||
id: '000000003',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
|
||||
title: '这种模板可以区分多种通知类型',
|
||||
description: '',
|
||||
datetime: '2017-08-07',
|
||||
// read: true,
|
||||
type: '1',
|
||||
},
|
||||
{
|
||||
id: '000000004',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
|
||||
title: '左侧图标用于区分不同的类型',
|
||||
description: '',
|
||||
datetime: '2017-08-07',
|
||||
type: '1',
|
||||
},
|
||||
// {
|
||||
// id: '000000005',
|
||||
// avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
|
||||
// title: '内容不要超过两行字,超出时自动截断',
|
||||
// description: '',
|
||||
// datetime: '2017-08-07',
|
||||
// type: '1',
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
name: '消息',
|
||||
list: [
|
||||
{
|
||||
id: '000000006',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '曲丽丽 评论了你',
|
||||
description: '描述信息描述信息描述信息',
|
||||
datetime: '2017-08-07',
|
||||
type: '2',
|
||||
clickClose: true,
|
||||
},
|
||||
{
|
||||
id: '000000007',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '朱偏右 回复了你',
|
||||
description: '这种模板用于提醒谁与你发生了互动',
|
||||
datetime: '2017-08-07',
|
||||
type: '2',
|
||||
clickClose: true,
|
||||
},
|
||||
{
|
||||
id: '000000008',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
|
||||
title: '标题',
|
||||
description: '这种模板用于提醒谁与你发生了互动',
|
||||
datetime: '2017-08-07',
|
||||
type: '2',
|
||||
clickClose: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
name: '待办',
|
||||
list: [
|
||||
{
|
||||
id: '000000009',
|
||||
avatar: '',
|
||||
title: '任务名称',
|
||||
description: '任务需要在 2017-01-12 20:00 前启动',
|
||||
datetime: '',
|
||||
extra: '未开始',
|
||||
color: '',
|
||||
type: '3',
|
||||
},
|
||||
{
|
||||
id: '000000010',
|
||||
avatar: '',
|
||||
title: '第三方紧急代码变更',
|
||||
description: '冠霖 需在 2017-01-07 前完成代码变更任务',
|
||||
datetime: '',
|
||||
extra: '马上到期',
|
||||
color: 'red',
|
||||
type: '3',
|
||||
},
|
||||
{
|
||||
id: '000000011',
|
||||
avatar: '',
|
||||
title: '信息安全考试',
|
||||
description: '指派竹尔于 2017-01-09 前完成更新并发布',
|
||||
datetime: '',
|
||||
extra: '已耗时 8 天',
|
||||
color: 'gold',
|
||||
type: '3',
|
||||
},
|
||||
{
|
||||
id: '000000012',
|
||||
avatar: '',
|
||||
title: 'ABCD 版本发布',
|
||||
description: '指派竹尔于 2017-01-09 前完成更新并发布',
|
||||
datetime: '',
|
||||
extra: '进行中',
|
||||
color: 'blue',
|
||||
type: '3',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
@ -39,6 +39,8 @@ const setting: ProjectConfig = {
|
||||
showDoc: true,
|
||||
// 是否显示github
|
||||
showGithub: true,
|
||||
// 显示消息中心按钮
|
||||
showNotice: true,
|
||||
},
|
||||
// 菜单配置
|
||||
menuSetting: {
|
||||
|
2
src/types/config.d.ts
vendored
2
src/types/config.d.ts
vendored
@ -47,6 +47,8 @@ export interface HeaderSetting {
|
||||
// 显示文档按钮
|
||||
showDoc: boolean;
|
||||
showGithub: boolean;
|
||||
// 显示消息中心按钮
|
||||
showNotice: boolean;
|
||||
}
|
||||
export interface ProjectConfig {
|
||||
// 是否显示配置按钮
|
||||
|
Loading…
Reference in New Issue
Block a user