Commit c16be2c499d90126dfa35d699da9294c21a4ab48
1 parent
0f28e803
feat(notice-list): add `pagination` support
为通知列表组件添加分页、超长自动省略、标题点击响应、标题删除线等功能 fixed: #894
Showing
4 changed files
with
164 additions
and
18 deletions
CHANGELOG.zh_CN.md
src/layouts/default/header/components/notify/NoticeList.vue
1 | 1 | <template> |
2 | - <a-list :class="prefixCls"> | |
3 | - <template v-for="item in list" :key="item.id"> | |
2 | + <a-list :class="prefixCls" bordered :pagination="getPagination" size="small"> | |
3 | + <template v-for="item in getData" :key="item.id"> | |
4 | 4 | <a-list-item class="list-item"> |
5 | 5 | <a-list-item-meta> |
6 | 6 | <template #title> |
7 | 7 | <div class="title"> |
8 | - {{ item.title }} | |
8 | + <a-typography-paragraph | |
9 | + @click="handleTitleClick(item)" | |
10 | + style="width: 100%" | |
11 | + :style="{ cursor: isTitleClickable ? 'pointer' : '' }" | |
12 | + :delete="!!item.titleDelete" | |
13 | + :ellipsis=" | |
14 | + $props.titleRows > 0 ? { rows: $props.titleRows, tooltip: item.title } : false | |
15 | + " | |
16 | + :content="item.title" | |
17 | + /> | |
9 | 18 | <div class="extra" v-if="item.extra"> |
10 | 19 | <a-tag class="tag" :color="item.color"> |
11 | 20 | {{ item.extra }} |
... | ... | @@ -21,8 +30,16 @@ |
21 | 30 | |
22 | 31 | <template #description> |
23 | 32 | <div> |
24 | - <div class="description"> | |
25 | - {{ item.description }} | |
33 | + <div class="description" v-if="item.description"> | |
34 | + <a-typography-paragraph | |
35 | + style="width: 100%" | |
36 | + :ellipsis=" | |
37 | + $props.descRows > 0 | |
38 | + ? { rows: $props.descRows, tooltip: item.description } | |
39 | + : false | |
40 | + " | |
41 | + :content="item.description" | |
42 | + /> | |
26 | 43 | </div> |
27 | 44 | <div class="datetime"> |
28 | 45 | {{ item.datetime }} |
... | ... | @@ -35,16 +52,18 @@ |
35 | 52 | </a-list> |
36 | 53 | </template> |
37 | 54 | <script lang="ts"> |
38 | - import { defineComponent, PropType } from 'vue'; | |
55 | + import { computed, defineComponent, PropType, ref, watch, unref } from 'vue'; | |
39 | 56 | import { ListItem } from './data'; |
40 | 57 | import { useDesign } from '/@/hooks/web/useDesign'; |
41 | - import { List, Avatar, Tag } from 'ant-design-vue'; | |
58 | + import { List, Avatar, Tag, Typography } from 'ant-design-vue'; | |
59 | + import { isNumber } from '/@/utils/is'; | |
42 | 60 | export default defineComponent({ |
43 | 61 | components: { |
44 | 62 | [Avatar.name]: Avatar, |
45 | 63 | [List.name]: List, |
46 | 64 | [List.Item.name]: List.Item, |
47 | 65 | AListItemMeta: List.Item.Meta, |
66 | + ATypographyParagraph: Typography.Paragraph, | |
48 | 67 | [Tag.name]: Tag, |
49 | 68 | }, |
50 | 69 | props: { |
... | ... | @@ -52,10 +71,66 @@ |
52 | 71 | type: Array as PropType<ListItem[]>, |
53 | 72 | default: () => [], |
54 | 73 | }, |
74 | + pageSize: { | |
75 | + type: [Boolean, Number] as PropType<Boolean | Number>, | |
76 | + default: 5, | |
77 | + }, | |
78 | + currentPage: { | |
79 | + type: Number, | |
80 | + default: 1, | |
81 | + }, | |
82 | + titleRows: { | |
83 | + type: Number, | |
84 | + default: 1, | |
85 | + }, | |
86 | + descRows: { | |
87 | + type: Number, | |
88 | + default: 2, | |
89 | + }, | |
90 | + onTitleClick: { | |
91 | + type: Function as PropType<(Recordable) => void>, | |
92 | + }, | |
55 | 93 | }, |
56 | - setup() { | |
94 | + emits: ['update:currentPage'], | |
95 | + setup(props, { emit }) { | |
57 | 96 | const { prefixCls } = useDesign('header-notify-list'); |
58 | - return { prefixCls }; | |
97 | + const current = ref(props.currentPage || 1); | |
98 | + const getData = computed(() => { | |
99 | + const { pageSize, list } = props; | |
100 | + console.log('refreshData', list); | |
101 | + if (pageSize === false) return []; | |
102 | + let size = isNumber(pageSize) ? pageSize : 5; | |
103 | + return list.slice(size * (unref(current) - 1), size * unref(current)); | |
104 | + }); | |
105 | + watch( | |
106 | + () => props.currentPage, | |
107 | + (v) => { | |
108 | + current.value = v; | |
109 | + } | |
110 | + ); | |
111 | + const isTitleClickable = computed(() => !!props.onTitleClick); | |
112 | + const getPagination = computed(() => { | |
113 | + const { list, pageSize } = props; | |
114 | + if (pageSize > 0 && list && list.length > pageSize) { | |
115 | + return { | |
116 | + total: list.length, | |
117 | + pageSize, | |
118 | + current: unref(current), | |
119 | + onChange(page) { | |
120 | + current.value = page; | |
121 | + emit('update:currentPage', page); | |
122 | + }, | |
123 | + }; | |
124 | + } else { | |
125 | + return false; | |
126 | + } | |
127 | + }); | |
128 | + | |
129 | + function handleTitleClick(item: ListItem) { | |
130 | + props.onTitleClick && props.onTitleClick(item); | |
131 | + } | |
132 | + | |
133 | + return { prefixCls, getPagination, getData, handleTitleClick, isTitleClickable }; | |
59 | 134 | }, |
60 | 135 | }); |
61 | 136 | </script> | ... | ... |
src/layouts/default/header/components/notify/data.ts
1 | 1 | export interface ListItem { |
2 | 2 | id: string; |
3 | 3 | avatar: string; |
4 | + // 通知的标题内容 | |
4 | 5 | title: string; |
6 | + // 是否在标题上显示删除线 | |
7 | + titleDelete?: boolean; | |
5 | 8 | datetime: string; |
6 | 9 | type: string; |
7 | 10 | read?: boolean; |
... | ... | @@ -56,6 +59,55 @@ export const tabListData: TabItem[] = [ |
56 | 59 | datetime: '2017-08-07', |
57 | 60 | type: '1', |
58 | 61 | }, |
62 | + { | |
63 | + id: '000000005', | |
64 | + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', | |
65 | + title: | |
66 | + '标题可以设置自动显示省略号,本例中标题行数已设为1行,如果内容超过1行将自动截断并支持tooltip显示完整标题。', | |
67 | + description: '', | |
68 | + datetime: '2017-08-07', | |
69 | + type: '1', | |
70 | + }, | |
71 | + { | |
72 | + id: '000000006', | |
73 | + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', | |
74 | + title: '左侧图标用于区分不同的类型', | |
75 | + description: '', | |
76 | + datetime: '2017-08-07', | |
77 | + type: '1', | |
78 | + }, | |
79 | + { | |
80 | + id: '000000007', | |
81 | + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', | |
82 | + title: '左侧图标用于区分不同的类型', | |
83 | + description: '', | |
84 | + datetime: '2017-08-07', | |
85 | + type: '1', | |
86 | + }, | |
87 | + { | |
88 | + id: '000000008', | |
89 | + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', | |
90 | + title: '左侧图标用于区分不同的类型', | |
91 | + description: '', | |
92 | + datetime: '2017-08-07', | |
93 | + type: '1', | |
94 | + }, | |
95 | + { | |
96 | + id: '000000009', | |
97 | + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', | |
98 | + title: '左侧图标用于区分不同的类型', | |
99 | + description: '', | |
100 | + datetime: '2017-08-07', | |
101 | + type: '1', | |
102 | + }, | |
103 | + { | |
104 | + id: '000000010', | |
105 | + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', | |
106 | + title: '左侧图标用于区分不同的类型', | |
107 | + description: '', | |
108 | + datetime: '2017-08-07', | |
109 | + type: '1', | |
110 | + }, | |
59 | 111 | ], |
60 | 112 | }, |
61 | 113 | { |
... | ... | @@ -84,7 +136,8 @@ export const tabListData: TabItem[] = [ |
84 | 136 | id: '000000008', |
85 | 137 | avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', |
86 | 138 | title: '标题', |
87 | - description: '这种模板用于提醒谁与你发生了互动', | |
139 | + description: | |
140 | + '请将鼠标移动到此处,以便测试超长的消息在此处将如何处理。本例中设置的描述最大行数为2,超过2行的描述内容将被省略并且可以通过tooltip查看完整内容', | |
88 | 141 | datetime: '2017-08-07', |
89 | 142 | type: '2', |
90 | 143 | clickClose: true, | ... | ... |
src/layouts/default/header/components/notify/index.vue
... | ... | @@ -6,13 +6,15 @@ |
6 | 6 | </Badge> |
7 | 7 | <template #content> |
8 | 8 | <Tabs> |
9 | - <template v-for="item in tabListData" :key="item.key"> | |
9 | + <template v-for="item in listData" :key="item.key"> | |
10 | 10 | <TabPane> |
11 | 11 | <template #tab> |
12 | 12 | {{ item.name }} |
13 | 13 | <span v-if="item.list.length !== 0">({{ item.list.length }})</span> |
14 | 14 | </template> |
15 | - <NoticeList :list="item.list" /> | |
15 | + <!-- 绑定title-click事件的通知列表中标题是“可点击”的--> | |
16 | + <NoticeList :list="item.list" v-if="item.key === '1'" @title-click="onNoticeClick" /> | |
17 | + <NoticeList :list="item.list" v-else /> | |
16 | 18 | </TabPane> |
17 | 19 | </template> |
18 | 20 | </Tabs> |
... | ... | @@ -21,28 +23,40 @@ |
21 | 23 | </div> |
22 | 24 | </template> |
23 | 25 | <script lang="ts"> |
24 | - import { defineComponent } from 'vue'; | |
26 | + import { computed, defineComponent, ref } from 'vue'; | |
25 | 27 | import { Popover, Tabs, Badge } from 'ant-design-vue'; |
26 | 28 | import { BellOutlined } from '@ant-design/icons-vue'; |
27 | - import { tabListData } from './data'; | |
29 | + import { tabListData, ListItem } from './data'; | |
28 | 30 | import NoticeList from './NoticeList.vue'; |
29 | 31 | import { useDesign } from '/@/hooks/web/useDesign'; |
32 | + import { useMessage } from '/@/hooks/web/useMessage'; | |
30 | 33 | |
31 | 34 | export default defineComponent({ |
32 | 35 | components: { Popover, BellOutlined, Tabs, TabPane: Tabs.TabPane, Badge, NoticeList }, |
33 | 36 | setup() { |
34 | 37 | const { prefixCls } = useDesign('header-notify'); |
38 | + const { createMessage } = useMessage(); | |
39 | + const listData = ref(tabListData); | |
35 | 40 | |
36 | - let count = 0; | |
41 | + const count = computed(() => { | |
42 | + let count = 0; | |
43 | + for (let i = 0; i < tabListData.length; i++) { | |
44 | + count += tabListData[i].list.length; | |
45 | + } | |
46 | + return count; | |
47 | + }); | |
37 | 48 | |
38 | - for (let i = 0; i < tabListData.length; i++) { | |
39 | - count += tabListData[i].list.length; | |
49 | + function onNoticeClick(record: ListItem) { | |
50 | + createMessage.success('你点击了通知,ID=' + record.id); | |
51 | + // 可以直接将其标记为已读(为标题添加删除线),此处演示的代码会切换删除线状态 | |
52 | + record.titleDelete = !record.titleDelete; | |
40 | 53 | } |
41 | 54 | |
42 | 55 | return { |
43 | 56 | prefixCls, |
44 | - tabListData, | |
57 | + listData, | |
45 | 58 | count, |
59 | + onNoticeClick, | |
46 | 60 | numberStyle: {}, |
47 | 61 | }; |
48 | 62 | }, | ... | ... |