Browse Source

perf(table): the table fills the height according to the screen close #310

Vben 4 years ago
parent
commit
551fe50a44

+ 2 - 0
CHANGELOG.zh_CN.md

@@ -9,6 +9,8 @@
 
 - 表格关闭分页时不再携带分页参数
 - 登录页监听回车事件进行登录
+- 当表格设置自适应大小时,根据屏幕来铺满了高度.
+- Tree 滚动条优化
 
 ### 🐛 Bug Fixes
 

+ 1 - 1
package.json

@@ -109,7 +109,7 @@
     "vite-plugin-mock": "^2.2.0",
     "vite-plugin-purge-icons": "^0.7.0",
     "vite-plugin-pwa": "^0.5.6",
-    "vite-plugin-style-import": "^0.7.6",
+    "vite-plugin-style-import": "^0.8.1",
     "vite-plugin-svg-icons": "^0.2.1",
     "vite-plugin-theme": "^0.4.8",
     "vite-plugin-windicss": "0.6.10",

+ 17 - 11
src/components/Page/src/PageWrapper.vue

@@ -1,6 +1,11 @@
 <template>
   <div :class="getClass">
-    <PageHeader :ghost="ghost" v-bind="$attrs" ref="headerRef">
+    <PageHeader
+      :ghost="ghost"
+      v-bind="$attrs"
+      ref="headerRef"
+      v-if="content || $slots.headerContent"
+    >
       <template #default>
         <template v-if="content">
           {{ content }}
@@ -11,7 +16,11 @@
         <slot :name="item" v-bind="data"></slot>
       </template>
     </PageHeader>
-    <div :class="[`${prefixCls}-content`, $attrs.contentClass]" :style="getContentStyle">
+    <div
+      class="m-4 overflow-hidden"
+      :class="[`${prefixCls}-content`, contentClass]"
+      :style="getContentStyle"
+    >
       <slot></slot>
     </div>
     <PageFooter v-if="getShowFooter" ref="footerRef">
@@ -48,6 +57,8 @@
       },
       contentBackground: propTypes.bool,
       contentFullHeight: propTypes.bool,
