feat(table): add table component

This commit is contained in:
陈文彬
2020-10-08 01:35:05 +08:00
parent 5b0a21ecb0
commit faf3f4602e
71 changed files with 3948 additions and 202 deletions

View File

@@ -1,4 +1,3 @@
export { default as BasicArrow } from './src/BasicArrow.vue';
export { default as BasicHelp } from './src/BasicHelp';
export { default as BasicTitle } from './src/BasicTitle.vue';
export { default as BasicEmpty } from './src/BasicEmpty.vue';

View File

@@ -43,11 +43,16 @@
&.right {
transform: rotate(0deg);
> span {
transition: all 0.3s ease 0.1s !important;
}
}
&__active {
transform: rotate(90deg) !important;
transition: all 0.3s ease 0.1s !important;
> span {
transform: rotate(90deg) !important;
}
}
}
</style>

View File

@@ -1,28 +0,0 @@
<template>
<Empty :image="image" :description="description" />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { Empty } from 'ant-design-vue';
import emptySrc from '/@/assets/images/page_null.png';
export default defineComponent({
extends: Empty as any,
components: { Empty },
props: {
description: {
type: String,
default: '暂无内容',
},
image: {
type: String,
default: emptySrc,
required: false,
},
},
setup() {
return {};
},
});
</script>

View File

@@ -1,7 +1,7 @@
<template>
<Button v-bind="getBindValue" :class="[getColor, $attrs.class]">
<template v-slot:[item] v-for="item in Object.keys($slots)">
<slot :name="item" />
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data" />
</template>
</Button>
</template>

View File

@@ -6,7 +6,7 @@
<div class="collapse-container__body" v-else v-show="show">
<LazyContainer :timeout="lazyTime" v-if="lazy">
<slot />
<template v-slot:skeleton>
<template #skeleton>
<slot name="lazySkeleton" />
</template>
</LazyContainer>

View File

@@ -9,8 +9,8 @@
:allDefaultValues="getAllDefaultValues"
:formModel="formModel"
>
<template v-slot:[item] v-for="item in Object.keys($slots)">
<slot :name="item" />
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data" />
</template>
</FormItem>
</template>

View File

@@ -0,0 +1,13 @@
export { default as BasicTable } from './src/BasicTable.vue';
export { default as TableAction } from './src/components/TableAction';
export { default as TableImg } from './src/components/TableImg.vue';
export { renderEditableCell } from './src/components/renderEditableCell';
export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue';
export * from './src/types/table';
export * from './src/types/pagination';
export * from './src/types/tableAction';
export { useTable } from './src/hooks/useTable';
export type { FormSchema, FormProps } from '/@/components/Form/src/types/form';

View File

@@ -0,0 +1,285 @@
<template>
<div
class="basic-table"
:class="{
'table-form-container': getBindValues.useSearchForm,
}"
>
<BasicForm
v-bind="getFormProps"
v-if="getBindValues.useSearchForm"
:submitButtonOptions="{ loading }"
@register="registerForm"
@submit="handleSearchInfoChange"
@advanced-change="redoHeight"
>
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="`form-${item}`" v-bind="data" />
</template>
</BasicForm>
<Table
ref="tableElRef"
v-bind="getBindValues"
:rowClassName="getRowClassName"
:class="{
hidden: !getEmptyDataIsShowTable,
}"
@change="handleTableChange"
>
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data" />
</template>
</Table>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed, unref, watch, nextTick } from 'vue';
import { Table } from 'ant-design-vue';
import { basicProps } from './props';
import type {
BasicTableProps,
FetchParams,
GetColumnsParams,
TableActionType,
} from './types/table';
import { isFunction, isString } from '/@/utils/is';
import renderTitle from './components/renderTitle';
import renderFooter from './components/renderFooter';
import renderExpandIcon from './components/renderExpandIcon';
import { usePagination } from './hooks/usePagination';
import { useColumns } from './hooks/useColumns';
import { useDataSource } from './hooks/useDataSource';
import { useLoading } from './hooks/useLoading';
import { useRowSelection } from './hooks/useRowSelection';
import { useTableScroll } from './hooks/useTableScroll';
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';
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 innerPropsRef = ref<Partial<BasicTableProps>>();
const [registerForm, { getFieldsValue }] = useForm();
const getMergeProps = computed(
(): BasicTableProps => {
return {
...props,
...unref(innerPropsRef),
} as BasicTableProps;
}
);
const { loadingRef } = useLoading(getMergeProps);
const { getPaginationRef, setPagination } = usePagination(getMergeProps);
const { getColumnsRef, setColumns } = useColumns(getMergeProps, getPaginationRef);
const { getDataSourceRef, setTableData, fetch, getAutoCreateKey } = useDataSource(
getMergeProps,
{
getPaginationRef,
loadingRef,
setPagination,
getFieldsValue,
},
emit
);
const { getScrollRef, redoHeight } = useTableScroll(getMergeProps, tableElRef);
const {
getRowSelectionRef,
getSelectRows,
clearSelectedRowKeys,
getSelectRowKeys,
deleteSelectRowByKey,
setSelectedRowKeys,
} = useRowSelection(getMergeProps, emit);
const getRowKey = computed(() => {
const { rowKey } = unref(getMergeProps);
return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
});
const getBindValues = computed(() => {
const { title, titleHelpMessage, showSummary } = unref(getMergeProps);
const titleData: any =
!slots.tableTitle && !isString(title) && !title && !slots.toolbar
? {}
: {
title:
!slots.tableTitle && !title && !slots.toolbar
? null
: renderTitle.bind(null, title, titleHelpMessage, slots),
};
const pagination = unref(getPaginationRef);
const rowSelection = unref(getRowSelectionRef);
const scroll = unref(getScrollRef);
const loading = unref(loadingRef);
const rowKey = unref(getRowKey);
const columns = unref(getColumnsRef);
const dataSource = unref(getDataSourceRef);
let propsData = {
size: 'middle',
...(slots.expandedRowRender ? { expandIcon: renderExpandIcon() } : {}),
...attrs,
...unref(getMergeProps),
...titleData,
scroll,
loading,
tableLayout: 'fixed',
rowSelection,
rowKey,
columns,
pagination,
dataSource,
};
if (slots.expandedRowRender) {
propsData = omit(propsData, 'scroll');
}
if (showSummary) {
propsData.footer = renderFooter.bind(null, {
scroll,
columnsRef: getColumnsRef,
summaryFunc: unref(getMergeProps).summaryFunc,
dataSourceRef: getDataSourceRef,
rowSelectionRef: getRowSelectionRef,
});
}
return propsData;
});
const getFormProps = computed(() => {
const { formConfig } = unref(getBindValues);
const formProps: FormProps = {
showAdvancedButton: true,
...(formConfig as FormProps),
compact: true,
};
return formProps;
});
const getEmptyDataIsShowTable = computed(() => {
const { emptyDataIsShowTable, useSearchForm } = unref(getMergeProps);
if (emptyDataIsShowTable || !useSearchForm) {
return true;
}
return !!unref(getDataSourceRef).length;
});
function getRowClassName(record: TableCustomRecord<any>, index: number) {
const { striped, rowClassName } = unref(getMergeProps);
if (!striped) return;
if (rowClassName && isFunction(rowClassName)) {
return rowClassName(record);
}
return (index || 0) % 2 === 1 ? 'basic-table-row__striped' : '';
}
function handleSearchInfoChange(info: any) {
const { handleSearchInfoFn } = unref(getMergeProps);
if (handleSearchInfoFn && isFunction(handleSearchInfoFn)) {
info = handleSearchInfoFn(info) || info;
}
fetch({ searchInfo: info, page: 1 });
}
function handleTableChange(pagination: PaginationProps) {
const { clearSelectOnPageChange } = unref(getMergeProps);
if (clearSelectOnPageChange) {
clearSelectedRowKeys();
}
setPagination(pagination);
fetch();
}
watch(
() => unref(getDataSourceRef),
() => {
if (unref(getMergeProps).showSummary) {
nextTick(() => {
const tableEl = unref(tableElRef);
if (!tableEl) {
return;
}
const bodyDomList = tableEl.$el.querySelectorAll(
'.ant-table-body'
) as HTMLDivElement[];
const bodyDom = bodyDomList[0];
useEvent({
el: bodyDom,
name: 'scroll',
listener: () => {
const footerBodyDom = tableEl.$el.querySelector(
'.ant-table-footer .ant-table-body'
) as HTMLDivElement;
if (!footerBodyDom || !bodyDom) return;
footerBodyDom.scrollLeft = bodyDom.scrollLeft;
},
wait: 0,
options: true,
});
});
}
},
{ immediate: true }
);
const tableAction: TableActionType = {
reload: async (opt?: FetchParams) => {
await fetch(opt);
},
getSelectRows,
clearSelectedRowKeys,
getSelectRowKeys,
deleteSelectRowByKey,
setPagination,
setTableData,
redoHeight,
setSelectedRowKeys,
setColumns,
getPaginationRef: () => {
return unref(getPaginationRef);
},
getColumns: (opt?: GetColumnsParams) => {
const { ignoreIndex } = opt || {};
let columns = unref(getColumnsRef);
if (ignoreIndex) {
columns = columns.filter((item) => item.flag !== 'INDEX');
}
return columns;
},
getDataSource: () => {
return unref(getDataSourceRef);
},
setLoading: (loading: boolean) => {
loadingRef.value = loading;
},
setProps: (props: Partial<BasicTableProps>) => {
innerPropsRef.value = deepMerge(unref(innerPropsRef) || {}, props);
},
};
provideTable(tableAction);
emit('register', tableAction);
return {
tableElRef,
getBindValues,
loading: loadingRef,
registerForm,
handleSearchInfoChange,
getFormProps,
getEmptyDataIsShowTable,
handleTableChange,
getRowClassName,
...tableAction,
};
},
});
</script>

View File

@@ -0,0 +1,26 @@
import { Component } from 'vue';
import { Input, Select, Checkbox, InputNumber, Switch } from 'ant-design-vue';
import { ComponentType } from './types/componentType';
const componentMap = new Map<ComponentType, Component>();
componentMap.set('Input', Input);
componentMap.set('InputPassword', Input.Password);
componentMap.set('InputNumber', InputNumber);
componentMap.set('Select', Select);
componentMap.set('Switch', Switch);
componentMap.set('Checkbox', Checkbox);
componentMap.set('CheckboxGroup', Checkbox.Group);
export function add(compName: ComponentType, component: Component) {
componentMap.set(compName, component);
}
export function del(compName: ComponentType) {
componentMap.delete(compName);
}
export { componentMap };

View File

@@ -0,0 +1,72 @@
import { defineComponent, ref, computed, unref } from 'vue';
import { injectTable } from '../hooks/useProvinceTable';
import { getSlot } from '/@/utils/helper/tsxHelper';
import VueDraggableResizable from 'vue-draggable-resizable';
export default defineComponent({
name: 'DragResize',
setup(props, { slots, attrs }) {
const elRef = ref<HTMLTableRowElement | null>(null);
const draggingMapRef = ref<{ [key in string]: number | string }>({});
const tableInstance = injectTable();
const getColumnsRef = computed(() => {
const columns = tableInstance.getColumns();
columns.forEach((col) => {
const { key } = col;
if (key) {
draggingMapRef.value[key] = col.width as number;
}
});
return columns;
});
return () => {
const { key = '', ...restProps } = { ...attrs };
const col = unref(getColumnsRef).find((col) => {
const k = col.dataIndex || col.key;
return k === key;
});
if (!col || !col.width) {
return <th {...restProps}>{getSlot(slots, 'default')}</th>;
}
const onDrag = (x: number) => {
draggingMapRef.value[key] = 0;
col.width = Math.max(x, 1);
};
const onDragstop = () => {
const el = unref(elRef);
if (!el) {
return;
}
draggingMapRef.value[key] = el.getBoundingClientRect().width;
};
return (
<th
{...restProps}
class="resize-table-th"
ref={elRef}
style={{
width: `${col.width}px`,
}}
>
{getSlot(slots, 'default')}
<VueDraggableResizable
key={col.key}
class="table-draggable-handle"
w={10}
x={draggingMapRef.value[key] || col.width}
z={1}
axis="x"
draggable={true}
resizable={false}
onDragging={onDrag}
onDragstop={onDragstop}
/>
</th>
);
};
},
});

View File

@@ -0,0 +1,21 @@
<template>
<span>
{{ title }}
<FormOutlined class="ml-2" />
</span>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import { FormOutlined } from '@ant-design/icons-vue';
export default defineComponent({
name: 'EditTableHeaderIcon',
components: { FormOutlined },
props: {
title: {
type: String as PropType<string>,
default: '',
},
},
setup() {},
});
</script>

View File

@@ -0,0 +1,144 @@
import { defineComponent, PropType } from 'vue';
import { Dropdown, Menu, Popconfirm } from 'ant-design-vue';
import Icon from '/@/components/Icon/index';
import { DownOutlined } from '@ant-design/icons-vue';
import { ActionItem } from '../types/tableAction';
import Button from '/@/components/Button/index.vue';
const prefixCls = 'basic-table-action';
export default defineComponent({
name: 'TableAction',
props: {
actions: {
type: Array as PropType<ActionItem[]>,
default: null,
},
dropDownActions: {
type: Array as PropType<ActionItem[]>,
default: null,
},
},
setup(props) {
// 增加按钮的TYPE和COLOR
return () => {
const { dropDownActions = [], actions } = props;
return (
<div class={prefixCls}>
{actions &&
actions.length &&
actions.map((action, index) => {
const {
disabled = false,
label,
props,
icon,
color = '',
type = 'link',
popConfirm = null,
} = action;
const button = (
<Button
type={type}
size="small"
disabled={disabled}
color={color}
{...props}
key={index}
>
{() => (
<>
{label}
{icon && <Icon icon={icon} />}
</>
)}
</Button>
);
if (popConfirm !== null) {
const {
title,
okText = '确定',
cancelText = '取消',
confirm = () => {},
cancel = () => {},
icon = '',
} = popConfirm;
return (
<Popconfirm
key={`P-${index}`}
title={title}
onConfirm={confirm}
onCancel={cancel}
okText={okText}
cancelText={cancelText}
icon={icon}
>
{() => button}
</Popconfirm>
);
}
return button;
})}
{dropDownActions && dropDownActions.length && (
<Dropdown>
{{
default: () => (
<Button type="link" size="small">
{{
default: () => (
<>
<DownOutlined />
</>
),
}}
</Button>
),
overlay: () => {
return (
<Menu>
{{
default: () => {
return dropDownActions.map((action, index) => {
const {
disabled = false,
label,
props,
icon,
color = '',
type = 'link',
} = action;
return (
<Menu.Item key={`${index}`} disabled={disabled}>
{() => (
<Button
type={type}
size="small"
{...props}
disabled={disabled}
color={color}
>
{{
default: () => (
<>
{label}
{icon && <Icon icon={icon} />}
</>
),
}}
</Button>
)}
</Menu.Item>
);
});
},
}}
</Menu>
);
},
}}
</Dropdown>
)}
</div>
);
};
},
});

View File

@@ -0,0 +1,36 @@
<template>
<div class="basic-table-img__preview" v-if="imgList && imgList.length">
<template v-for="(img, index) in imgList" :key="img">
<img :width="size" @click="handlePreview(index)" :src="img" />
</template>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import { createImgPreview } from '/@/components/Preview/index';
export default defineComponent({
name: 'TableAction',
props: {
imgList: {
type: Array as PropType<string[]>,
default: null,
},
size: {
type: Number as PropType<number>,
default: 40,
},
},
setup(props) {
function handlePreview(index: number) {
const { imgList } = props;
createImgPreview({
imageList: imgList as string[],
index: index,
});
}
return { handlePreview };
},
});
</script>

View File

@@ -0,0 +1,41 @@
<template>
<BasicTitle class="basic-table-title" v-if="tableTitle" :helpMessage="helpMessage">
{{ tableTitle }}
</BasicTitle>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import { BasicTitle } from '/@/components/Basic/index';
import { isFunction } from '/@/utils/is';
export default defineComponent({
name: 'TableTitle',
components: { BasicTitle },
props: {
title: {
type: [Function, String] as PropType<string | ((data: any) => string)>,
},
getSelectRows: {
type: Function as PropType<() => any[]>,
},
helpMessage: {
type: [String, Array] as PropType<string | string[]>,
},
},
setup(props) {
const tableTitle = computed(() => {
const { title, getSelectRows = () => {} } = props;
let tit = title;
if (isFunction(title)) {
tit = title({
selectRows: getSelectRows(),
});
}
return tit;
});
return { tableTitle };
},
});
</script>

View File

@@ -0,0 +1,153 @@
import { defineComponent, PropType, ref, unref, nextTick } from 'vue';
import { injectTable } from '../hooks/useProvinceTable';
import ClickOutSide from '/@/components/ClickOutSide/index.vue';
import { RenderEditableCellParams } from '../types/table';
import { ComponentType } from '../types/componentType';
import { componentMap } from '../componentMap';
import '../style/editable-cell.less';
import { isString, isBoolean } from '/@/utils/is';
import { FormOutlined, CloseOutlined, CheckOutlined } from '@ant-design/icons-vue';
const prefixCls = 'editable-cell';
const EditableCell = defineComponent({
name: 'EditableCell',
props: {
value: {
type: String as PropType<string>,
default: '',
},
componentProps: {
type: Object as PropType<any>,
default: null,
},
dataKey: {
type: String as PropType<string>,
default: '',
},
dataIndex: {
type: String as PropType<string>,
default: '',
},
component: {
type: String as PropType<ComponentType>,
default: 'Input',
},
},
setup(props, { attrs }) {
const table = injectTable();
const elRef = ref<any>(null);
const isEditRef = ref(false);
const currentValueRef = ref<string | boolean>('');
function handleChange(e: ChangeEvent | string | boolean) {
if ((e as ChangeEvent).target && Reflect.has((e as ChangeEvent).target, 'value')) {
currentValueRef.value = (e as ChangeEvent).target.value;
}
if (isString(e) || isBoolean(e)) {
currentValueRef.value = e;
}
}
function handleEdit() {
isEditRef.value = true;
nextTick(() => {
const el = unref(elRef);
el && el.focus && el.focus();
});
}
function handleCancel() {
isEditRef.value = false;
}
function handleSubmit() {
const { dataKey, dataIndex } = props;
if (!dataKey || !dataIndex) {
return;
}
isEditRef.value = false;
const { getDataSource } = table;
const dataSource = getDataSource();
const target = dataSource.find((item) => item.key === dataKey);
if (target) {
target[dataIndex] = unref(currentValueRef);
}
}
function onClickOutside() {
const { component } = props;
if (component?.includes('Input')) {
handleCancel();
}
}
return () => {
const { value, component, componentProps = {} } = props;
const Comp = componentMap.get(component!) as any;
// const propsData: any = {};
return (
<div class={prefixCls}>
{unref(isEditRef) && (
<ClickOutSide onClickOutside={onClickOutside}>
{() => (
<div class={`${prefixCls}__wrapper`}>
<Comp
{...{
...attrs,
...componentProps,
}}
style={{ width: 'calc(100% - 48px)' }}
ref={elRef}
value={value}
size="small"
onChange={handleChange}
onPressEnter={handleSubmit}
/>
<div class={`${prefixCls}__action`}>
<CheckOutlined class={[`${prefixCls}__icon`, 'mx-2']} onClick={handleSubmit} />
<CloseOutlined class={[`${prefixCls}__icon `]} onClick={handleCancel} />
</div>
</div>
)}
</ClickOutSide>
)}
{!unref(isEditRef) && (
<div class={`${prefixCls}__normal`} onClick={handleEdit}>
{value}
<FormOutlined class={`${prefixCls}__normal-icon`} />
</div>
)}
</div>
);
};
},
});
export function renderEditableCell({
dataIndex,
component,
componentOn = {},
componentProps = {},
}: RenderEditableCellParams) {
return ({ text, record }: { text: string; record: any }) => {
return (
<EditableCell
value={text}
dataKey={record.key}
dataIndex={dataIndex}
component={component}
on={componentOn}
componentProps={componentProps}
/>
);
};
}

View File

@@ -0,0 +1,15 @@
import { BasicArrow } from '/@/components/Basic';
export default () => {
return (props: any) => {
return (
<BasicArrow
onClick={(e: Event) => {
props.onExpand(props.record, e);
}}
expand={props.expanded}
class="right"
/>
);
};
};

View File

@@ -0,0 +1,64 @@
import { Table } from 'ant-design-vue';
import { TableRowSelection } from 'ant-design-vue/types/table/table';
import { cloneDeep } from 'lodash-es';
import { unref, ComputedRef } from 'vue';
import { BasicColumn } from '../types/table';
import { isFunction } from '/@/utils/is';
export default ({
scroll = {},
columnsRef,
summaryFunc,
rowKey = 'key',
dataSourceRef,
rowSelectionRef,
}: {
scroll: { x?: number | true; y?: number };
columnsRef: ComputedRef<BasicColumn[]>;
summaryFunc: any;
rowKey?: string;
dataSourceRef: ComputedRef<any[]>;
rowSelectionRef: ComputedRef<TableRowSelection<any> | null>;
}) => {
if (!summaryFunc) {
return;
}
const dataSource: any[] = isFunction(summaryFunc) ? summaryFunc(unref(dataSourceRef)) : [];
const columns: BasicColumn[] = cloneDeep(unref(columnsRef));
const index = columns.findIndex((item) => item.flag === 'INDEX');
const hasRowSummary = dataSource.some((item) => Reflect.has(item, '_row'));
const hasIndexSummary = dataSource.some((item) => Reflect.has(item, '_index'));
if (index !== -1) {
if (hasIndexSummary) {
columns[index].customRender = ({ record }) => record._index;
columns[index].ellipsis = false;
} else {
Reflect.deleteProperty(columns[index], 'customRender');
}
}
if (unref(rowSelectionRef) && hasRowSummary) {
columns.unshift({
width: 60,
title: 'selection',
key: 'selectionKey',
align: 'center',
customRender: ({ record }) => record._row,
});
}
dataSource.forEach((item, i) => {
item[rowKey] = i;
});
return (
<Table
showHeader={false}
bordered={false}
pagination={false}
dataSource={dataSource}
rowKey={rowKey}
columns={columns}
tableLayout="fixed"
scroll={scroll as any}
/>
);
};

View File

@@ -0,0 +1,14 @@
import { Slots } from 'vue';
import TableTitle from './TableTitle.vue';
import { getSlot } from '/@/utils/helper/tsxHelper';
export default (title: any, titleHelpMessage: string | string[], slots: Slots) => {
return (
<>
{getSlot(slots, 'tableTitle') ||
(title && <TableTitle helpMessage={titleHelpMessage} title={title} />) || (
<span>&nbsp;</span>
)}
{slots.toolbar && <div class="basic-table-toolbar">{getSlot(slots, 'toolbar')}</div>}
</>
);
};

View File

@@ -0,0 +1,12 @@
export const ROW_KEY = 'key';
export const PAGE_SIZE_OPTIONS = ['10', '50', '80', '100'];
export const PAGE_SIZE = ~~PAGE_SIZE_OPTIONS[0];
export const FETCH_SETTING = {
pageField: 'page',
sizeField: 'pageSize',
listField: 'items',
totalField: 'total',
};

View File

@@ -0,0 +1,123 @@
import { BasicColumn, BasicTableProps } from '../types/table';
import { PaginationProps } from '../types/pagination';
import { unref, ComputedRef, Ref, computed, watch, ref } from 'vue';
import { isBoolean, isArray, isObject } from '/@/utils/is';
import { PAGE_SIZE } from '../const';
import { useProps } from './useProps';
export function useColumns(
refProps: ComputedRef<BasicTableProps>,
getPaginationRef: ComputedRef<false | PaginationProps>
) {
const { propsRef } = useProps(refProps);
const columnsRef = (ref(unref(propsRef).columns) as unknown) as Ref<BasicColumn[]>;
const cacheColumnsRef = (ref(unref(propsRef).columns) as unknown) as Ref<BasicColumn[]>;
watch(
() => unref(propsRef).columns,
(columns) => {
columnsRef.value = columns;
cacheColumnsRef.value = columns;
},
{
immediate: true,
}
);
const getColumnsRef = computed(() => {
const props = unref(propsRef);
const { showIndexColumn, indexColumnProps, ellipsis, actionColumn, isTreeTable } = props;
const columns = unref(columnsRef);
if (!columns) {
return [];
}
let pushIndexColumns = false;
columns.forEach((item) => {
const { key, dataIndex } = item;
item.align = item.align || 'center';
if (ellipsis) {
if (!key) {
item.key = dataIndex;
}
if (!isBoolean(item.ellipsis)) {
Object.assign(item, {
ellipsis,
});
}
}
const indIndex = columns.findIndex((column) => column.flag === 'INDEX');
if (showIndexColumn && !isTreeTable) {
pushIndexColumns = indIndex === -1;
} else if (!showIndexColumn && !isTreeTable && indIndex !== -1) {
columns.splice(indIndex, 1);
}
});
if (pushIndexColumns) {
const isFixedLeft = columns.some((item) => item.fixed === 'left');
columns.unshift({
flag: 'INDEX',
width: 50,
title: '序号',
align: 'center',
customRender: ({ index }) => {
const getPagination = unref(getPaginationRef);
if (isBoolean(getPagination)) {
return `${index + 1}`;
}
const { current = 1, pageSize = PAGE_SIZE } = getPagination;
const currentIndex = (current - 1) * pageSize + index + 1;
return currentIndex;
},
...(isFixedLeft
? {
fixed: 'left',
}
: {}),
...indexColumnProps,
});
}
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',
};
}
}
return columns;
});
function setColumns(columns: BasicColumn[] | string[]) {
if (!isArray(columns)) {
return;
}
if (columns.length <= 0) {
columnsRef.value = [];
return;
}
const firstColumn = columns[0];
if (isObject(firstColumn)) {
columnsRef.value = columns as any;
} else {
const newColumns = unref(cacheColumnsRef).filter((item) =>
(columns as string[]).includes(item.key! || item.dataIndex!)
);
columnsRef.value = newColumns;
}
}
return { getColumnsRef, setColumns };
}

View File

@@ -0,0 +1,148 @@
import { useTimeout } from '/@/hooks/core/useTimeout';
import { BasicTableProps, FetchParams } from '../types/table';
import { PaginationProps } from '../types/pagination';
import { watch, ref, unref, ComputedRef, computed, onMounted, Ref } from 'vue';
import { buildUUID } from '/@/utils/uuid';
import { isFunction, isBoolean } from '/@/utils/is';
import { FETCH_SETTING, ROW_KEY } from '../const';
import { get } from 'lodash-es';
import { useProps } from './useProps';
interface ActionType {
getPaginationRef: ComputedRef<false | PaginationProps>;
setPagination: (info: Partial<PaginationProps>) => void;
loadingRef: Ref<boolean | undefined>;
getFieldsValue: () => {
[field: string]: any;
};
}
export function useDataSource(
refProps: ComputedRef<BasicTableProps>,
{ getPaginationRef, setPagination, loadingRef, getFieldsValue }: ActionType,
emit: EmitType
) {
const { propsRef } = useProps(refProps);
const dataSourceRef = ref<any[]>([]);
watch(
() => unref(propsRef).dataSource,
(data: any[]) => {
const { api } = unref(propsRef);
!api && (dataSourceRef.value = data);
},
{ immediate: true }
);
function setTableKey(items: any[]) {
if (!items || !Array.isArray(items)) {
return;
}
items.forEach((item) => {
if (!item[ROW_KEY]) {
item[ROW_KEY] = buildUUID();
}
if (item.children && item.children.length) {
setTableKey(item.children);
}
});
}
const getAutoCreateKey = computed(() => {
return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
});
const getDataSourceRef = computed(() => {
const dataSource = unref(dataSourceRef);
if (!dataSource || dataSource.length === 0) {
return [];
}
if (unref(getAutoCreateKey)) {
const firstItem = dataSource[0];
const lastItem = dataSource[dataSource.length - 1];
if (firstItem && lastItem) {
if (!firstItem[ROW_KEY] || !lastItem[ROW_KEY]) {
unref(dataSourceRef).forEach((item) => {
if (!item[ROW_KEY]) {
item[ROW_KEY] = buildUUID();
}
if (item.children && item.children.length) {
setTableKey(item.children);
}
});
}
}
}
return unref(dataSourceRef);
});
async function fetch(opt?: FetchParams) {
const { api, searchInfo, fetchSetting, beforeFetch, afterFetch, useSearchForm } = unref(
propsRef
);
if (!api && !isFunction(api)) return;
try {
loadingRef.value = true;
const { pageField, sizeField, listField, totalField } = fetchSetting || FETCH_SETTING;
let pageParams: any = {};
if (isBoolean(getPaginationRef)) {
pageParams = {};
} else {
const { current, pageSize } = unref(getPaginationRef) as PaginationProps;
pageParams[pageField] = opt?.page || current;
pageParams[sizeField] = pageSize;
}
let params: any = {
...pageParams,
...(useSearchForm ? getFieldsValue() : {}),
...searchInfo,
...(opt ? opt.searchInfo : {}),
};
if (beforeFetch && isFunction(beforeFetch)) {
params = beforeFetch(params) || params;
}
const res = await api(params);
let resultItems: any[] = get(res, listField);
const resultTotal: number = get(res, totalField);
if (afterFetch && isFunction(afterFetch)) {
resultItems = afterFetch(resultItems) || resultItems;
}
dataSourceRef.value = resultItems;
setPagination({
total: resultTotal || 0,
});
if (opt && opt.page) {
setPagination({
current: opt.page || 1,
});
}
emit('fetch-success', {
items: unref(resultItems),
total: resultTotal,
});
} catch (error) {
emit('fetch-error', error);
dataSourceRef.value = [];
setPagination({
total: 0,
});
} finally {
loadingRef.value = false;
}
}
function setTableData(values: any[]) {
dataSourceRef.value = values;
}
onMounted(() => {
// 转异步任务
useTimeout(() => {
unref(propsRef).immediate && fetch();
}, 0);
});
return { getDataSourceRef, setTableData, getAutoCreateKey, fetch: fetch };
}

View File

@@ -0,0 +1,15 @@
import { watch, ref, ComputedRef, unref } from 'vue';
import { BasicTableProps } from '../types/table';
import { useProps } from './useProps';
export function useLoading(refProps: ComputedRef<BasicTableProps>) {
const { propsRef } = useProps(refProps);
const loadingRef = ref(unref(propsRef).loading);
watch(
() => unref(propsRef).loading,
(v: boolean) => {
loadingRef.value = v;
}
);
return { loadingRef };
}

View File

@@ -0,0 +1,53 @@
import { computed, unref, ref, ComputedRef } from 'vue';
import { PaginationProps } from '../types/pagination';
import { isBoolean } from '/@/utils/is';
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
import { PAGE_SIZE, PAGE_SIZE_OPTIONS } from '../const';
import { useProps } from './useProps';
import { BasicTableProps } from '../..';
export function usePagination(refProps: ComputedRef<BasicTableProps>) {
const configRef = ref<PaginationProps>({});
const { propsRef } = useProps(refProps);
const getPaginationRef = computed((): PaginationProps | false => {
const { pagination } = unref(propsRef);
if (isBoolean(pagination) && !pagination) {
return false;
}
return {
current: 1,
pageSize: PAGE_SIZE,
size: 'small',
defaultPageSize: PAGE_SIZE,
showTotal: (total) => `${total} 条数据`,
showSizeChanger: true,
pageSizeOptions: PAGE_SIZE_OPTIONS,
itemRender: ({ page, type, originalElement }) => {
if (type === 'prev') {
if (page === 0) {
return null;
}
return <LeftOutlined />;
} else if (type === 'next') {
if (page === 1) {
return null;
}
return <RightOutlined />;
}
return originalElement;
},
showQuickJumper: true,
...(isBoolean(pagination) ? {} : pagination),
...unref(configRef),
};
});
function setPagination(info: Partial<PaginationProps>) {
configRef.value = {
...unref(getPaginationRef),
...info,
};
}
return { getPaginationRef, setPagination };
}

View File

@@ -0,0 +1,29 @@
/*
* @description:
* @author: wenbin.chen
* @Date: 2020-05-12 13:20:26
* @LastEditors: vben
* @LastEditTime: 2020-10-07 14:52:34
* @email: 190848757@qq.com
*/
import { Ref, ref, watch, unref } from 'vue';
import { BasicTableProps } from '../types/table';
/**
* @description:
* @Date: 2020-05-12 13:20:37
*/
export function useProps(props: Readonly<Ref<BasicTableProps>>) {
const propsRef = (ref<BasicTableProps>(unref(props)) as unknown) as Ref<BasicTableProps>;
watch(
() => props.value,
(v) => {
propsRef.value = unref(v);
},
{
immediate: false,
}
);
return { propsRef };
}

View File

@@ -0,0 +1,12 @@
import { provide, inject } from 'vue';
import { TableActionType } from '../types/table';
const key = Symbol('table');
export function provideTable(instance: TableActionType) {
provide(key, instance);
}
export function injectTable(): TableActionType {
return inject(key) as TableActionType;
}

View File

@@ -0,0 +1,63 @@
import { computed, ref, unref, ComputedRef } from 'vue';
import { BasicTableProps } from '../types/table';
import { TableRowSelection } from 'ant-design-vue/types/table/table';
import { useProps } from './useProps';
/* eslint-disable */
export function useRowSelection(refProps: ComputedRef<BasicTableProps>, emit: EmitType) {
const { propsRef } = useProps(refProps);
const selectedRowKeysRef = ref<string[]>([]);
const selectedRowRef = ref<any[]>([]);
const getRowSelectionRef = computed((): TableRowSelection<any> | null => {
const rowSelection = unref(propsRef).rowSelection;
if (!rowSelection) {
return null;
}
return {
selectedRowKeys: unref(selectedRowKeysRef),
hideDefaultSelections: false,
onChange: (selectedRowKeys: string[], selectedRows: any[]) => {
selectedRowKeysRef.value = selectedRowKeys;
selectedRowRef.value = selectedRows;
emit('selection-change', {
keys: selectedRowKeys,
rows: selectedRows,
});
},
...rowSelection,
};
});
function setSelectedRowKeys(rowKeys: string[]) {
selectedRowKeysRef.value = rowKeys;
}
function clearSelectedRowKeys() {
selectedRowRef.value = [];
selectedRowKeysRef.value = [];
}
function deleteSelectRowByKey(key: string) {
const selectedRowKeys = unref(selectedRowKeysRef);
const index = selectedRowKeys.findIndex((item) => item === key);
if (index !== -1) {
unref(selectedRowKeysRef).splice(index, 1);
}
}
function getSelectRowKeys() {
return unref(selectedRowKeysRef);
}
function getSelectRows() {
return unref(selectedRowRef);
}
return {
getRowSelectionRef,
getSelectRows,
getSelectRowKeys,
setSelectedRowKeys,
clearSelectedRowKeys,
deleteSelectRowByKey,
};
}

View File

@@ -0,0 +1,88 @@
import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table';
import type { PaginationProps } from '../types/pagination';
import { ref, getCurrentInstance, onUnmounted, unref } from 'vue';
import { isProdMode } from '/@/utils/env';
export function useTable(
tableProps?: Partial<BasicTableProps>
): [(instance: TableActionType) => void, TableActionType] {
if (!getCurrentInstance()) {
throw new Error('Please put useTable function in the setup function!');
}
const tableRef = ref<TableActionType | null>(null);
const loadedRef = ref<boolean | null>(false);
function register(instance: TableActionType) {
onUnmounted(() => {
tableRef.value = null;
loadedRef.value = null;
});
if (unref(loadedRef) && isProdMode() && instance === unref(tableRef)) {
return;
}
tableRef.value = instance;
tableProps && instance.setProps(tableProps);
loadedRef.value = true;
}
function getTableInstance(): TableActionType {
const table = unref(tableRef);
if (!table) {
throw new Error('table is undefined!');
}
return table;
}
const methods: TableActionType = {
reload: (opt?: FetchParams) => {
getTableInstance().reload(opt);
},
setProps: (props: Partial<BasicTableProps>) => {
getTableInstance().setProps(props);
},
redoHeight: () => {
getTableInstance().redoHeight();
},
setLoading: (loading: boolean) => {
getTableInstance().setLoading(loading);
},
getDataSource: () => {
return getTableInstance().getDataSource();
},
getColumns: ({ ignoreIndex = false }: { ignoreIndex?: boolean } = {}) => {
const columns = getTableInstance().getColumns({ ignoreIndex }) || [];
return columns;
},
setColumns: (columns: BasicColumn[]) => {
getTableInstance().setColumns(columns);
},
setTableData: (values: any[]) => {
return getTableInstance().setTableData(values);
},
setPagination: (info: Partial<PaginationProps>) => {
return getTableInstance().setPagination(info);
},
deleteSelectRowByKey: (key: string) => {
getTableInstance().deleteSelectRowByKey(key);
},
getSelectRowKeys: () => {
return getTableInstance().getSelectRowKeys();
},
getSelectRows: () => {
return getTableInstance().getSelectRows();
},
clearSelectedRowKeys: () => {
getTableInstance().clearSelectedRowKeys();
},
setSelectedRowKeys: (keys: string[] | number[]) => {
getTableInstance().setSelectedRowKeys(keys);
},
getPaginationRef: () => {
return getTableInstance().getPaginationRef();
},
} as TableActionType;
return [register, methods];
}

View File

@@ -0,0 +1,134 @@
import { BasicTableProps } from '../types/table';
import { computed, Ref, onMounted, unref, ref, nextTick, ComputedRef, watch } from 'vue';
import { getViewportOffset } from '/@/utils/domUtils';
import { triggerWindowResize } from '/@/utils/event/triggerWindowResizeEvent';
import { isBoolean } from '/@/utils/is';
import { useTimeout } from '/@/hooks/core/useTimeout';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSize';
import { useProps } from './useProps';
export function useTableScroll(refProps: ComputedRef<BasicTableProps>, tableElRef: Ref<any>) {
const { propsRef } = useProps(refProps);
const tableHeightRef: Ref<number | null> = ref(null);
watch(
() => unref(propsRef).canResize,
() => {
redoHeight();
}
);
function redoHeight() {
const { canResize } = unref(propsRef);
if (!canResize) {
return;
}
calcTableHeight();
}
async function calcTableHeight(cb?: () => void) {
const { canResize, resizeHeightOffset, pagination, maxHeight } = unref(propsRef);
if (!canResize) {
return;
}
await nextTick();
const table = unref(tableElRef) as any;
if (!table) {
return;
}
const tableEl: Element = table.$el;
if (!tableEl) {
return;
}
const el: HTMLElement | null = tableEl.querySelector('.ant-table-thead ');
// const layoutMain: Element | null = document.querySelector('.default-layout__main ');
if (!el) {
return;
}
// 表格距离底部高度
const { bottomIncludeBody } = getViewportOffset(el);
// 表格高度+距离底部高度-自定义偏移量
const paddingHeight = 32;
const borderHeight = 2 * 2;
// 分页器高度
// TODO 先固定20
const paginationHeight = 20;
// if (!isBoolean(pagination)) {
// const paginationDom = tableEl.querySelector('.ant-pagination') as HTMLElement;
// if (paginationDom) {
// const offsetHeight = paginationDom.offsetHeight;
// paginationHeight += offsetHeight || 0;
// }
// }
let footerHeight = 0;
if (!isBoolean(pagination)) {
const footerEl = tableEl.querySelector('.ant-table-footer') as HTMLElement;
if (footerEl) {
const offsetHeight = footerEl.offsetHeight;
footerHeight += offsetHeight || 0;
}
}
let headerHeight = 0;
if (el) {
headerHeight = (el as HTMLElement).offsetHeight;
}
tableHeightRef.value =
bottomIncludeBody -
(resizeHeightOffset || 0) -
paddingHeight -
borderHeight -
paginationHeight -
footerHeight -
headerHeight;
useTimeout(() => {
tableHeightRef.value =
tableHeightRef.value! > maxHeight! ? (maxHeight as number) : tableHeightRef.value;
cb && cb();
}, 0);
}
const getCanResize = computed(() => {
const { canResize, scroll } = unref(propsRef);
return canResize && !(scroll || {}).y;
});
useWindowSizeFn(calcTableHeight, 100);
// function clear() {
// window.clearInterval(timer);
// }
onMounted(() => {
if (unref(getCanResize)) {
calcTableHeight();
const hasFixedLeft = (unref(propsRef).columns || []).some((item) => item.fixed === 'left');
// TODO antv table问题情况太多只能先用下面方式定时器hack
useTimeout(() => {
calcTableHeight(() => {
// 有左侧固定列的时候才有问题
hasFixedLeft &&
useTimeout(() => {
triggerWindowResize();
}, 300);
});
}, 200);
}
});
const getScrollRef = computed(() => {
const tableHeight = unref(tableHeightRef);
const { canResize, scroll } = unref(propsRef);
return {
x: '100%',
y: canResize ? tableHeight : null,
scrollToFirstRowOnChange: false,
...scroll,
};
});
return { getScrollRef, redoHeight };
}

View File

@@ -0,0 +1,155 @@
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 { FETCH_SETTING } from './const';
// 注释看 types/table
export const basicProps = {
autoCreateKey: {
type: Boolean as PropType<boolean>,
default: true,
},
striped: {
type: Boolean as PropType<boolean>,
default: true,
},
showSummary: {
type: Boolean as PropType<boolean>,
default: false,
},
summaryFunc: {
type: [Function, Array] as PropType<(...arg: any[]) => any[]>,
default: null,
},
canColDrag: {
type: Boolean as PropType<boolean>,
default: true,
},
isTreeTable: {
type: Boolean as PropType<boolean>,
default: false,
},
api: {
type: Function as PropType<(...arg: any[]) => Promise<any>>,
default: null,
},
beforeFetch: {
type: Function as PropType<Fn>,
default: null,
},
afterFetch: {
type: Function as PropType<Fn>,
default: null,
},
handleSearchInfoFn: {
type: Function as PropType<Fn>,
default: null,
},
fetchSetting: {
type: Object as PropType<FetchSetting>,
default: () => {
return FETCH_SETTING;
},
},
// 立即请求接口
immediate: { type: Boolean as PropType<boolean>, default: true },
emptyDataIsShowTable: {
type: Boolean as PropType<boolean>,
default: true,
},
// 额外的请求参数
searchInfo: {
type: Object as PropType<any>,
default: null,
},
// 使用搜索表单
useSearchForm: {
type: Boolean as PropType<boolean>,
default: false,
},
// 表单配置
formConfig: {
type: Object as PropType<Partial<FormProps>>,
default: null,
},
columns: {
type: [Array] as PropType<BasicColumn[]>,
default: null,
},
showIndexColumn: {
type: Boolean as PropType<boolean>,
default: true,
},
indexColumnProps: {
type: Object as PropType<BasicColumn>,
default: null,
},
actionColumn: {
type: Object as PropType<BasicColumn>,
default: null,
},
ellipsis: {
type: Boolean as PropType<boolean>,
default: true,
},
canResize: {
type: Boolean as PropType<boolean>,
default: true,
},
clearSelectOnPageChange: {
type: Boolean as PropType<boolean>,
default: false,
},
resizeHeightOffset: {
type: Number as PropType<number>,
default: 0,
},
rowSelection: {
type: Object as PropType<TableRowSelection<any> | null>,
default: null,
},
title: {
type: [String, Function] as PropType<string | ((data: any) => any)>,
default: null,
},
titleHelpMessage: {
type: [String, Array] as PropType<string | string[]>,
},
maxHeight: {
type: Number as PropType<number>,
},
dataSource: {
type: Array as PropType<any[]>,
default: null,
},
rowKey: {
type: [String, Function] as PropType<string | ((record: any) => string)>,
default: '',
},
bordered: {
type: Boolean as PropType<boolean>,
default: true,
},
pagination: {
type: [Object, Boolean] as PropType<PaginationProps | boolean>,
default: null,
},
loading: {
type: Boolean as PropType<boolean>,
default: false,
},
rowClassName: {
type: Function as PropType<(record: TableCustomRecord<any>, index: number) => string>,
},
scroll: {
type: Object as PropType<{ x: number | true; y: number }>,
default: null,
},
};

View File

@@ -0,0 +1,41 @@
@import (reference) '../../../../design/index.less';
@prefix-cls: ~'editable-cell';
.@{prefix-cls} {
position: relative;
&__wrapper {
display: flex;
align-items: center;
}
&__icon {
&:hover {
transform: scale(1.2);
svg {
color: @primary-color;
}
}
}
&__normal {
padding-right: 48px;
&-icon {
position: absolute;
top: 4px;
right: 0;
display: none;
width: 20px;
cursor: pointer;
}
}
&:hover {
.@{prefix-cls}__normal-icon {
display: inline-block;
}
}
}

View File

@@ -0,0 +1,228 @@
@import (reference) '../../../../design/index.less';
@border-color: hsla(0, 0%, 80.8%, 0.25);
.basic-table {
&-title {
display: flex;
justify-content: space-between;
align-items: center;
}
&-row__striped {
td {
background: #fafafa;
}
}
&-img__preview {
display: flex;
img {
margin-right: 4px;
}
}
&-action {
display: flex;
}
&-toolbar {
> * {
margin-right: 10px;
}
}
.resize-table-th {
position: relative !important;
.table-draggable-handle {
position: absolute;
right: -5px;
bottom: 0;
left: auto !important;
height: 100% !important;
cursor: col-resize;
transform: none !important;
touch-action: none;
}
}
&-drag-body {
position: relative;
cursor: move;
}
.drag-line td {
border-top: 2px dashed @primary-color;
}
.ant-table-wrapper {
padding: 8px;
background: #fff;
border-radius: 2px;
.ant-table-title {
padding: 0 0 10px 0 !important;
}
.ant-table.ant-table-bordered .ant-table-title {
border: none !important;
}
}
//
.ant-table {
&-title {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 6px;
}
.ant-table-thead > tr > th,
.ant-table-header {
background: #f1f3f4;
}
.ant-table-tbody > tr.ant-table-row-selected td {
background: fade(@primary-color, 8%) !important;
}
}
.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;
}
.ant-table-thead {
th {
border: none;
}
}
.ant-table-bordered .ant-table-tbody > tr > td {
border-bottom: 1px solid @border-color;
&: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 td {
white-space: nowrap;
}
.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;
}
.ant-table-thead > tr > th,
.ant-table-tbody > tr > td {
padding: 9px 8px !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 .ant-table-header {
border-left: 1px solid @border-color;
.ant-table-fixed {
border-bottom: none;
}
}
.ant-table-fixed-left {
.ant-table-header {
overflow-y: hidden !important;
}
.ant-table-fixed {
border-bottom: none;
}
}
.ant-radio {
&-inner {
border-color: @text-color-base;
}
}
.ant-checkbox {
&:not(.ant-checkbox-checked) {
.ant-checkbox-inner {
border-color: @text-color-base;
}
}
}
.ant-table-bordered .ant-table-thead > tr:not(:last-child) > th,
.ant-table-tbody > tr > td {
word-break: break-word;
border-color: @border-color;
}
.ant-table-footer {
padding: 0;
.ant-table-wrapper {
padding: 0;
}
table {
border: none !important;
}
.ant-table-body {
overflow-x: hidden !important;
overflow-y: scroll !important;
}
td {
padding: 12px 8px;
}
}
}
.table-form-container {
padding: 16px;
.ant-form {
padding: 12px 12px 4px 12px;
margin-bottom: 12px;
background: #fff;
border-radius: 2px;
}
.ant-table-wrapper {
border-radius: 2px;
}
}

View File

@@ -0,0 +1,8 @@
export type ComponentType =
| 'Input'
| 'InputPassword'
| 'InputNumber'
| 'Select'
| 'Checkbox'
| 'CheckboxGroup'
| 'Switch';

View File

@@ -0,0 +1,89 @@
import { VNodeChild } from 'vue';
import { PaginationRenderProps } from 'ant-design-vue/types/pagination';
export interface PaginationProps {
/**
* total number of data items
* @default 0
* @type number
*/
total?: number;
/**
* default initial page number
* @default 1
* @type number
*/
defaultCurrent?: number;
/**
* current page number
* @type number
*/
current?: number;
/**
* default number of data items per page
* @default 10
* @type number
*/
defaultPageSize?: number;
/**
* number of data items per page
* @type number
*/
pageSize?: number;
/**
* Whether to hide pager on single page
* @default false
* @type boolean
*/
hideOnSinglePage?: boolean;
/**
* determine whether pageSize can be changed
* @default false
* @type boolean
*/
showSizeChanger?: boolean;
/**
* specify the sizeChanger options
* @default ['10', '20', '30', '40']
* @type string[]
*/
pageSizeOptions?: string[];
/**
* determine whether you can jump to pages directly
* @default false
* @type boolean
*/
showQuickJumper?: boolean | object;
/**
* to display the total number and range
* @type Function
*/
showTotal?: (total: number, range: [number, number]) => any;
/**
* specify the size of Pagination, can be set to small
* @default ''
* @type string
*/
size?: string;
/**
* whether to use simple mode
* @type boolean
*/
simple?: boolean;
/**
* to customize item innerHTML
* @type Function
*/
itemRender?: (props: PaginationRenderProps) => VNodeChild | JSX.Element;
}

View File

@@ -0,0 +1,315 @@
import { VNodeChild } from 'vue';
import { PaginationProps } from './pagination';
import { FormProps } from '/@/components/Form/index';
import {
ExpandedRowRenderRecord,
PaginationConfig,
SorterResult,
TableCurrentDataSource,
TableCustomRecord,
TableRowSelection,
} from 'ant-design-vue/types/table/table';
import { ColumnProps } from 'ant-design-vue/types/table/column';
import { ComponentType } from './componentType';
export declare type SortOrder = 'ascend' | 'descend';
export interface ColumnFilterItem {
text?: string;
value?: string;
children?: any;
}
export interface RenderEditableCellParams {
dataIndex: string;
component?: ComponentType;
componentOn?: { [key: string]: Fn };
componentProps?: any;
}
export interface FetchParams {
searchInfo?: any;
page?: number;
}
export interface GetColumnsParams {
ignoreIndex?: boolean;
}
export interface TableActionType {
reload: (opt?: FetchParams) => Promise<void>;
getSelectRows: () => any[];
clearSelectedRowKeys: () => void;
getSelectRowKeys: () => string[];
deleteSelectRowByKey: (key: string) => void;
setPagination: (info: Partial<PaginationProps>) => void;
setTableData: (values: any[]) => void;
getColumns: ({ ignoreIndex }?: GetColumnsParams) => BasicColumn[];
setColumns: (columns: BasicColumn[] | string[]) => void;
getDataSource: () => any[];
setLoading: (loading: boolean) => void;
setProps: (props: Partial<BasicTableProps>) => void;
redoHeight: () => void;
setSelectedRowKeys: (rowKeys: string[] | number[]) => void;
getPaginationRef: () => PaginationProps | boolean;
}
export interface FetchSetting {
// 请求接口当前页数
pageField: string;
// 每页显示多少条
sizeField: string;
// 请求结果列表字段 支持 a.b.c
listField: string;
// 请求结果总数字段 支持 a.b.c
totalField: string;
}
export interface BasicTableProps<T = any> {
// 斑马纹
striped?: boolean;
// 是否自动生成key
autoCreateKey?: boolean;
// 计算合计行的方法
summaryFunc?: (...arg: any) => any[];
// 是否显示合计行
showSummary?: boolean;
// 是否可拖拽列
canColDrag?: boolean;
// 是否树表
isTreeTable?: boolean;
// 接口请求对象
api?: (...arg: any) => Promise<any>;
// 请求之前处理参数
beforeFetch?: Fn;
// 自定义处理接口返回参数
afterFetch?: Fn;
// 查询条件请求之前处理
handleSearchInfoFn?: Fn;
// 请求接口配置
fetchSetting?: FetchSetting;
// 立即请求接口
immediate?: boolean;
// 在开起搜索表单的时候,如果没有数据是否显示表格
emptyDataIsShowTable?: boolean;
// 额外的请求参数
searchInfo?: any;
// 使用搜索表单
useSearchForm?: boolean;
// 表单配置
formConfig?: FormProps;
// 列配置
columns: BasicColumn[];
// 是否显示序号列
showIndexColumn?: boolean;
// 序号列配置
indexColumnProps?: BasicColumn;
actionColumn?: BasicColumn;
// 文本超过宽度是否显示。。。
ellipsis?: boolean;
// 是否可以自适应高度
canResize?: boolean;
// 自适应高度偏移, 计算结果-偏移量
resizeHeightOffset?: number;
// 在分页改变的时候清空选项
clearSelectOnPageChange?: boolean;
//
rowKey?: string | ((record: any) => string);
// 数据
dataSource?: any[];
// 标题右侧提示
titleHelpMessage?: string | string[];
// 表格滚动最大高度
maxHeight?: number;
// 是否显示边框
bordered?: boolean;
// 分页配置
pagination?: PaginationProps | boolean;
// loading加载
loading?: boolean;
/**
* The column contains children to display
* @default 'children'
* @type string | string[]
*/
childrenColumnName?: string | string[];
/**
* Override default table elements
* @type object
*/
components?: object;
/**
* Expand all rows initially
* @default false
* @type boolean
*/
defaultExpandAllRows?: boolean;
/**
* Initial expanded row keys
* @type string[]
*/
defaultExpandedRowKeys?: string[];
/**
* Current expanded row keys
* @type string[]
*/
expandedRowKeys?: string[];
/**
* Expanded container render for each row
* @type Function
*/
expandedRowRender?: (record?: ExpandedRowRenderRecord<T>) => VNodeChild | JSX.Element;
/**
* Customize row expand Icon.
* @type Function | VNodeChild
*/
expandIcon?: Function | VNodeChild | JSX.Element;
/**
* Whether to expand row by clicking anywhere in the whole row
* @default false
* @type boolean
*/
expandRowByClick?: boolean;
/**
* The index of `expandIcon` which column will be inserted when `expandIconAsCell` is false. default 0
*/
expandIconColumnIndex?: number;
/**
* Table footer renderer
* @type Function | VNodeChild
*/
footer?: Function | VNodeChild | JSX.Element;
/**
* Indent size in pixels of tree data
* @default 15
* @type number
*/
indentSize?: number;
/**
* i18n text including filter, sort, empty text, etc
* @default { filterConfirm: 'Ok', filterReset: 'Reset', emptyText: 'No Data' }
* @type object
*/
locale?: object;
/**
* Row's className
* @type Function
*/
rowClassName?: (record: TableCustomRecord<T>) => string;
/**
* Row selection config
* @type object
*/
rowSelection?: TableRowSelection<T>;
/**
* Set horizontal or vertical scrolling, can also be used to specify the width and height of the scroll area.
* It is recommended to set a number for x, if you want to set it to true,
* you need to add style .ant-table td { white-space: nowrap; }.
* @type object
*/
scroll?: { x?: number | true; y?: number };
/**
* Whether to show table header
* @default true
* @type boolean
*/
showHeader?: boolean;
/**
* Size of table
* @default 'default'
* @type string
*/
size?: 'default' | 'middle' | 'small' | 'large';
/**
* Table title renderer
* @type Function | ScopedSlot
*/
title?: VNodeChild | JSX.Element;
/**
* Set props on per header row
* @type Function
*/
customHeaderRow?: (column: ColumnProps<T>, index: number) => object;
/**
* Set props on per row
* @type Function
*/
customRow?: (record: T, index: number) => object;
/**
* `table-layout` attribute of table element
* `fixed` when header/columns are fixed, or using `column.ellipsis`
*
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout
* @version 1.5.0
*/
tableLayout?: 'auto' | 'fixed' | string;
/**
* the render container of dropdowns in table
* @param triggerNode
* @version 1.5.0
*/
getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement;
/**
* Data can be changed again before rendering.
* The default configuration of general user empty data.
* You can configured globally through [ConfigProvider](https://antdv.com/components/config-provider-cn/)
*
* @version 1.5.4
*/
transformCellText?: Function;
/**
* Callback executed when pagination, filters or sorter is changed
* @param pagination
* @param filters
* @param sorter
* @param currentDataSource
*/
onChange?: (
pagination: PaginationConfig,
filters: Partial<Record<keyof T, string[]>>,
sorter: SorterResult<T>,
extra: TableCurrentDataSource<T>
) => void;
/**
* Callback executed when the row expand icon is clicked
*
* @param expanded
* @param record
*/
onExpand?: (expande: boolean, record: T) => void;
/**
* Callback executed when the expanded rows change
* @param expandedRows
*/
onExpandedRowsChange?: (expandedRows: string[] | number[]) => void;
}
export interface BasicColumn<T = any> extends ColumnProps<T> {
children?: BasicColumn[];
//
flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION';
}

View File

@@ -0,0 +1,19 @@
export interface ActionItem {
on?: any;
label: string;
disabled?: boolean;
color?: 'success' | 'error' | 'warning';
type?: string;
props?: any;
icon?: string;
popConfirm?: PopConfirm;
}
export interface PopConfirm {
title: string;
okText?: string;
cancelText?: string;
confirm: any;
cancel?: any;
icon?: string;
}