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 { errorStore } from '/@/store/modules/error';
|
||||||
import { useGo } from '/@/hooks/web/usePage';
|
import { useGo } from '/@/hooks/web/usePage';
|
||||||
import { useWindowSizeFn } from '/@/hooks/event/useWindowSize';
|
import { useWindowSizeFn } from '/@/hooks/event/useWindowSize';
|
||||||
|
import NoticeAction from './actions/notice/NoticeActionItem.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'DefaultLayoutHeader',
|
name: 'DefaultLayoutHeader',
|
||||||
|
|
@ -85,7 +86,14 @@ export default defineComponent({
|
||||||
const {
|
const {
|
||||||
useErrorHandle,
|
useErrorHandle,
|
||||||
showLogo,
|
showLogo,
|
||||||
headerSetting: { theme: headerTheme, useLockPage, showRedo, showGithub, showFullScreen },
|
headerSetting: {
|
||||||
|
theme: headerTheme,
|
||||||
|
useLockPage,
|
||||||
|
showRedo,
|
||||||
|
showGithub,
|
||||||
|
showFullScreen,
|
||||||
|
showNotice,
|
||||||
|
},
|
||||||
menuSetting: { mode, type: menuType, split: splitMenu, topMenuAlign },
|
menuSetting: { mode, type: menuType, split: splitMenu, topMenuAlign },
|
||||||
showBreadCrumb,
|
showBreadCrumb,
|
||||||
} = getProjectConfig;
|
} = getProjectConfig;
|
||||||
|
|
@ -163,6 +171,20 @@ export default defineComponent({
|
||||||
}}
|
}}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
{showNotice && (
|
||||||
|
<div>
|
||||||
|
<Tooltip>
|
||||||
|
{{
|
||||||
|
title: () => '消息中心',
|
||||||
|
default: () => (
|
||||||
|
<div class={`layout-header__action-item`}>
|
||||||
|
<NoticeAction />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{showRedo && (
|
{showRedo && (
|
||||||
<Tooltip>
|
<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}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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,
|
showDoc: true,
|
||||||
// 是否显示github
|
// 是否显示github
|
||||||
showGithub: true,
|
showGithub: true,
|
||||||
|
// 显示消息中心按钮
|
||||||
|
showNotice: true,
|
||||||
},
|
},
|
||||||
// 菜单配置
|
// 菜单配置
|
||||||
menuSetting: {
|
menuSetting: {
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,8 @@ export interface HeaderSetting {
|
||||||
// 显示文档按钮
|
// 显示文档按钮
|
||||||
showDoc: boolean;
|
showDoc: boolean;
|
||||||
showGithub: boolean;
|
showGithub: boolean;
|
||||||
|
// 显示消息中心按钮
|
||||||
|
showNotice: boolean;
|
||||||
}
|
}
|
||||||
export interface ProjectConfig {
|
export interface ProjectConfig {
|
||||||
// 是否显示配置按钮
|
// 是否显示配置按钮
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue