Commit 7a1e94c49d546e155d8c17b492ff6b1e5fb55121
Committed by
GitHub
1 parent
463aeabd
feat: add notice (#47)
Showing
8 changed files
with
341 additions
and
142 deletions
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 | // 是否显示配置按钮 |