vben
2020-10-21 8b3a4d37a8addd151b918cf64bce6361376dec9e
feat: add table setting
1个文件已添加
14个文件已修改
456 ■■■■ 已修改文件
src/components/Form/src/BasicForm.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/src/BasicTable.vue 56 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/src/components/TableSetting.vue 269 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/src/components/renderTitle.tsx 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/src/hooks/useColumns.ts 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/src/hooks/useDataSource.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/src/hooks/useProvinceTable.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/src/hooks/useTable.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/src/props.ts 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/src/style/index.less 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/src/types/table.ts 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/hooks/web/useFullScreen.ts 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/default/LayoutBreadcrumb.tsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/menu.ts 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/demo/table/FormTable.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Form/src/BasicForm.vue
@@ -74,7 +74,7 @@
      const getMergePropsRef = computed(
        (): FormProps => {
          return deepMerge(toRaw(props), unref(propsRef));
          return deepMerge(props, unref(propsRef));
        }
      );
      // 获取表单基本配置
src/components/Table/src/BasicTable.vue
@@ -1,5 +1,6 @@
<template>
  <div
    ref="wrapRef"
    class="basic-table"
    :class="{
      'table-form-container': getBindValues.useSearchForm,
@@ -33,7 +34,7 @@
  </div>
</template>
<script lang="ts">
  import { defineComponent, ref, computed, unref, watch, nextTick } from 'vue';
  import { defineComponent, ref, computed, unref, watch, nextTick, toRaw } from 'vue';
  import { Table } from 'ant-design-vue';
  import { basicProps } from './props';
  import type {
@@ -41,7 +42,9 @@
    FetchParams,
    GetColumnsParams,
    TableActionType,
    SizeType,
  } from './types/table';
  import { isFunction, isString } from '/@/utils/is';
  import renderTitle from './components/renderTitle';
@@ -57,18 +60,20 @@
  import { provideTable } from './hooks/useProvinceTable';
  import { BasicForm, FormProps, useForm } from '/@/components/Form/index';
  import { omit } from 'lodash-es';
  import './style/index.less';
  import { ROW_KEY } from './const';
  import { PaginationProps } from './types/pagination';
  import { deepMerge } from '/@/utils';
  import { TableCustomRecord } from 'ant-design-vue/types/table/table';
  import { useEvent } from '/@/hooks/event/useEvent';
  import './style/index.less';
  export default defineComponent({
    props: basicProps,
    components: { Table, BasicForm },
    emits: ['fetch-success', 'fetch-error', 'selection-change', 'register'],
    setup(props, { attrs, emit, slots }) {
      const tableElRef = ref<any>(null);
      const wrapRef = ref<Nullable<HTMLDivElement>>(null);
      const innerPropsRef = ref<Partial<BasicTableProps>>();
      const [registerForm, { getFieldsValue }] = useForm();
@@ -93,6 +98,7 @@
        },
        emit
      );
      const { getScrollRef, redoHeight } = useTableScroll(getMergeProps, tableElRef);
      const {
        getRowSelectionRef,
@@ -108,16 +114,26 @@
        return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
      });
      const getBindValues = computed(() => {
        const { title, titleHelpMessage, showSummary } = unref(getMergeProps);
        const { title, titleHelpMessage, showSummary, showTableSetting, tableSetting } = unref(
          getMergeProps
        );
        const hideTitle = !slots.tableTitle && !title && !slots.toolbar && !showTableSetting;
        const titleData: any =
          !slots.tableTitle && !isString(title) && !title && !slots.toolbar
          hideTitle && !isString(title)
            ? {}
            : {
                title:
                  !slots.tableTitle && !title && !slots.toolbar
                    ? null
                    : renderTitle.bind(null, title, titleHelpMessage, slots),
                title: hideTitle
                  ? null
                  : renderTitle.bind(
                      null,
                      title,
                      titleHelpMessage,
                      slots,
                      showTableSetting,
                      tableSetting
                    ),
              };
        const pagination = unref(getPaginationRef);
        const rowSelection = unref(getRowSelectionRef);
@@ -155,6 +171,7 @@
        }
        return propsData;
      });
      const getFormProps = computed(() => {
        const { formConfig } = unref(getBindValues);
        const formProps: FormProps = {
@@ -198,6 +215,7 @@
        setPagination(pagination);
        fetch();
      }
      watch(
        () => unref(getDataSourceRef),
        () => {
@@ -230,6 +248,10 @@
        { immediate: true }
      );
      function setProps(props: Partial<BasicTableProps>) {
        innerPropsRef.value = deepMerge(unref(innerPropsRef) || {}, props);
      }
      const tableAction: TableActionType = {
        reload: async (opt?: FetchParams) => {
          await fetch(opt);
@@ -247,10 +269,13 @@
          return unref(getPaginationRef);
        },
        getColumns: (opt?: GetColumnsParams) => {
          const { ignoreIndex } = opt || {};
          let columns = unref(getColumnsRef);
          const { ignoreIndex, ignoreAction } = opt || {};
          let columns = toRaw(unref(getColumnsRef));
          if (ignoreIndex) {
            columns = columns.filter((item) => item.flag !== 'INDEX');
          }
          if (ignoreAction) {
            columns = columns.filter((item) => item.flag !== 'ACTION');
          }
          return columns;
        },
@@ -260,12 +285,16 @@
        setLoading: (loading: boolean) => {
          loadingRef.value = loading;
        },
        setProps: (props: Partial<BasicTableProps>) => {
          innerPropsRef.value = deepMerge(unref(innerPropsRef) || {}, props);
        setProps,
        getSize: (): SizeType => {
          return unref(getBindValues).size;
        },
      };
      provideTable(tableAction);
      provideTable({
        ...tableAction,
        wrapRef,
      });
      emit('register', tableAction);
      return {
@@ -278,6 +307,7 @@
        getEmptyDataIsShowTable,
        handleTableChange,
        getRowClassName,
        wrapRef,
        ...tableAction,
      };
    },
src/components/Table/src/components/TableSetting.vue
New file
@@ -0,0 +1,269 @@
<template>
  <div class="table-settings">
    <Divider type="vertical" />
    <Tooltip placement="top" v-if="getSetting.redo">
      <template #title>
        <span>刷新</span>
      </template>
      <RedoOutlined @click="redo" />
    </Tooltip>
    <Tooltip placement="top" v-if="getSetting.size">
      <template #title>
        <span>密度</span>
      </template>
      <Dropdown placement="bottomCenter" :trigger="['click']">
        <ColumnHeightOutlined />
        <template #overlay>
          <Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef">
            <MenuItem key="default">
              <span>默认</span>
            </MenuItem>
            <MenuItem key="middle">
              <span>中等</span>
            </MenuItem>
            <MenuItem key="small">
              <span>紧凑</span>
            </MenuItem>
          </Menu>
        </template>
      </Dropdown>
    </Tooltip>
    <Tooltip placement="top" v-if="getSetting.setting">
      <template #title>
        <span>列设置</span>
      </template>
      <Popover
        placement="bottomLeft"
        trigger="click"
        overlayClassName="table-settings__cloumn-list"
      >
        <template #content>
          <CheckboxGroup v-model:value="checkedList" @change="onChange">
            <template v-for="item in plainOptions" :key="item.value">
              <div class="table-settings__check-item">
                <Checkbox :value="item.value">
                  {{ item.label }}
                </Checkbox>
              </div>
            </template>
          </CheckboxGroup>
        </template>
        <template #title>
          <div class="table-settings__popover-title">
            <Checkbox
              :indeterminate="indeterminate"
              v-model:checked="checkAll"
              @change="onCheckAllChange"
            >
              列展示
            </Checkbox>
            <a-button size="small" type="link" @click="reset">重置</a-button>
          </div>
        </template>
        <SettingOutlined />
      </Popover>
    </Tooltip>
    <Tooltip placement="top" v-if="getSetting.fullScreen">
      <template #title>
        <span>全屏</span>
      </template>
      <FullscreenOutlined @click="handleFullScreen" v-if="!isFullscreenRef" />
      <FullscreenExitOutlined @click="handleFullScreen" v-else />
    </Tooltip>
  </div>
</template>
<script lang="ts">
  import { defineComponent, ref, reactive, toRefs, PropType, computed } from 'vue';
  import { injectTable } from '../hooks/useProvinceTable';
  import { Tooltip, Divider, Dropdown, Menu, Popover, Checkbox } from 'ant-design-vue';
  import {
    RedoOutlined,
    ColumnHeightOutlined,
    FullscreenOutlined,
    FullscreenExitOutlined,
    SettingOutlined,
  } from '@ant-design/icons-vue';
  import { useFullscreen } from '/@/hooks/web/useFullScreen';
  import type { SizeType, TableSetting } from '../types/table';
  interface Options {
    label: string;
    value: string;
  }
  interface State {
    indeterminate: boolean;
    checkAll: boolean;
    // defaultColumns: BasicColumn[];
    // columns: BasicColumn[];
    checkedList: string[];
    defaultCheckList: string[];
  }
  export default defineComponent({
    name: 'TableSetting',
    components: {
      RedoOutlined,
      ColumnHeightOutlined,
      FullscreenExitOutlined,
      FullscreenOutlined,
      SettingOutlined,
      Popover,
      Tooltip,
      Divider,
      Dropdown,
      Checkbox,
      CheckboxGroup: Checkbox.Group,
      Menu,
      MenuItem: Menu.Item,
    },
    props: {
      setting: {
        type: Object as PropType<TableSetting>,
        default: {},
      },
    },
    setup(props) {
      const table = injectTable();
      const { toggleFullscreen, isFullscreenRef } = useFullscreen(table.wrapRef);
      const selectedKeysRef = ref<SizeType[]>([table.getSize()]);
      let plainOptions: Options[] = [];
      const state = reactive<State>({
        indeterminate: false,
        checkAll: true,
        checkedList: [],
        defaultCheckList: [],
      });
      function init() {
        let ret: Options[] = [];
        table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => {
          ret.push({
            label: item.title as string,
            value: (item.dataIndex || item.title) as string,
          });
        });
        plainOptions = ret;
        const checkList = table
          .getColumns()
          .map((item) => item.dataIndex || item.title) as string[];
        state.checkedList = checkList;
        state.defaultCheckList = checkList;
      }
      function handleTitleClick({ key }: { key: SizeType }) {
        selectedKeysRef.value = [key];
        table.setProps({
          size: key,
        });
      }
      function handleFullScreen() {
        toggleFullscreen();
      }
      function onCheckAllChange(e: ChangeEvent) {
        state.indeterminate = false;
        const checkList = plainOptions.map((item) => item.value);
        if (e.target.checked) {
          state.checkedList = checkList;
          table.setColumns(checkList);
        } else {
          state.checkedList = [];
          table.setColumns([]);
        }
      }
      function onChange(checkedList: string[]) {
        state.indeterminate = !!checkedList.length && checkedList.length < plainOptions.length;
        state.checkAll = checkedList.length === plainOptions.length;
        table.setColumns(checkedList);
      }
      function reset() {
        if (state.checkAll) return;
        state.checkedList = [...state.defaultCheckList];
        state.checkAll = true;
        state.indeterminate = false;
        table.setColumns(state.defaultCheckList);
      }
      const getSetting = computed(
        (): TableSetting => {
          return {
            redo: true,
            size: true,
            setting: true,
            fullScreen: true,
            ...props.setting,
          };
        }
      );
      init();
      return {
        redo: () => table.reload(),
        handleTitleClick,
        selectedKeysRef,
        handleFullScreen,
        isFullscreenRef,
        onCheckAllChange,
        onChange,
        plainOptions,
        reset,
        getSetting,
        ...toRefs(state),
      };
    },
  });
</script>
<style lang="less">
  @import (reference) '../../../../design/index.less';
  .table-settings {
    & > * {
      margin-right: 12px;
    }
    svg {
      width: 1.2em;
      height: 1.2em;
    }
    &__popover-title {
      display: flex;
      align-items: center;
      justify-content: space-between;
    }
    &__check-item {
      width: 100%;
      padding: 4px 16px 4px 16px;
      .ant-checkbox-wrapper {
        width: 100%;
      }
      &:hover {
        background: fade(@primary-color, 10%);
      }
    }
    &__cloumn-list {
      .ant-popover-inner-content {
        max-height: 360px;
        padding-right: 0;
        padding-left: 0;
        overflow: auto;
      }
      .ant-checkbox-group {
        width: 100%;
      }
    }
  }
</style>
src/components/Table/src/components/renderTitle.tsx
@@ -1,14 +1,29 @@
import { Slots } from 'vue';
import TableTitle from './TableTitle.vue';
import { getSlot } from '/@/utils/helper/tsxHelper';
export default (title: any, titleHelpMessage: string | string[], slots: Slots) => {
import TableSettingComp from './TableSetting.vue';
import type { TableSetting } from '../types/table';
export default (
  title: any,
  titleHelpMessage: string | string[],
  slots: Slots,
  showTableSetting: boolean,
  tableSetting: TableSetting
) => {
  return (
    <>
      {getSlot(slots, 'tableTitle') ||
        (title && <TableTitle helpMessage={titleHelpMessage} title={title} />) || (
          <span>&nbsp;</span>
        )}
      {slots.toolbar && <div class="basic-table-toolbar">{getSlot(slots, 'toolbar')}</div>}
      {
        <div class="basic-table-toolbar">
          {slots.toolbar && getSlot(slots, 'toolbar')}
          {showTableSetting && <TableSettingComp setting={tableSetting} />}
        </div>
      }
    </>
  );
};
src/components/Table/src/hooks/useColumns.ts
@@ -81,28 +81,19 @@
    }
    if (actionColumn) {
      const hasIndex = columns.findIndex((column) => column.flag === 'ACTION');
      if (hasIndex === -1) {
        columns.push({
          fixed: 'right',
          ...actionColumn,
          flag: 'ACTION',
        });
      } else {
        columns[hasIndex] = {
          ...columns[hasIndex],
          fixed: 'right',
          ...actionColumn,
          flag: 'ACTION',
        };
      }
      columns.push({
        ...(hasIndex === -1 ? columns[hasIndex] : {}),
        fixed: 'right',
        ...actionColumn,
        flag: 'ACTION',
      });
    }
    return columns;
  });
  function setColumns(columns: BasicColumn[] | string[]) {
    if (!isArray(columns)) {
      return;
    }
    if (!isArray(columns)) return;
    if (columns.length <= 0) {
      columnsRef.value = [];
      return;
src/components/Table/src/hooks/useDataSource.ts
@@ -131,6 +131,7 @@
      });
    } finally {
      loadingRef.value = false;
      // setSearchFormLoading(false);
    }
  }
src/components/Table/src/hooks/useProvinceTable.ts
@@ -1,12 +1,15 @@
import type { Ref } from 'vue';
import { provide, inject } from 'vue';
import { TableActionType } from '../types/table';
const key = Symbol('table');
export function provideTable(instance: TableActionType) {
type Instance = TableActionType & { wrapRef: Ref<Nullable<HTMLElement>> };
export function provideTable(instance: Instance) {
  provide(key, instance);
}
export function injectTable(): TableActionType {
  return inject(key) as TableActionType;
export function injectTable(): Instance {
  return inject(key) as Instance;
}
src/components/Table/src/hooks/useTable.ts
@@ -82,6 +82,9 @@
    getPaginationRef: () => {
      return getTableInstance().getPaginationRef();
    },
    getSize: () => {
      return getTableInstance().getSize();
    },
  } as TableActionType;
  return [register, methods];
src/components/Table/src/props.ts
@@ -1,12 +1,19 @@
import { PropType } from 'vue';
import { PaginationProps } from './types/pagination';
import { BasicColumn, FetchSetting } from './types/table';
import { TableCustomRecord, TableRowSelection } from 'ant-design-vue/types/table/table';
import { FormProps } from '/@/components/Form/index';
import type { PropType } from 'vue';
import type { PaginationProps } from './types/pagination';
import type { BasicColumn, FetchSetting, TableSetting } from './types/table';
import type { TableCustomRecord, TableRowSelection } from 'ant-design-vue/types/table/table';
import type { FormProps } from '/@/components/Form/index';
import { FETCH_SETTING } from './const';
// 注释看 types/table
export const basicProps = {
  tableSetting: {
    type: Object as PropType<TableSetting>,
  },
  showTableSetting: {
    type: Boolean as PropType<boolean>,
    default: false,
  },
  autoCreateKey: {
    type: Boolean as PropType<boolean>,
    default: true,
src/components/Table/src/style/index.less
@@ -27,6 +27,9 @@
  }
  &-toolbar {
    display: flex;
    align-items: center;
    > * {
      margin-right: 10px;
    }
@@ -132,10 +135,10 @@
    border-right: 1px solid @border-color !important;
  }
  .ant-table-thead > tr > th,
  .ant-table-tbody > tr > td {
    padding: 9px 8px !important;
  }
  // .ant-table-thead > tr > th,
  // .ant-table-tbody > tr > td {
  //   padding: 9px 8px !important;
  // }
  .ant-pagination {
    margin: 10px 0 0 0;
src/components/Table/src/types/table.ts
@@ -32,7 +32,11 @@
export interface GetColumnsParams {
  ignoreIndex?: boolean;
  ignoreAction?: boolean;
}
export type SizeType = 'default' | 'middle' | 'small' | 'large';
export interface TableActionType {
  reload: (opt?: FetchParams) => Promise<void>;
  getSelectRows: () => any[];
@@ -41,7 +45,7 @@
  deleteSelectRowByKey: (key: string) => void;
  setPagination: (info: Partial<PaginationProps>) => void;
  setTableData: (values: any[]) => void;
  getColumns: ({ ignoreIndex }?: GetColumnsParams) => BasicColumn[];
  getColumns: (opt?: GetColumnsParams) => BasicColumn[];
  setColumns: (columns: BasicColumn[] | string[]) => void;
  getDataSource: () => any[];
  setLoading: (loading: boolean) => void;
@@ -49,6 +53,7 @@
  redoHeight: () => void;
  setSelectedRowKeys: (rowKeys: string[] | number[]) => void;
  getPaginationRef: () => PaginationProps | boolean;
  getSize: () => SizeType;
}
export interface FetchSetting {
@@ -61,7 +66,18 @@
  // 请求结果总数字段  支持 a.b.c
  totalField: string;
}
export interface TableSetting {
  redo?: boolean;
  size?: boolean;
  setting?: boolean;
  fullScreen?: boolean;
}
export interface BasicTableProps<T = any> {
  // 显示表格设置
  showTableSetting?: boolean;
  tableSetting?: TableSetting;
  // 斑马纹
  striped?: boolean;
  // 是否自动生成key
@@ -234,7 +250,7 @@
   * @default 'default'
   * @type string
   */
  size?: 'default' | 'middle' | 'small' | 'large';
  size?: SizeType;
  /**
   * Table title renderer
src/hooks/web/useFullScreen.ts
@@ -42,15 +42,17 @@
    RFC_METHOD_NAME = 'mozRequestFullScreen';
    EFS_METHOD_NAME = 'mozCancelFullScreen';
    FSE_PROP_NAME = 'mozFullScreenElement';
    ON_FSC_PROP_NAME = 'onmozfullscreenchange';
    // ON_FSC_PROP_NAME = 'onmozfullscreenchange';
  } else if (!('requestFullscreen' in DOC_EL)) {
    throw new Error('当前浏览器不支持Fullscreen API !');
  }
  function enterFullscreen(): Promise<void> {
    isFullscreenRef.value = true;
    return (target.value as any)[RFC_METHOD_NAME](options);
  }
  function exitFullscreen(): Promise<void> {
    isFullscreenRef.value = false;
    return (document as any)[EFS_METHOD_NAME]();
  }
@@ -89,6 +91,7 @@
  watchFullscreen((isFull: boolean) => {
    isFullscreenRef.value = isFull;
  });
  return {
    watchFullscreen,
    toggleFullscreen,
src/layouts/default/LayoutBreadcrumb.tsx
@@ -65,7 +65,7 @@
    return () => (
      <>
        <Breadcrumb class="layout-breadcrumb ">
        <Breadcrumb class="layout-breadcrumb">
          {() => (
            <>
              <TransitionGroup name="breadcrumb">
src/store/modules/menu.ts
@@ -8,16 +8,16 @@
hotModuleUnregisterModule(NAME);
@Module({ namespaced: true, name: NAME, dynamic: true, store })
class Menu extends VuexModule {
  // 默认展开
  private collapsedState: boolean = appStore.getProjectConfig.menuSetting.collapsed;
  // // 默认展开
  // private collapsedState: boolean = appStore.getProjectConfig.menuSetting.collapsed;
  // 菜单宽度
  private menuWidthState: number = appStore.getProjectConfig.menuSetting.menuWidth;
  // // 菜单宽度
  // private menuWidthState: number = appStore.getProjectConfig.menuSetting.menuWidth;
  // 是否开始拖拽
  private dragStartState: boolean = false;
  private dragStartState = false;
  private currentTopSplitMenuPathState: string = '';
  private currentTopSplitMenuPathState = '';
  /**
   * @description: 获取窗口名称
@@ -51,7 +51,7 @@
  // 改变菜单展开状态
  @Mutation
  commitCollapsedState(collapsed: boolean): void {
    this.collapsedState = collapsed;
    // this.collapsedState = collapsed;
    appStore.commitProjectConfigState({
      menuSetting: {
        collapsed: collapsed,
@@ -61,7 +61,7 @@
  @Mutation
  commitMenuWidthState(menuWidth: number): void {
    this.menuWidthState = menuWidth;
    // this.menuWidthState = menuWidth;
    appStore.commitProjectConfigState({
      menuSetting: {
        menuWidth: menuWidth,
src/views/demo/table/FormTable.vue
@@ -17,6 +17,7 @@
        columns: getBasicColumns(),
        useSearchForm: true,
        formConfig: getFormConfig(),
        showTableSetting: true,
      });
      return {