Jelajahi Sumber

feat(notice-list): add `pagination` support

为通知列表组件添加分页、超长自动省略、标题点击响应、标题删除线等功能

fixed: #894
无木 3 tahun lalu
induk
melakukan
c16be2c499

+ 4 - 0
CHANGELOG.zh_CN.md

@@ -1,3 +1,7 @@
+### ✨ Features
+
+- **NoticeList** 添加分页、超长自动省略、标题点击事件、标题删除线等功能
+
 ### 🐛 Bug Fixes
 
 - **Table**

+ 84 - 9
src/layouts/default/header/components/notify/NoticeList.vue

@@ -1,11 +1,20 @@
 <template>
-  <a-list :class="prefixCls">
-    <template v-for="item in list" :key="item.id">
+  <a-list :class="prefixCls" bordered :pagination="getPagination" size="small">
+    <template v-for="item in getData" :key="item.id">
       <a-list-item class="list-item">
         <a-list-item-meta>
           <template #title>
             <div class="title">
-              {{ item.title }}
+              <a-typography-paragraph
+                @click="handleTitleClick(item)"
+                style="width: 100%"
+                :style="{ cursor: isTitleClickable ? 'pointer' : '' }"
+                :delete="!!item.titleDelete"
+                :ellipsis="
+                  $props.titleRows > 0 ? { rows: $props.titleRows, tooltip: item.title } : false
+                "
+                :content="item.title"
+              />
               <div class="extra" v-if="item.extra">
                 <a-tag class="tag" :color="item.color">
                   {{ item.extra }}
@@ -21,8 +30,16 @@
 
           <template #description>
             <div>
-              <div class="description">
-                {{ item.description }}
+              <div class="description" v-if="item.description">
+                <a-typography-paragraph
+                  style="width: 100%"
+                  :ellipsis="
+                    $props.descRows > 0
+                      ? { rows: $props.descRows, tooltip: item.description }
+                      : false
+                  "
+                  :content="item.description"
+                />
               </div>
               <div class="datetime">
                 {{ item.datetime }}
@@ -35,16 +52,18 @@
   </a-list>
 </template>
 <script lang="ts">
-  import { defineComponent, PropType } from 'vue';
+  import { computed, defineComponent, PropType, ref, watch, unref } from 'vue';
   import { ListItem } from './data';
   import { useDesign } from '/@/hooks/web/useDesign';
-  import { List, Avatar, Tag } from 'ant-design-vue';
+  import { List, Avatar, Tag, Typography } from 'ant-design-vue';
+  import { isNumber } from '/@/utils/is';
   export default defineComponent({
     components: {
       [Avatar.name]: Avatar,
       [List.name]: List,
       [List.Item.name]: List.Item,
       AListItemMeta: List.Item.Meta,
+      ATypographyParagraph: Typography.Paragraph,
       [Tag.name]: Tag,
     },
     props: {
@@ -52,10 +71,66 @@
         type: Array as PropType<ListItem[]>,
         default: () => [],
       },
+      pageSize: {
+        type: [Boolean, Number] as PropType<Boolean | Number>,
+        default: 5,
+      },
+      currentPage: {
+        type: Number,
+        default: 1,
+      },
+      titleRows: {
+        type: Number,
+        default: 1,
+      },
+      descRows: {
+        type: Number,
+        default: 2,
+      },
+      onTitleClick: {
+        type: Function as PropType<(Recordable) => void>,
+      },
     },
-    setup() {
+    emits: ['update:currentPage'],
+    setup(props, { emit }) {
       const { prefixCls } = useDesign('header-notify-list');
-      return { prefixCls };
+      const current = ref(props.currentPage || 1);
+      const getData = computed(() => {
+        const { pageSize, list } = props;
+        console.log('refreshData', list);
+        if (pageSize === false) return [];
+        let size = isNumber(pageSize) ? pageSize : 5;
+        return list.slice(size * (unref(current) - 1), size * unref(current));
+      });
+      watch(
+        () => props.currentPage,
+        (v) => {
+          current.value = v;
+        }
+      );
+      const isTitleClickable = computed(() => !!props.onTitleClick);
+      const getPagination = computed(() => {
+        const { list, pageSize } = props;
+        if (pageSize > 0 && list && list.length > pageSize) {
+          return {
+            total: list.length,
+            pageSize,
+            current: unref(current),
+            onChange(page) {
+              current.value = page;
+              emit('update:currentPage', page);
+            },
+          };
+        } else {
+          return false;
+        }
+      });
+
+      function handleTitleClick(item: ListItem) {
+        props.onTitleClick && props.onTitleClick(item);
+      }
+
+      return { prefixCls, getPagination, getData, handleTitleClick, isTitleClickable };
     },
   });
 </script>

+ 54 - 1
src/layouts/default/header/components/notify/data.ts

@@ -1,7 +1,10 @@
 export interface ListItem {
   id: string;
   avatar: string;
+  // 通知的标题内容
   title: string;
+  // 是否在标题上显示删除线
+  titleDelete?: boolean;
   datetime: string;
   type: string;
   read?: boolean;
@@ -56,6 +59,55 @@ export const tabListData: TabItem[] = [
         datetime: '2017-08-07',
         type: '1',
       },
+      {
+        id: '000000005',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
+        title:
+          '标题可以设置自动显示省略号,本例中标题行数已设为1行,如果内容超过1行将自动截断并支持tooltip显示完整标题。',
+        description: '',
+        datetime: '2017-08-07',
+        type: '1',
+      },
+      {
+        id: '000000006',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
+        title: '左侧图标用于区分不同的类型',
+        description: '',
+        datetime: '2017-08-07',
+        type: '1',
+      },
+      {
+        id: '000000007',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
+        title: '左侧图标用于区分不同的类型',
+        description: '',
+        datetime: '2017-08-07',
+        type: '1',
+      },
+      {
+        id: '000000008',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
+        title: '左侧图标用于区分不同的类型',
+        description: '',
+        datetime: '2017-08-07',
+        type: '1',
+      },
+      {
+        id: '000000009',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
+        title: '左侧图标用于区分不同的类型',
+        description: '',
+        datetime: '2017-08-07',
+        type: '1',
+      },
+      {
+        id: '000000010',
+        avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
+        title: '左侧图标用于区分不同的类型',
+        description: '',
+        datetime: '2017-08-07',
+        type: '1',
+      },
     ],
   },
   {
@@ -84,7 +136,8 @@ export const tabListData: TabItem[] = [
         id: '000000008',
         avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
         title: '标题',
-        description: '这种模板用于提醒谁与你发生了互动',
+        description:
+          '请将鼠标移动到此处,以便测试超长的消息在此处将如何处理。本例中设置的描述最大行数为2,超过2行的描述内容将被省略并且可以通过tooltip查看完整内容',
         datetime: '2017-08-07',
         type: '2',
         clickClose: true,

+ 22 - 8
src/layouts/default/header/components/notify/index.vue

@@ -6,13 +6,15 @@
       </Badge>
       <template #content>
         <Tabs>
-          <template v-for="item in tabListData" :key="item.key">
+          <template v-for="item in listData" :key="item.key">
             <TabPane>
               <template #tab>
                 {{ item.name }}
                 <span v-if="item.list.length !== 0">({{ item.list.length }})</span>
               </template>
-              <NoticeList :list="item.list" />
+              <!-- 绑定title-click事件的通知列表中标题是“可点击”的-->
+              <NoticeList :list="item.list" v-if="item.key === '1'" @title-click="onNoticeClick" />
+              <NoticeList :list="item.list" v-else />
             </TabPane>
           </template>
         </Tabs>
@@ -21,28 +23,40 @@
   </div>
 </template>
 <script lang="ts">
-  import { defineComponent } from 'vue';
+  import { computed, defineComponent, ref } from 'vue';
   import { Popover, Tabs, Badge } from 'ant-design-vue';
   import { BellOutlined } from '@ant-design/icons-vue';
-  import { tabListData } from './data';
+  import { tabListData, ListItem } from './data';
   import NoticeList from './NoticeList.vue';
   import { useDesign } from '/@/hooks/web/useDesign';
+  import { useMessage } from '/@/hooks/web/useMessage';
 
   export default defineComponent({
     components: { Popover, BellOutlined, Tabs, TabPane: Tabs.TabPane, Badge, NoticeList },
     setup() {
       const { prefixCls } = useDesign('header-notify');
+      const { createMessage } = useMessage();
+      const listData = ref(tabListData);
 
-      let count = 0;
+      const count = computed(() => {
+        let count = 0;
+        for (let i = 0; i < tabListData.length; i++) {
+          count += tabListData[i].list.length;
+        }
+        return count;
+      });
 
-      for (let i = 0; i < tabListData.length; i++) {
-        count += tabListData[i].list.length;
+      function onNoticeClick(record: ListItem) {
+        createMessage.success('你点击了通知,ID=' + record.id);
+        // 可以直接将其标记为已读(为标题添加删除线),此处演示的代码会切换删除线状态
+        record.titleDelete = !record.titleDelete;
       }
 
       return {
         prefixCls,
-        tabListData,
+        listData,
         count,
+        onNoticeClick,
         numberStyle: {},
       };
     },