Commit 7a1e94c49d546e155d8c17b492ff6b1e5fb55121

Authored by chen-xt
Committed by GitHub
1 parent 463aeabd

feat: add notice (#47)

src/layouts/default/LayoutHeader.tsx
@@ -22,6 +22,7 @@ import { useModal } from '/@/components/Modal/index'; @@ -22,6 +22,7 @@ import { useModal } from '/@/components/Modal/index';
22 import { errorStore } from '/@/store/modules/error'; 22 import { errorStore } from '/@/store/modules/error';
23 import { useGo } from '/@/hooks/web/usePage'; 23 import { useGo } from '/@/hooks/web/usePage';
24 import { useWindowSizeFn } from '/@/hooks/event/useWindowSize'; 24 import { useWindowSizeFn } from '/@/hooks/event/useWindowSize';
  25 +import NoticeAction from './actions/notice/NoticeActionItem.vue';
25 26
26 export default defineComponent({ 27 export default defineComponent({
27 name: 'DefaultLayoutHeader', 28 name: 'DefaultLayoutHeader',
@@ -85,7 +86,14 @@ export default defineComponent({ @@ -85,7 +86,14 @@ export default defineComponent({
85 const { 86 const {
86 useErrorHandle, 87 useErrorHandle,
87 showLogo, 88 showLogo,
88 - headerSetting: { theme: headerTheme, useLockPage, showRedo, showGithub, showFullScreen }, 89 + headerSetting: {
  90 + theme: headerTheme,
  91 + useLockPage,
  92 + showRedo,
  93 + showGithub,
  94 + showFullScreen,
  95 + showNotice,
  96 + },
89 menuSetting: { mode, type: menuType, split: splitMenu, topMenuAlign }, 97 menuSetting: { mode, type: menuType, split: splitMenu, topMenuAlign },
90 showBreadCrumb, 98 showBreadCrumb,
91 } = getProjectConfig; 99 } = getProjectConfig;
@@ -163,6 +171,20 @@ export default defineComponent({ @@ -163,6 +171,20 @@ export default defineComponent({
163 }} 171 }}
164 </Tooltip> 172 </Tooltip>
165 )} 173 )}
  174 + {showNotice && (
  175 + <div>
  176 + <Tooltip>
  177 + {{
  178 + title: () => '消息中心',
  179 + default: () => (
  180 + <div class={`layout-header__action-item`}>
  181 + <NoticeAction />
  182 + </div>
  183 + ),
  184 + }}
  185 + </Tooltip>
  186 + </div>
  187 + )}
166 {showRedo && ( 188 {showRedo && (
167 <Tooltip> 189 <Tooltip>
168 {{ 190 {{
src/layouts/default/actions/notice/NoticeActionItem.tsx deleted 100644 → 0
1 -import { defineComponent } from 'vue';  
2 -import { Popover, Tabs } from 'ant-design-vue';  
3 -  
4 -import NoticeList from './NoticeList';  
5 -import { NoticeTabItem, NoticeListItem, noticeTabListData, noticeListData } from './data';  
6 -import './index.less';  
7 -  
8 -const prefixCls = 'notice-popover';  
9 -export default defineComponent({  
10 - name: 'NoticePopover',  
11 - props: {  
12 - visible: {  
13 - type: Boolean,  
14 - default: false,  
15 - },  
16 - },  
17 - setup(props, { attrs }) {  
18 - // 渲染卡片内容  
19 - function renderContent() {  
20 - return (  
21 - <Tabs class={`${prefixCls}__tabs`}>  
22 - {() => {  
23 - return noticeTabListData.map((item: NoticeTabItem) => {  
24 - const { key, name } = item;  
25 - return (  
26 - <Tabs.TabPane key={key} tab={renderTab(key, name)}>  
27 - {() => <NoticeList list={getListData(key)} />}  
28 - </Tabs.TabPane>  
29 - );  
30 - });  
31 - }}  
32 - </Tabs>  
33 - );  
34 - }  
35 -  
36 - // tab标题渲染  
37 - function renderTab(key: string, name: string) {  
38 - const list = getListData(key);  
39 - const unreadlist = list.filter((item: NoticeListItem) => !item.read);  
40 - return (  
41 - <div>  
42 - {name}  
43 - {unreadlist.length > 0 && <span>({unreadlist.length})</span>}  
44 - </div>  
45 - );  
46 - }  
47 -  
48 - // 获取数据  
49 - function getListData(type: string) {  
50 - return noticeListData.filter((item: NoticeListItem) => item.type === type);  
51 - }  
52 -  
53 - return () => {  
54 - const { visible } = props;  
55 - return (  
56 - <Popover  
57 - title=""  
58 - {...{  
59 - ...attrs,  
60 - visible,  
61 - }}  
62 - content={renderContent}  
63 - class={prefixCls}  
64 - />  
65 - );  
66 - };  
67 - },  
68 -});  
src/layouts/default/actions/notice/NoticeActionItem.vue 0 → 100644
  1 +<template>
  2 + <div>
  3 + <Popover title="" trigger="click">
  4 + <Badge :count="count" :numberStyle="numberStyle">
  5 + <BellOutlined class="layout-header__action-icon" />
  6 + </Badge>
  7 + <template #content>
  8 + <Tabs>
  9 + <template v-for="item in tabListData" :key="item.key">
  10 + <TabPane>
  11 + <template #tab>
  12 + {{ item.name }}
  13 + <span v-if="item.list.length !== 0">({{ item.list.length }})</span>
  14 + </template>
  15 + <NoticeList :list="item.list" />
  16 + </TabPane>
  17 + </template>
  18 + </Tabs>
  19 + </template>
  20 + </Popover>
  21 + </div>
  22 +</template>
  23 +<script lang="ts">
  24 + import { defineComponent } from 'vue';
  25 + import { Popover, Tabs, Badge } from 'ant-design-vue';
  26 + import { BellOutlined } from '@ant-design/icons-vue';
  27 + import { tabListData } from './data';
  28 + import NoticeList from './NoticeList.vue';
  29 +
  30 + export default defineComponent({
  31 + components: { Popover, BellOutlined, Tabs, TabPane: Tabs.TabPane, Badge, NoticeList },
  32 + setup() {
  33 + let count = 0;
  34 + for (let i = 0; i < tabListData.length; i++) {
  35 + count += tabListData[i].list.length;
  36 + }
  37 +
  38 + return {
  39 + tabListData,
  40 + count,
  41 + numberStyle: {},
  42 + };
  43 + },
  44 + });
  45 +</script>
  46 +<style lang="less" scoped>
  47 + /deep/ .ant-tabs-tab {
  48 + padding-top: 8px;
  49 + margin-right: 12px;
  50 + }
  51 +
  52 + /deep/ .ant-tabs-content {
  53 + width: 300px;
  54 + }
  55 +
  56 + /deep/ .ant-badge {
  57 + font-size: 18px;
  58 +
  59 + .ant-badge-multiple-words {
  60 + padding: 0 4px;
  61 + transform: translate(26%, -48%);
  62 + }
  63 + }
  64 +</style>
src/layouts/default/actions/notice/NoticeList.tsx deleted 100644 → 0
1 -import { defineComponent } from 'vue';  
2 -import { List, Avatar, Tag } from 'ant-design-vue';  
3 -  
4 -import { NoticeListItem } from './data';  
5 -import './index.less';  
6 -  
7 -const prefixCls = 'notice-popover';  
8 -export default defineComponent({  
9 - name: 'NoticeList',  
10 - props: {  
11 - list: {  
12 - type: Array,  
13 - default: () => [],  
14 - },  
15 - },  
16 - setup(props) {  
17 - // 头像渲染  
18 - function renderAvatar(avatar: string) {  
19 - return avatar ? <Avatar class="avatar" src={avatar} /> : <span>{avatar}</span>;  
20 - }  
21 -  
22 - // 描述渲染  
23 - function renderDescription(description: string, datetime: string) {  
24 - return (  
25 - <div>  
26 - <div class="description">{description}</div>  
27 - <div class="datetime">{datetime}</div>  
28 - </div>  
29 - );  
30 - }  
31 -  
32 - // 标题渲染  
33 - function renderTitle(title: string, extra?: string, color?: string) {  
34 - return (  
35 - <div class="title">  
36 - {title}  
37 - {extra && (  
38 - <div class="extra">  
39 - <Tag class="tag" color={color}>  
40 - {() => extra}  
41 - </Tag>  
42 - </div>  
43 - )}  
44 - </div>  
45 - );  
46 - }  
47 -  
48 - return () => {  
49 - const { list } = props;  
50 - return (  
51 - <List dataSource={list} class={`${prefixCls}__list`}>  
52 - {() => {  
53 - return list.map((item: NoticeListItem) => {  
54 - const { id, avatar, title, description, datetime, extra, read, color } = item;  
55 - return (  
56 - <List.Item key={id} class={`${prefixCls}__list-item ${read ? 'read' : ''}`}>  
57 - {() => (  
58 - <List.Item.Meta  
59 - class="meta"  
60 - avatar={renderAvatar(avatar)}  
61 - title={renderTitle(title, extra, color)}  
62 - description={renderDescription(description, datetime)}  
63 - />  
64 - )}  
65 - </List.Item>  
66 - );  
67 - });  
68 - }}  
69 - </List>  
70 - );  
71 - };  
72 - },  
73 -});  
src/layouts/default/actions/notice/NoticeList.vue 0 → 100644
  1 +<template>
  2 + <List class="list">
  3 + <template v-for="item in list" :key="item.id">
  4 + <ListItem class="list__item">
  5 + <ListItemMeta>
  6 + <template #title>
  7 + <div class="title">
  8 + {{ item.title }}
  9 + <div class="extra" v-if="item.extra">
  10 + <Tag class="tag" :color="item.color">
  11 + {{ item.extra }}
  12 + </Tag>
  13 + </div>
  14 + </div>
  15 + </template>
  16 + <template #avatar>
  17 + <Avatar v-if="item.avatar" class="avatar" :src="item.avatar" />
  18 + <span v-else> {{ item.avatar }}</span>
  19 + </template>
  20 + <template #description>
  21 + <div>
  22 + <div class="description">{{ item.description }}</div>
  23 + <div class="datetime">{{ item.datetime }}</div>
  24 + </div>
  25 + </template>
  26 + </ListItemMeta>
  27 + </ListItem>
  28 + </template>
  29 + </List>
  30 +</template>
  31 +<script lang="ts">
  32 + import { defineComponent, PropType } from 'vue';
  33 + import { List, Avatar, Tag } from 'ant-design-vue';
  34 + import { ListItem } from './data';
  35 +
  36 + export default defineComponent({
  37 + props: {
  38 + list: {
  39 + type: Array as PropType<Array<ListItem>>,
  40 + default: () => [],
  41 + },
  42 + },
  43 + components: {
  44 + List,
  45 + ListItem: List.Item,
  46 + ListItemMeta: List.Item.Meta,
  47 + Avatar,
  48 + Tag,
  49 + },
  50 + setup(props) {
  51 + const { list = [] } = props;
  52 + return {
  53 + list,
  54 + };
  55 + },
  56 + });
  57 +</script>
  58 +<style lang="less" scoped>
  59 + .list {
  60 + &::-webkit-scrollbar {
  61 + display: none;
  62 + }
  63 +
  64 + &__item {
  65 + padding: 6px;
  66 + overflow: hidden;
  67 + cursor: pointer;
  68 + transition: all 0.3s;
  69 +
  70 + .title {
  71 + margin-bottom: 8px;
  72 + font-weight: normal;
  73 +
  74 + .extra {
  75 + float: right;
  76 + margin-top: -1.5px;
  77 + margin-right: 0;
  78 + font-weight: normal;
  79 +
  80 + .tag {
  81 + margin-right: 0;
  82 + }
  83 + }
  84 +
  85 + .avatar {
  86 + margin-top: 4px;
  87 + }
  88 +
  89 + .description {
  90 + font-size: 12px;
  91 + line-height: 18px;
  92 + }
  93 +
  94 + .datetime {
  95 + margin-top: 4px;
  96 + font-size: 12px;
  97 + line-height: 18px;
  98 + }
  99 + }
  100 + }
  101 + }
  102 +</style>
src/layouts/default/actions/notice/data.ts 0 → 100644
  1 +export interface ListItem {
  2 + id: string;
  3 + avatar: string;
  4 + title: string;
  5 + datetime: string;
  6 + type: string;
  7 + read?: boolean;
  8 + description: string;
  9 + clickClose?: boolean;
  10 + extra?: string;
  11 + color?: string;
  12 +}
  13 +
  14 +export interface TabItem {
  15 + key: string;
  16 + name: string;
  17 + list: ListItem[];
  18 + unreadlist?: ListItem[];
  19 +}
  20 +
  21 +export const tabListData: TabItem[] = [
  22 + {
  23 + key: '1',
  24 + name: '通知',
  25 + list: [
  26 + {
  27 + id: '000000001',
  28 + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
  29 + title: '你收到了 14 份新周报',
  30 + description: '',
  31 + datetime: '2017-08-09',
  32 + type: '1',
  33 + },
  34 + {
  35 + id: '000000002',
  36 + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
  37 + title: '你推荐的 曲妮妮 已通过第三轮面试',
  38 + description: '',
  39 + datetime: '2017-08-08',
  40 + type: '1',
  41 + },
  42 + {
  43 + id: '000000003',
  44 + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
  45 + title: '这种模板可以区分多种通知类型',
  46 + description: '',
  47 + datetime: '2017-08-07',
  48 + // read: true,
  49 + type: '1',
  50 + },
  51 + {
  52 + id: '000000004',
  53 + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
  54 + title: '左侧图标用于区分不同的类型',
  55 + description: '',
  56 + datetime: '2017-08-07',
  57 + type: '1',
  58 + },
  59 + // {
  60 + // id: '000000005',
  61 + // avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
  62 + // title: '内容不要超过两行字,超出时自动截断',
  63 + // description: '',
  64 + // datetime: '2017-08-07',
  65 + // type: '1',
  66 + // },
  67 + ],
  68 + },
  69 + {
  70 + key: '2',
  71 + name: '消息',
  72 + list: [
  73 + {
  74 + id: '000000006',
  75 + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
  76 + title: '曲丽丽 评论了你',
  77 + description: '描述信息描述信息描述信息',
  78 + datetime: '2017-08-07',
  79 + type: '2',
  80 + clickClose: true,
  81 + },
  82 + {
  83 + id: '000000007',
  84 + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
  85 + title: '朱偏右 回复了你',
  86 + description: '这种模板用于提醒谁与你发生了互动',
  87 + datetime: '2017-08-07',
  88 + type: '2',
  89 + clickClose: true,
  90 + },
  91 + {
  92 + id: '000000008',
  93 + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
  94 + title: '标题',
  95 + description: '这种模板用于提醒谁与你发生了互动',
  96 + datetime: '2017-08-07',
  97 + type: '2',
  98 + clickClose: true,
  99 + },
  100 + ],
  101 + },
  102 + {
  103 + key: '3',
  104 + name: '待办',
  105 + list: [
  106 + {
  107 + id: '000000009',
  108 + avatar: '',
  109 + title: '任务名称',
  110 + description: '任务需要在 2017-01-12 20:00 前启动',
  111 + datetime: '',
  112 + extra: '未开始',
  113 + color: '',
  114 + type: '3',
  115 + },
  116 + {
  117 + id: '000000010',
  118 + avatar: '',
  119 + title: '第三方紧急代码变更',
  120 + description: '冠霖 需在 2017-01-07 前完成代码变更任务',
  121 + datetime: '',
  122 + extra: '马上到期',
  123 + color: 'red',
  124 + type: '3',
  125 + },
  126 + {
  127 + id: '000000011',
  128 + avatar: '',
  129 + title: '信息安全考试',
  130 + description: '指派竹尔于 2017-01-09 前完成更新并发布',
  131 + datetime: '',
  132 + extra: '已耗时 8 天',
  133 + color: 'gold',
  134 + type: '3',
  135 + },
  136 + {
  137 + id: '000000012',
  138 + avatar: '',
  139 + title: 'ABCD 版本发布',
  140 + description: '指派竹尔于 2017-01-09 前完成更新并发布',
  141 + datetime: '',
  142 + extra: '进行中',
  143 + color: 'blue',
  144 + type: '3',
  145 + },
  146 + ],
  147 + },
  148 +];
src/settings/projectSetting.ts
@@ -39,6 +39,8 @@ const setting: ProjectConfig = { @@ -39,6 +39,8 @@ const setting: ProjectConfig = {
39 showDoc: true, 39 showDoc: true,
40 // 是否显示github 40 // 是否显示github
41 showGithub: true, 41 showGithub: true,
  42 + // 显示消息中心按钮
  43 + showNotice: true,
42 }, 44 },
43 // 菜单配置 45 // 菜单配置
44 menuSetting: { 46 menuSetting: {
src/types/config.d.ts
@@ -47,6 +47,8 @@ export interface HeaderSetting { @@ -47,6 +47,8 @@ export interface HeaderSetting {
47 // 显示文档按钮 47 // 显示文档按钮
48 showDoc: boolean; 48 showDoc: boolean;
49 showGithub: boolean; 49 showGithub: boolean;
  50 + // 显示消息中心按钮
  51 + showNotice: boolean;
50 } 52 }
51 export interface ProjectConfig { 53 export interface ProjectConfig {
52 // 是否显示配置按钮 54 // 是否显示配置按钮