+      contentClass: propTypes.string,
+      fixedHeight: propTypes.bool,
     },
     setup(props, { slots }) {
       const headerRef = ref<ComponentRef>(null);
@@ -73,15 +84,17 @@
 
       const getContentStyle = computed(
         (): CSSProperties => {
-          const { contentBackground, contentFullHeight, contentStyle } = props;
+          const { contentBackground, contentFullHeight, contentStyle, fixedHeight } = props;
           const bg = contentBackground ? { backgroundColor: '#fff' } : {};
           if (!contentFullHeight) {
             return { ...bg, ...contentStyle };
           }
+          const height = `${unref(pageHeight)}px`;
           return {
             ...bg,
             ...contentStyle,
-            minHeight: `${unref(pageHeight)}px`,
+            minHeight: height,
+            ...(fixedHeight ? { height } : {}),
             paddingBottom: `${unref(footerHeight)}px`,
           };
         }
@@ -137,18 +150,11 @@
     position: relative;
 
     .ant-page-header {
-      // padding: 12px 16px;
-
       &:empty {
         padding: 0;
       }
     }
 
-    &-content {
-      // padding: 12px;
-      margin: 16px;
-    }
-
     &--dense {
       .@{prefix-cls}-content {
         margin: 0;

+ 3 - 0
src/components/Table/src/BasicTable.vue

@@ -3,6 +3,7 @@
     ref="wrapRef"
     :class="[
       prefixCls,
+      $attrs.class,
       {
         [`${prefixCls}-form-container`]: getBindValues.useSearchForm,
         [`${prefixCls}--inset`]: getBindValues.inset,
@@ -211,6 +212,8 @@
           propsData = omit(propsData, 'scroll');
         }
 
+        propsData = omit(propsData, 'class');
+
         return propsData;
       });
 

+ 7 - 0
src/components/Table/src/hooks/useTableScroll.ts

@@ -55,6 +55,7 @@ export function useTableScroll(
   // No need to repeat queries
   let paginationEl: HTMLElement | null;
   let footerEl: HTMLElement | null;
+  let bodyEl: HTMLElement | null;
 
   async function calcTableHeight() {
     const { resizeHeightOffset, pagination, maxHeight } = unref(propsRef);
@@ -68,6 +69,7 @@ export function useTableScroll(
     if (!tableEl) return;
 
     const headEl = tableEl.querySelector('.ant-table-thead ');
+
     if (!headEl) return;
 
     // Table height from bottom
@@ -117,6 +119,11 @@ export function useTableScroll(
 
     height = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
     setHeight(height);
+
+    if (!bodyEl) {
+      bodyEl = tableEl.querySelector('.ant-table-body');
+    }
+    bodyEl!.style.height = `${height}px`;
   }
 
   useWindowSizeFn(calcTableHeight, 200);

+ 2 - 89
src/components/Table/src/style/index.less

@@ -4,12 +4,11 @@
 
 .@{prefix-cls} {
   &-form-container {
-    width: 100%;
     padding: 16px;
 
     .ant-form {
-      padding: 16px 16px 6px 12px;
-      margin-bottom: 18px;
+      padding: 12px 10px 6px 10px;
+      margin-bottom: 16px;
       background: #fff;
       border-radius: 4px;
     }
@@ -74,40 +73,6 @@
     }
   }
 
-  // .ant-table-bordered .ant-table-header > table,
-  // .ant-table-bordered .ant-table-body > table,
-  // .ant-table-bordered .ant-table-fixed-left table,
-  // .ant-table-bordered .ant-table-fixed-right table {
-  //   border: 1px solid @border-color !important;
-  // }
-
-  // .ant-table-thead {
-  //   tr {
-  //     border: none;
-  //   }
-
-  //   th {
-  //     border: none;
-  //   }
-  // }
-
-  // .ant-table-bordered .ant-table-tbody > tr > td {
-  // border-bottom: 1px solid @border-color !important;
-
-  // &:last-child {
-  //   border-right: none !important;
-  // }
-  // }
-
-  // .ant-table.ant-table-bordered .ant-table-footer,
-  // .ant-table.ant-table-bordered .ant-table-title {
-  //   border: 1px solid @border-color !important;
-  // }
-
-  // .ant-table-bordered.ant-table-empty .ant-table-placeholder {
-  //   border: 1px solid @border-color !important;
-  // }
-
   .ant-table-tbody > tr > td,
   .ant-table-tbody > tr > th,
   .ant-table-thead > tr > td,
@@ -115,62 +80,10 @@
     white-space: pre;
   }
 
-  // .ant-table-row-cell-last {
-  //   border-right: none !important;
-  // }
-
-  // .ant-table-bordered .ant-table-thead > tr > th,
-  // .ant-table-bordered .ant-table-tbody > tr > td {
-  //   border-right: 1px solid @border-color !important;
-  // }
-
   .ant-pagination {
     margin: 10px 0 0 0;
   }
 
-  // .ant-table-body {
-  //   overflow-x: auto !important;
-  //   overflow-y: scroll !important;
-  // }
-
-  // .ant-table-header {
-  //   margin-bottom: 0 !important;
-  //   overflow-x: hidden !important;
-  //   overflow-y: scroll !important;
-  // }
-
-  // .ant-table-fixed-right {
-  //   right: -1px;
-
-  //   .ant-table-header {
-  //     border-left: 1px solid @border-color !important;
-
-  //     .ant-table-fixed {
-  //       border-bottom: none;
-
-  //       .ant-table-thead th {
-  //         background: rgb(241, 243, 244);
-  //       }
-  //     }
-  //   }
-  // }
-
-  // .ant-table-fixed-left {
-  //   .ant-table-header {
-  //     overflow-y: hidden !important;
-  //   }
-
-  // .ant-table-fixed {
-  //   border-bottom: none;
-  // }
-  // }
-
-  // .ant-table-bordered .ant-table-thead > tr:not(:last-child) > th,
-  // .ant-table-tbody > tr > td {
-  //   word-break: break-word;
-  //   // border-color: @border-color !important;
-  // }
-
   .ant-table-footer {
     padding: 0;
 

+ 39 - 13
src/components/Tree/src/index.vue

@@ -1,10 +1,21 @@
 <script lang="tsx">
   import type { ReplaceFields, Keys, CheckKeys, TreeActionType, TreeItem } from './types';
 
-  import { defineComponent, reactive, computed, unref, ref, watchEffect, toRaw, watch } from 'vue';
-  import { Tree } from 'ant-design-vue';
+  import {
+    defineComponent,
+    reactive,
+    computed,
+    unref,
+    ref,
+    watchEffect,
+    toRaw,
+    watch,
+    CSSProperties,
+  } from 'vue';
+  import { Tree, Empty } from 'ant-design-vue';
   import { TreeIcon } from './TreeIcon';
   import TreeHeader from './TreeHeader.vue';
+  import { ScrollContainer } from '/@/components/Container';
   // import { DownOutlined } from '@ant-design/icons-vue';
 
   import { omit, get } from 'lodash-es';
@@ -95,6 +106,11 @@
             emit('update:value', rawVal);
           },
           onRightClick: handleRightClick,
+          // onSelect: (k, e) => {
+          //   setTimeout(() => {
+          //     emit('select', k, e);
+          //   }, 16);
+          // },
         };
         propsData = omit(propsData, 'treeData', 'class');
         return propsData;
@@ -104,6 +120,10 @@
         searchState.startSearch ? searchState.searchData : unref(treeDataRef)
       );
 
+      const getNotFound = computed((): boolean => {
+        return searchState.startSearch && searchState.searchData?.length === 0;
+      });
+
       const {
         deleteNodeByKey,
         insertNodeByKey,
@@ -178,10 +198,10 @@
           return;
         }
         searchState.startSearch = true;
+        const { title: titleField } = unref(getReplaceFields);
 
         searchState.searchData = filter(unref(treeDataRef), (node) => {
-          const { title } = node;
-          return title?.includes(searchValue) ?? false;
+          return node[titleField]?.includes(searchValue) ?? false;
         });
       }
 
@@ -284,7 +304,7 @@
                 title: () => (
                   <span
                     class={`${prefixCls}-title pl-2`}
-                    onClick={handleClickNode.bind(null, item.key, children)}
+                    onClick={handleClickNode.bind(null, item[keyField], item[childrenField])}
                   >
                     {icon && <TreeIcon icon={icon} />}
                     <span
@@ -304,9 +324,11 @@
       }
       return () => {
         const { title, helpMessage, toolbar, search } = props;
+        const showTitle = title || toolbar || search;
+        const scrollStyle: CSSProperties = { height: 'calc(100% - 38px)' };
         return (
           <div class={[prefixCls, 'h-full bg-white', attrs.class]}>
-            {(title || toolbar || search) && (
+            {showTitle && (
               <TreeHeader
                 checkAll={checkAll}
                 expandAll={expandAll}
@@ -318,13 +340,17 @@
                 onSearch={handleSearch}
               />
             )}
-            <Tree {...unref(getBindValues)} showIcon={false}>
-              {{
-                // switcherIcon: () => <DownOutlined />,
-                default: () => renderTreeNode({ data: unref(getTreeData), level: 1 }),
-                ...extendSlots(slots),
-              }}
-            </Tree>
+            <ScrollContainer style={scrollStyle} v-show={!unref(getNotFound)}>
+              <Tree {...unref(getBindValues)} showIcon={false}>
+                {{
+                  // switcherIcon: () => <DownOutlined />,
+                  default: () => renderTreeNode({ data: unref(getTreeData), level: 1 }),
+                  ...extendSlots(slots),
+                }}
+              </Tree>
+            </ScrollContainer>
+
+            <Empty v-show={unref(getNotFound)} class="!mt-4" />
           </div>
         );
       };

+ 43 - 0
src/views/demo/system/account/DeptTree.vue

@@ -0,0 +1,43 @@
+<template>
+  <div class="bg-white m-4 mr-0 overflow-hidden">
+    <BasicTree
+      title="部门列表"
+      toolbar
+      search
+      :clickRowToExpand="false"
+      :treeData="treeData"
+      :replaceFields="{ key: 'id', title: 'deptName' }"
+      @select="handleSelect"
+    />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, onMounted, ref } from 'vue';
+
+  import { BasicTree, TreeItem } from '/@/components/Tree';
+  import { getDeptList } from '/@/api/demo/system';
+
+  export default defineComponent({
+    name: 'DeptTree',
+    components: { BasicTree },
+
+    emits: ['select'],
+    setup(_, { emit }) {
+      const treeData = ref<TreeItem[]>([]);
+
+      async function fetch() {
+        treeData.value = ((await getDeptList()) as unknown) as TreeItem[];
+      }
+
+      function handleSelect(keys: string, e) {
+        emit('select', keys[0]);
+        console.log(keys, e);
+      }
+
+      onMounted(() => {
+        fetch();
+      });
+      return { treeData, handleSelect };
+    },
+  });
+</script>

+ 12 - 15
src/views/demo/system/account/index.vue

@@ -1,6 +1,7 @@
 <template>
-  <div :class="[prefixCls]">
-    <BasicTable @register="registerTable">
+  <PageWrapper dense contentFullHeight fixedHeight contentClass="flex">
+    <DeptTree class="w-1/4 xl:w-1/5" @select="handleSelect" />
+    <BasicTable @register="registerTable" class="w-3/4 xl:w-4/5">
       <template #toolbar>
         <a-button type="primary" @click="handleCreate"> 新增账号 </a-button>
       </template>
@@ -24,14 +25,15 @@
       </template>
     </BasicTable>
     <AccountModal @register="registerModal" @success="handleSuccess" />
-  </div>
+  </PageWrapper>
 </template>
 <script lang="ts">
   import { defineComponent } from 'vue';
 
-  import { useDesign } from '/@/hooks/web/useDesign';
   import { BasicTable, useTable, TableAction } from '/@/components/Table';
   import { getAccountList } from '/@/api/demo/system';
+  import { PageWrapper } from '/@/components/Page';
+  import DeptTree from './DeptTree.vue';
 
   import { useModal } from '/@/components/Modal';
   import AccountModal from './AccountModal.vue';
@@ -40,10 +42,8 @@
 
   export default defineComponent({
     name: 'AccountManagement',
-    components: { BasicTable, AccountModal, TableAction },
+    components: { BasicTable, PageWrapper, DeptTree, AccountModal, TableAction },
     setup() {
-      const { prefixCls } = useDesign('account-management');
-
       const [registerModal, { openModal }] = useModal();
       const [registerTable, { reload }] = useTable({
         title: '账号列表',
@@ -86,22 +86,19 @@
         reload();
       }
 
+      function handleSelect(deptId: string = '') {
+        reload({ searchInfo: { deptId } });
+      }
+
       return {
-        prefixCls,
         registerTable,
         registerModal,
         handleCreate,
         handleEdit,
         handleDelete,
         handleSuccess,
+        handleSelect,
       };
     },
   });
 </script>
-<style lang="less" scoped>
-  @prefix-cls: ~'@{namespace}-account-management';
-
-  .@{prefix-cls} {
-    display: flex;
-  }
-</style>

+ 9 - 9
yarn.lock

@@ -3662,10 +3662,10 @@ es-module-lexer@^0.3.26:
   resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.3.26.tgz#7b507044e97d5b03b01d4392c74ffeb9c177a83b"
   integrity sha512-Va0Q/xqtrss45hWzP8CZJwzGSZJjDM5/MJRE3IXXnUCcVLElR9BRaE9F62BopysASyc4nM3uwhSW7FFB9nlWAA==
 
-es-module-lexer@^0.4.0:
-  version "0.4.0"
-  resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.4.0.tgz#21f4181cc8b7eee06855f1c59e6087c7bc4f77b0"
-  integrity sha512-iuEGihqqhKWFgh72Q/Jtch7V2t/ft8w8IPP2aEN8ArYKO+IWyo6hsi96hCdgyeEDQIV3InhYQ9BlwUFPGXrbEQ==
+es-module-lexer@^0.4.1:
+  version "0.4.1"
+  resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.4.1.tgz#dda8c6a14d8f340a24e34331e0fab0cb50438e0e"
+  integrity sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA==
 
 es-to-primitive@^1.2.1:
   version "1.2.1"
@@ -9120,15 +9120,15 @@ vite-plugin-pwa@^0.5.6:
     pretty-bytes "^5.6.0"
     workbox-build "^6.1.1"
 
-vite-plugin-style-import@^0.7.6:
-  version "0.7.6"
-  resolved "https://registry.npmjs.org/vite-plugin-style-import/-/vite-plugin-style-import-0.7.6.tgz#909a5402f3a915fb2512e2a039e9cdb360fd2882"
-  integrity sha512-EDjscCzMsmi6mJ0UbMLMkCGLo7LCdFsRJZdjO7sfUIB+2wsC1FjDJcIEGWg0Lzl+4gghv9rk+AP+WCibI83WNw==
+vite-plugin-style-import@^0.8.1:
+  version "0.8.1"
+  resolved "https://registry.npmjs.org/vite-plugin-style-import/-/vite-plugin-style-import-0.8.1.tgz#e098c633cba3abef9b5a156aaf47f001567ebbb9"
+  integrity sha512-qZg73SA2+tbuEk7b0VjubjceUKVzHB6NwDYd3R9Hd6At4+sJ/85UIlTkzxSWHNgkTQh4sIOMQi1olXjkSF7tjg==
   dependencies:
     "@rollup/pluginutils" "^4.1.0"
     change-case "^4.1.2"
     debug "^4.3.2"
-    es-module-lexer "^0.4.0"
+    es-module-lexer "^0.4.1"
     magic-string "^0.25.7"
 
 vite-plugin-svg-icons@^0.2.1: