mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-27 14:13:40 +08:00
wip(table): perf table #136,146,134
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import Button from './src/BasicButton.vue';
|
||||
import PopConfirmButton from './src/PopConfirmButton.vue';
|
||||
import { withInstall } from '../util';
|
||||
|
||||
withInstall(Button);
|
||||
export { Button };
|
||||
withInstall(Button, PopConfirmButton);
|
||||
export { Button, PopConfirmButton };
|
||||
|
34
src/components/Button/src/PopConfirmButton.vue
Normal file
34
src/components/Button/src/PopConfirmButton.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, h, unref } from 'vue';
|
||||
|
||||
import { Popconfirm } from 'ant-design-vue';
|
||||
import BasicButton from './BasicButton.vue';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { extendSlots } from '/@/utils/helper/tsxHelper';
|
||||
import { omit } from 'lodash-es';
|
||||
const { t } = useI18n();
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PopButton',
|
||||
inheritAttrs: false,
|
||||
components: { Popconfirm, BasicButton },
|
||||
props: {
|
||||
enable: propTypes.bool.def(true),
|
||||
okText: propTypes.string.def(t('component.drawer.okText')),
|
||||
cancelText: propTypes.string.def(t('component.drawer.cancelText')),
|
||||
},
|
||||
setup(props, { slots, attrs }) {
|
||||
return () => {
|
||||
const popValues = { ...props, ...unref(attrs) };
|
||||
|
||||
const Button = h(BasicButton, omit(unref(attrs), 'icon'), extendSlots(slots));
|
||||
if (!props.enable) {
|
||||
return Button;
|
||||
}
|
||||
|
||||
return h(Popconfirm, omit(popValues, 'icon'), { default: () => Button });
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
@@ -1,11 +1,5 @@
|
||||
<template>
|
||||
<Scrollbar
|
||||
ref="scrollbarRef"
|
||||
:wrapClass="`scrollbar__wrap`"
|
||||
:viewClass="`scrollbar__view`"
|
||||
class="scroll-container"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<Scrollbar ref="scrollbarRef" class="scroll-container" v-bind="$attrs">
|
||||
<slot />
|
||||
</Scrollbar>
|
||||
</template>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<template #overlay>
|
||||
<a-menu :selectedKeys="selectedKeys">
|
||||
<template v-for="item in getMenuList" :key="`${item.event}`">
|
||||
<a-menu-item @click="handleClickMenu({ key: item.event })" :disabled="item.disabled">
|
||||
<a-menu-item @click="handleClickMenu(item)" :disabled="item.disabled">
|
||||
<Icon :icon="item.icon" v-if="item.icon" />
|
||||
<span class="ml-1">{{ item.text }}</span>
|
||||
</a-menu-item>
|
||||
@@ -59,9 +59,11 @@
|
||||
setup(props, { emit }) {
|
||||
const getMenuList = computed(() => props.dropMenuList);
|
||||
|
||||
function handleClickMenu({ key }: { key: string }) {
|
||||
const menu = unref(getMenuList).find((item) => `${item.event}` === `${key}`);
|
||||
function handleClickMenu(item: DropMenu) {
|
||||
const { event } = item;
|
||||
const menu = unref(getMenuList).find((item) => `${item.event}` === `${event}`);
|
||||
emit('menuEvent', menu);
|
||||
item.onClick?.();
|
||||
}
|
||||
|
||||
return { handleClickMenu, getMenuList };
|
||||
|
@@ -1,4 +1,5 @@
|
||||
export interface DropMenu {
|
||||
onClick?: Fn;
|
||||
to?: string;
|
||||
icon?: string;
|
||||
event: string | number;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Select v-bind="attrs" :options="options" v-model:value="state">
|
||||
<Select v-bind="attrs" :options="getOptions" v-model:value="state">
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)">
|
||||
<slot :name="item" v-bind="data" />
|
||||
</template>
|
||||
@@ -15,7 +15,7 @@
|
||||
</Select>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, ref, watchEffect } from 'vue';
|
||||
import { defineComponent, PropType, ref, watchEffect, computed, unref } from 'vue';
|
||||
import { Select } from 'ant-design-vue';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
|
||||
@@ -24,31 +24,31 @@
|
||||
|
||||
import { LoadingOutlined } from '@ant-design/icons-vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
|
||||
type OptionsItem = { label: string; value: string; disabled?: boolean };
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RadioButtonGroup',
|
||||
name: 'ApiSelect',
|
||||
components: {
|
||||
Select,
|
||||
LoadingOutlined,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String as PropType<string>,
|
||||
},
|
||||
value: propTypes.string,
|
||||
api: {
|
||||
type: Function as PropType<(arg?: Recordable) => Promise<OptionsItem[]>>,
|
||||
default: null,
|
||||
},
|
||||
// api params
|
||||
params: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: () => {},
|
||||
},
|
||||
resultField: {
|
||||
type: String as PropType<string>,
|
||||
default: '',
|
||||
},
|
||||
// support xxx.xxx.xx
|
||||
resultField: propTypes.string.def(''),
|
||||
labelField: propTypes.string.def('label'),
|
||||
valueField: propTypes.string.def('value'),
|
||||
},
|
||||
setup(props) {
|
||||
const options = ref<OptionsItem[]>([]);
|
||||
@@ -59,6 +59,20 @@
|
||||
// Embedded in the form, just use the hook binding to perform form verification
|
||||
const [state] = useRuleFormItem(props);
|
||||
|
||||
const getOptions = computed(() => {
|
||||
const { labelField, valueField } = props;
|
||||
|
||||
return unref(options).reduce((prev, next: Recordable) => {
|
||||
if (next) {
|
||||
prev.push({
|
||||
label: next[labelField],
|
||||
value: next[valueField],
|
||||
});
|
||||
}
|
||||
return prev;
|
||||
}, [] as OptionsItem[]);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
fetch();
|
||||
});
|
||||
@@ -83,7 +97,7 @@
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
return { state, attrs, options, loading, t };
|
||||
return { state, attrs, getOptions, loading, t };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@@ -117,7 +117,7 @@ export function useFormEvents({
|
||||
const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
|
||||
|
||||
const index = schemaList.findIndex((schema) => schema.field === prefixField);
|
||||
const hasInList = schemaList.some((item) => item.field === prefixField);
|
||||
const hasInList = schemaList.some((item) => item.field === prefixField || schema.field);
|
||||
|
||||
if (!hasInList) return;
|
||||
|
||||
@@ -147,6 +147,7 @@ export function useFormEvents({
|
||||
error(
|
||||
'All children of the form Schema array that need to be updated must contain the `field` field'
|
||||
);
|
||||
return;
|
||||
}
|
||||
const schema: FormSchema[] = [];
|
||||
updateData.forEach((item) => {
|
||||
|
@@ -1 +1,4 @@
|
||||
export { createImgPreview } from './src/functional';
|
||||
|
||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||
export const ImagePreview = createAsyncComponent(() => import('./src/index.vue'));
|
||||
|
69
src/components/Preview/src/index.vue
Normal file
69
src/components/Preview/src/index.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<PreviewGroup :class="prefixCls">
|
||||
<slot v-if="!imageList || $slots.default" />
|
||||
<template v-else>
|
||||
<template v-for="item in getImageList" :key="item.src">
|
||||
<Image v-bind="item">
|
||||
<template #placeholder v-if="item.placeholder">
|
||||
<Image v-bind="item" :src="item.placeholder" :preview="false" />
|
||||
</template>
|
||||
</Image>
|
||||
</template>
|
||||
</template>
|
||||
</PreviewGroup>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
|
||||
import { Image } from 'ant-design-vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { ImageItem } from './types';
|
||||
import { isString } from '/@/utils/is';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ImagePreview',
|
||||
components: {
|
||||
Image,
|
||||
PreviewGroup: Image.PreviewGroup,
|
||||
},
|
||||
props: {
|
||||
functional: propTypes.bool,
|
||||
imageList: {
|
||||
type: Array as PropType<ImageItem[]>,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { prefixCls } = useDesign('image-preview');
|
||||
|
||||
const getImageList = computed(() => {
|
||||
const { imageList } = props;
|
||||
if (!imageList) {
|
||||
return [];
|
||||
}
|
||||
return imageList.map((item) => {
|
||||
if (isString(item)) {
|
||||
return {
|
||||
src: item,
|
||||
placeholder: false,
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
});
|
||||
|
||||
return { prefixCls, getImageList };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import (reference) '../../../design/index.less';
|
||||
@prefix-cls: ~'@{namespace}-image-preview';
|
||||
|
||||
.@{prefix-cls} {
|
||||
.ant-image-preview-operations {
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -10,3 +10,21 @@ export interface Props {
|
||||
imageList: string[];
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface ImageProps {
|
||||
alt?: string;
|
||||
fallback?: string;
|
||||
src: string;
|
||||
width: string | number;
|
||||
height?: string | number;
|
||||
placeholder?: string | boolean;
|
||||
preview?:
|
||||
| boolean
|
||||
| {
|
||||
visible?: boolean;
|
||||
onVisibleChange?: (visible: boolean, prevVisible: boolean) => void;
|
||||
getContainer: string | HTMLElement | (() => HTMLElement);
|
||||
};
|
||||
}
|
||||
|
||||
export type ImageItem = string | ImageProps;
|
||||
|
@@ -1,9 +1,14 @@
|
||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||
|
||||
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 { default as TableAction } from './src/components/TableAction.vue';
|
||||
// export { default as TableImg } from './src/components/TableImg.vue';
|
||||
export { renderEditableCell, renderEditableRow } from './src/components/renderEditable';
|
||||
export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue';
|
||||
|
||||
export const TableImg = createAsyncComponent(() => import('./src/components/TableImg.vue'));
|
||||
// export const TableAction = createAsyncComponent(() => import('./src/components/TableAction.vue'));
|
||||
|
||||
export * from './src/types/table';
|
||||
export * from './src/types/pagination';
|
||||
export * from './src/types/tableAction';
|
||||
|
@@ -1,14 +1,16 @@
|
||||
<template>
|
||||
<div
|
||||
ref="wrapRef"
|
||||
class="basic-table"
|
||||
:class="{
|
||||
'table-form-container': getBindValues.useSearchForm,
|
||||
inset: getBindValues.inset,
|
||||
}"
|
||||
:class="[
|
||||
prefixCls,
|
||||
{
|
||||
[`${prefixCls}-form-container`]: getBindValues.useSearchForm,
|
||||
[`${prefixCls}--inset`]: getBindValues.inset,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<BasicForm
|
||||
:submitOnReset="true"
|
||||
submitOnReset
|
||||
v-bind="getFormProps"
|
||||
v-if="getBindValues.useSearchForm"
|
||||
:submitButtonOptions="{ loading: getLoading }"
|
||||
@@ -17,10 +19,11 @@
|
||||
@submit="handleSearchInfoChange"
|
||||
@advanced-change="redoHeight"
|
||||
>
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)">
|
||||
<slot :name="`form-${item}`" v-bind="data" />
|
||||
<template #[replaceFormSlotKey(item)]="data" v-for="item in getFormSlotKeys">
|
||||
<slot :name="item" v-bind="data" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<Table
|
||||
ref="tableElRef"
|
||||
v-bind="getBindValues"
|
||||
@@ -38,15 +41,12 @@
|
||||
import type { BasicTableProps, TableActionType, SizeType, SorterResult } from './types/table';
|
||||
import { PaginationProps } from './types/pagination';
|
||||
|
||||
import { defineComponent, ref, computed, unref, watch, nextTick } from 'vue';
|
||||
import { defineComponent, ref, computed, unref } from 'vue';
|
||||
import { Table } from 'ant-design-vue';
|
||||
import renderTitle from './components/renderTitle';
|
||||
import renderFooter from './components/renderFooter';
|
||||
import renderExpandIcon from './components/renderExpandIcon';
|
||||
import { BasicForm, FormProps, useForm } from '/@/components/Form/index';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
|
||||
import { isFunction } from '/@/utils/is';
|
||||
|
||||
import { isFunction, isString } from '/@/utils/is';
|
||||
import { deepMerge } from '/@/utils';
|
||||
import { omit } from 'lodash-es';
|
||||
|
||||
import { usePagination } from './hooks/usePagination';
|
||||
@@ -55,15 +55,18 @@
|
||||
import { useLoading } from './hooks/useLoading';
|
||||
import { useRowSelection } from './hooks/useRowSelection';
|
||||
import { useTableScroll } from './hooks/useTableScroll';
|
||||
import { provideTable } from './hooks/useProvinceTable';
|
||||
import { useCustomRow } from './hooks/useCustomRow';
|
||||
import { useTableStyle } from './hooks/useTableStyle';
|
||||
import { useTableHeader } from './hooks/useTableHeader';
|
||||
import { createTableContext } from './hooks/useTableContext';
|
||||
import { useTableFooter } from './hooks/useTableFooter';
|
||||
import { useTableForm } from './hooks/useTableForm';
|
||||
|
||||
import { useEventListener } from '/@/hooks/event/useEventListener';
|
||||
import { basicProps } from './props';
|
||||
import { useExpose } from '/@/hooks/core/useExpose';
|
||||
|
||||
import './style/index.less';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
export default defineComponent({
|
||||
props: basicProps,
|
||||
components: { Table, BasicForm },
|
||||
@@ -84,7 +87,8 @@
|
||||
const wrapRef = ref<Nullable<HTMLDivElement>>(null);
|
||||
const innerPropsRef = ref<Partial<BasicTableProps>>();
|
||||
|
||||
const [registerForm, { getFieldsValue }] = useForm();
|
||||
const { prefixCls } = useDesign('basic-table');
|
||||
const [registerForm, formActions] = useForm();
|
||||
|
||||
const getProps = computed(() => {
|
||||
return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
|
||||
@@ -92,7 +96,14 @@
|
||||
|
||||
const { getLoading, setLoading } = useLoading(getProps);
|
||||
const { getPaginationInfo, getPagination, setPagination } = usePagination(getProps);
|
||||
const { getColumnsRef, getColumns, setColumns } = useColumns(getProps, getPaginationInfo);
|
||||
const {
|
||||
getSortFixedColumns,
|
||||
getColumns,
|
||||
setColumns,
|
||||
getColumnsRef,
|
||||
getCacheColumns,
|
||||
} = useColumns(getProps, getPaginationInfo);
|
||||
|
||||
const {
|
||||
getDataSourceRef,
|
||||
getDataSource,
|
||||
@@ -107,14 +118,13 @@
|
||||
getPaginationInfo,
|
||||
setLoading,
|
||||
setPagination,
|
||||
getFieldsValue,
|
||||
getFieldsValue: formActions.getFieldsValue,
|
||||
},
|
||||
emit
|
||||
);
|
||||
|
||||
const { getScrollRef, redoHeight } = useTableScroll(getProps, tableElRef);
|
||||
|
||||
const {
|
||||
getRowSelection,
|
||||
getRowSelectionRef,
|
||||
getSelectRows,
|
||||
clearSelectedRowKeys,
|
||||
@@ -123,6 +133,13 @@
|
||||
setSelectedRowKeys,
|
||||
} = useRowSelection(getProps, emit);
|
||||
|
||||
const { getScrollRef, redoHeight } = useTableScroll(
|
||||
getProps,
|
||||
tableElRef,
|
||||
getColumnsRef,
|
||||
getRowSelectionRef
|
||||
);
|
||||
|
||||
const { customRow } = useCustomRow(getProps, {
|
||||
setSelectedRowKeys,
|
||||
getSelectRowKeys,
|
||||
@@ -131,74 +148,47 @@
|
||||
emit,
|
||||
});
|
||||
|
||||
const { getRowClassName } = useTableStyle(getProps);
|
||||
const { getRowClassName } = useTableStyle(getProps, prefixCls);
|
||||
|
||||
const getTitleProps = computed(
|
||||
(): Recordable => {
|
||||
const { title, showTableSetting, titleHelpMessage, tableSetting } = unref(getProps);
|
||||
const hideTitle = !slots.tableTitle && !title && !slots.toolbar && !showTableSetting;
|
||||
if (hideTitle && !isString(title)) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
title: hideTitle
|
||||
? null
|
||||
: renderTitle.bind(
|
||||
null,
|
||||
title,
|
||||
titleHelpMessage,
|
||||
slots,
|
||||
showTableSetting,
|
||||
tableSetting
|
||||
),
|
||||
};
|
||||
}
|
||||
const { getHeaderProps } = useTableHeader(getProps, slots);
|
||||
|
||||
const { getFooterProps } = useTableFooter(
|
||||
getProps,
|
||||
getScrollRef,
|
||||
tableElRef,
|
||||
getDataSourceRef
|
||||
);
|
||||
|
||||
const getBindValues = computed(() => {
|
||||
const { showSummary } = unref(getProps);
|
||||
const {
|
||||
getFormProps,
|
||||
replaceFormSlotKey,
|
||||
getFormSlotKeys,
|
||||
handleSearchInfoChange,
|
||||
} = useTableForm(getProps, slots, fetch);
|
||||
|
||||
const getBindValues = computed(() => {
|
||||
let propsData: Recordable = {
|
||||
size: 'middle',
|
||||
...(slots.expandedRowRender ? { expandIcon: renderExpandIcon() } : {}),
|
||||
...attrs,
|
||||
customRow,
|
||||
...unref(getProps),
|
||||
...unref(getTitleProps),
|
||||
...unref(getHeaderProps),
|
||||
scroll: unref(getScrollRef),
|
||||
loading: unref(getLoading),
|
||||
tableLayout: 'fixed',
|
||||
rowSelection: unref(getRowSelectionRef),
|
||||
rowKey: unref(getRowKey),
|
||||
columns: unref(getColumnsRef),
|
||||
columns: unref(getSortFixedColumns),
|
||||
pagination: unref(getPaginationInfo),
|
||||
dataSource: unref(getDataSourceRef),
|
||||
footer: unref(getFooterProps),
|
||||
};
|
||||
if (slots.expandedRowRender) {
|
||||
propsData = omit(propsData, 'scroll');
|
||||
}
|
||||
if (showSummary) {
|
||||
propsData.footer = renderFooter.bind(null, {
|
||||
scroll: scroll as any,
|
||||
columnsRef: getColumnsRef,
|
||||
summaryFunc: unref(getProps).summaryFunc,
|
||||
dataSourceRef: getDataSourceRef,
|
||||
rowSelectionRef: getRowSelectionRef,
|
||||
});
|
||||
}
|
||||
return propsData;
|
||||
});
|
||||
|
||||
const getFormProps = computed(() => {
|
||||
const { formConfig } = unref(getProps);
|
||||
const formProps: Partial<FormProps> = {
|
||||
showAdvancedButton: true,
|
||||
...formConfig,
|
||||
compact: true,
|
||||
};
|
||||
return formProps;
|
||||
});
|
||||
|
||||
const getEmptyDataIsShowTable = computed(() => {
|
||||
const { emptyDataIsShowTable, useSearchForm } = unref(getProps);
|
||||
if (emptyDataIsShowTable || !useSearchForm) {
|
||||
@@ -207,22 +197,6 @@
|
||||
return !!unref(getDataSourceRef).length;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => unref(getDataSourceRef),
|
||||
() => {
|
||||
handleSummary();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function handleSearchInfoChange(info: any) {
|
||||
const { handleSearchInfoFn } = unref(getProps);
|
||||
if (handleSearchInfoFn && isFunction(handleSearchInfoFn)) {
|
||||
info = handleSearchInfoFn(info) || info;
|
||||
}
|
||||
fetch({ searchInfo: info, page: 1 });
|
||||
}
|
||||
|
||||
function handleTableChange(
|
||||
pagination: PaginationProps,
|
||||
// @ts-ignore
|
||||
@@ -243,32 +217,8 @@
|
||||
fetch();
|
||||
}
|
||||
|
||||
function handleSummary() {
|
||||
if (unref(getProps).showSummary) {
|
||||
nextTick(() => {
|
||||
const tableEl = unref(tableElRef);
|
||||
if (!tableEl) return;
|
||||
const bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body');
|
||||
const bodyDom = bodyDomList[0];
|
||||
useEventListener({
|
||||
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,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setProps(props: Partial<BasicTableProps>) {
|
||||
innerPropsRef.value = deepMerge(unref(innerPropsRef) || {}, props);
|
||||
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
|
||||
}
|
||||
|
||||
const tableAction: TableActionType = {
|
||||
@@ -285,21 +235,19 @@
|
||||
setLoading,
|
||||
getDataSource,
|
||||
setProps,
|
||||
getRowSelection,
|
||||
getPaginationRef: getPagination,
|
||||
getColumns,
|
||||
getCacheColumns,
|
||||
getSize: () => {
|
||||
return unref(getBindValues).size as SizeType;
|
||||
},
|
||||
};
|
||||
|
||||
provideTable({
|
||||
...tableAction,
|
||||
wrapRef,
|
||||
});
|
||||
createTableContext({ ...tableAction, wrapRef, getBindValues });
|
||||
|
||||
useExpose<TableActionType>(tableAction);
|
||||
|
||||
emit('register', tableAction);
|
||||
emit('register', tableAction, formActions);
|
||||
|
||||
return {
|
||||
tableElRef,
|
||||
@@ -307,13 +255,16 @@
|
||||
getLoading,
|
||||
registerForm,
|
||||
handleSearchInfoChange,
|
||||
getFormProps,
|
||||
getEmptyDataIsShowTable,
|
||||
handleTableChange,
|
||||
getRowClassName,
|
||||
wrapRef,
|
||||
tableAction,
|
||||
redoHeight,
|
||||
getFormProps,
|
||||
replaceFormSlotKey,
|
||||
getFormSlotKeys,
|
||||
prefixCls,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@@ -1,131 +0,0 @@
|
||||
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 '/@/components/Table';
|
||||
import { Button } from '/@/components/Button';
|
||||
import { snowUuid } from '/@/utils/uuid';
|
||||
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,
|
||||
},
|
||||
|
||||
moreText: {
|
||||
type: String as PropType<string>,
|
||||
default: '更多',
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
function renderButton(action: ActionItem) {
|
||||
const { disabled = false, label, icon, color = '', type = 'link', ...actionProps } = action;
|
||||
const button = (
|
||||
<Button
|
||||
type={type}
|
||||
size="small"
|
||||
disabled={disabled}
|
||||
color={color}
|
||||
{...actionProps}
|
||||
key={`${snowUuid()}`}
|
||||
>
|
||||
{() => (
|
||||
<>
|
||||
{icon && <Icon icon={icon} class="mr-1" />}
|
||||
{label}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
return button;
|
||||
}
|
||||
|
||||
function renderPopConfirm(action: ActionItem) {
|
||||
const { popConfirm = null } = action;
|
||||
if (!popConfirm) {
|
||||
return renderButton(action);
|
||||
}
|
||||
const {
|
||||
title,
|
||||
okText = '确定',
|
||||
cancelText = '取消',
|
||||
confirm = () => {},
|
||||
cancel = () => {},
|
||||
icon = '',
|
||||
} = popConfirm;
|
||||
return (
|
||||
<Popconfirm
|
||||
key={`${snowUuid()}`}
|
||||
title={title}
|
||||
onConfirm={confirm}
|
||||
onCancel={cancel}
|
||||
okText={okText}
|
||||
cancelText={cancelText}
|
||||
icon={icon}
|
||||
>
|
||||
{() => renderButton(action)}
|
||||
</Popconfirm>
|
||||
);
|
||||
}
|
||||
|
||||
const dropdownDefaultSLot = () => (
|
||||
<Button type="link" size="small">
|
||||
{{
|
||||
default: () => (
|
||||
<>
|
||||
{props.moreText}
|
||||
<DownOutlined />
|
||||
</>
|
||||
),
|
||||
}}
|
||||
</Button>
|
||||
);
|
||||
|
||||
// 增加按钮的TYPE和COLOR
|
||||
return () => {
|
||||
const { dropDownActions = [], actions } = props;
|
||||
return (
|
||||
<div class={prefixCls}>
|
||||
{actions &&
|
||||
actions.map((action) => {
|
||||
return renderPopConfirm(action);
|
||||
})}
|
||||
{dropDownActions && dropDownActions.length && (
|
||||
<Dropdown overlayClassName="basic-tale-action-dropdown">
|
||||
{{
|
||||
default: dropdownDefaultSLot,
|
||||
overlay: () => {
|
||||
return (
|
||||
<Menu>
|
||||
{{
|
||||
default: () => {
|
||||
return dropDownActions.map((action) => {
|
||||
const { disabled = false } = action;
|
||||
action.ghost = true;
|
||||
return (
|
||||
<Menu.Item key={`${snowUuid()}`} disabled={disabled}>
|
||||
{() => {
|
||||
return renderPopConfirm(action);
|
||||
}}
|
||||
</Menu.Item>
|
||||
);
|
||||
});
|
||||
},
|
||||
}}
|
||||
</Menu>
|
||||
);
|
||||
},
|
||||
}}
|
||||
</Dropdown>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
126
src/components/Table/src/components/TableAction.vue
Normal file
126
src/components/Table/src/components/TableAction.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div :class="[prefixCls, getAlign]">
|
||||
<template v-for="(action, index) in getActions" :key="`${index}`">
|
||||
<PopConfirmButton v-bind="action">
|
||||
<Icon :icon="action.icon" class="mr-1" v-if="action.icon" />
|
||||
{{ action.label }}
|
||||
</PopConfirmButton>
|
||||
<Divider type="vertical" v-if="divider && index < getActions.length" />
|
||||
</template>
|
||||
|
||||
<Dropdown :trigger="['hover']" :dropMenuList="getDropList">
|
||||
<slot name="more" />
|
||||
<a-button type="link" size="small" v-if="!$slots.more">
|
||||
<MoreOutlined class="icon-more" />
|
||||
</a-button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed } from 'vue';
|
||||
import Icon from '/@/components/Icon/index';
|
||||
import { ActionItem } from '/@/components/Table';
|
||||
import { PopConfirmButton } from '/@/components/Button';
|
||||
import { Divider } from 'ant-design-vue';
|
||||
import { Dropdown } from '/@/components/Dropdown';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { MoreOutlined } from '@ant-design/icons-vue';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { useTableContext } from '../hooks/useTableContext';
|
||||
import { ACTION_COLUMN_FLAG } from '../const';
|
||||
export default defineComponent({
|
||||
name: 'TableAction',
|
||||
components: { Icon, PopConfirmButton, Divider, Dropdown, MoreOutlined },
|
||||
props: {
|
||||
actions: {
|
||||
type: Array as PropType<ActionItem[]>,
|
||||
default: null,
|
||||
},
|
||||
dropDownActions: {
|
||||
type: Array as PropType<ActionItem[]>,
|
||||
default: null,
|
||||
},
|
||||
divider: propTypes.bool.def(true),
|
||||
},
|
||||
setup(props) {
|
||||
const { prefixCls } = useDesign('basic-table-action');
|
||||
const table = useTableContext();
|
||||
const getActions = computed(() => {
|
||||
return props.actions.map((action) => {
|
||||
const { popConfirm } = action;
|
||||
return {
|
||||
...action,
|
||||
...(popConfirm || {}),
|
||||
onConfirm: popConfirm?.confirm,
|
||||
onCancel: popConfirm?.cancel,
|
||||
enable: !!popConfirm,
|
||||
type: 'link',
|
||||
size: 'small',
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const getDropList = computed(() => {
|
||||
return props.dropDownActions.map((action, index) => {
|
||||
const { label } = action;
|
||||
return {
|
||||
...action,
|
||||
text: label,
|
||||
divider: index < props.dropDownActions.length - 1 ? props.divider : false,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const getAlign = computed(() => {
|
||||
const columns = table.getColumns();
|
||||
const actionColumn = columns.find((item) => item.flag === ACTION_COLUMN_FLAG);
|
||||
return actionColumn?.align ?? 'left';
|
||||
});
|
||||
|
||||
return { prefixCls, getActions, getDropList, getAlign };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-basic-table-action';
|
||||
|
||||
.@{prefix-cls} {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
&.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&.right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-divider,
|
||||
.ant-divider-vertical {
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.icon-more {
|
||||
transform: rotate(90deg);
|
||||
|
||||
svg {
|
||||
font-size: 1.1em;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
85
src/components/Table/src/components/TableFooter.vue
Normal file
85
src/components/Table/src/components/TableFooter.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<Table
|
||||
v-if="summaryFunc"
|
||||
:showHeader="false"
|
||||
:bordered="false"
|
||||
:pagination="false"
|
||||
:dataSource="getDataSource"
|
||||
:rowKey="(r) => r[rowKey]"
|
||||
:columns="getColumns"
|
||||
tableLayout="fixed"
|
||||
:scroll="scroll"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
import { defineComponent, unref, computed, toRaw } from 'vue';
|
||||
import { Table } from 'ant-design-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import type { BasicColumn } from '../types/table';
|
||||
import { INDEX_COLUMN_FLAG } from '../const';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { useTableContext } from '../hooks/useTableContext';
|
||||
|
||||
const SUMMARY_ROW_KEY = '_row';
|
||||
const SUMMARY_INDEX_KEY = '_index';
|
||||
export default defineComponent({
|
||||
name: 'BasicTableFooter',
|
||||
components: { Table },
|
||||
props: {
|
||||
summaryFunc: {
|
||||
type: Function as PropType<Fn>,
|
||||
},
|
||||
scroll: {
|
||||
type: Object as PropType<Recordable>,
|
||||
},
|
||||
rowKey: propTypes.string.def('key'),
|
||||
},
|
||||
setup(props) {
|
||||
const table = useTableContext();
|
||||
|
||||
const getDataSource = computed((): Recordable[] => {
|
||||
const { summaryFunc } = props;
|
||||
if (!isFunction(summaryFunc)) {
|
||||
return [];
|
||||
}
|
||||
let dataSource = toRaw(unref(table.getDataSource()));
|
||||
dataSource = summaryFunc(dataSource);
|
||||
dataSource.forEach((item, i) => {
|
||||
item[props.rowKey] = `${i}`;
|
||||
});
|
||||
return dataSource;
|
||||
});
|
||||
|
||||
const getColumns = computed(() => {
|
||||
const dataSource = unref(getDataSource);
|
||||
const columns: BasicColumn[] = cloneDeep(table.getColumns());
|
||||
const index = columns.findIndex((item) => item.flag === INDEX_COLUMN_FLAG);
|
||||
const hasRowSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_ROW_KEY));
|
||||
const hasIndexSummary = dataSource.some((item) => Reflect.has(item, SUMMARY_INDEX_KEY));
|
||||
|
||||
if (index !== -1) {
|
||||
if (hasIndexSummary) {
|
||||
columns[index].customRender = ({ record }) => record[SUMMARY_INDEX_KEY];
|
||||
columns[index].ellipsis = false;
|
||||
} else {
|
||||
Reflect.deleteProperty(columns[index], 'customRender');
|
||||
}
|
||||
}
|
||||
if (table.getRowSelection() && hasRowSummary) {
|
||||
columns.unshift({
|
||||
width: 60,
|
||||
title: 'selection',
|
||||
key: 'selectionKey',
|
||||
align: 'center',
|
||||
customRender: ({ record }) => record[SUMMARY_ROW_KEY],
|
||||
});
|
||||
}
|
||||
return columns;
|
||||
});
|
||||
return { getColumns, getDataSource };
|
||||
},
|
||||
});
|
||||
</script>
|
64
src/components/Table/src/components/TableHeader.vue
Normal file
64
src/components/Table/src/components/TableHeader.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<slot name="tableTitle" v-if="$slots.tableTitle" />
|
||||
<TableTitle :helpMessage="titleHelpMessage" :title="title" v-if="!$slots.tableTitle && title" />
|
||||
|
||||
<div :class="`${prefixCls}__toolbar`">
|
||||
<slot name="toolbar" />
|
||||
<Divider type="vertical" v-if="$slots.toolbar" />
|
||||
<TableSetting :setting="tableSetting" v-if="showTableSetting" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { TableSetting } from '../types/table';
|
||||
import type { PropType } from 'vue';
|
||||
import { Divider } from 'ant-design-vue';
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import TableSettingComp from './settings/index.vue';
|
||||
import TableTitle from './TableTitle.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicTableHeader',
|
||||
components: {
|
||||
Divider,
|
||||
TableTitle,
|
||||
TableSetting: TableSettingComp,
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: [Function, String] as PropType<string | ((data: Recordable) => string)>,
|
||||
},
|
||||
tableSetting: {
|
||||
type: Object as PropType<TableSetting>,
|
||||
},
|
||||
showTableSetting: {
|
||||
type: Boolean,
|
||||
},
|
||||
titleHelpMessage: {
|
||||
type: [String, Array] as PropType<string | string[]>,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { prefixCls } = useDesign('basic-table-header');
|
||||
return { prefixCls };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-basic-table-header';
|
||||
|
||||
.@{prefix-cls} {
|
||||
&__toolbar {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
> * {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,16 +1,21 @@
|
||||
<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 :class="prefixCls" v-if="imgList && imgList.length">
|
||||
<PreviewGroup>
|
||||
<template v-for="img in imgList" :key="img">
|
||||
<Image :width="size" :src="img" />
|
||||
</template>
|
||||
</PreviewGroup>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import { createImgPreview } from '/@/components/Preview/index';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
|
||||
import { Image } from 'ant-design-vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TableAction',
|
||||
name: 'TableImage',
|
||||
components: { Image, PreviewGroup: Image.PreviewGroup },
|
||||
props: {
|
||||
imgList: {
|
||||
type: Array as PropType<string[]>,
|
||||
@@ -21,16 +26,25 @@
|
||||
default: 40,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
function handlePreview(index: number) {
|
||||
const { imgList } = props;
|
||||
|
||||
createImgPreview({
|
||||
imageList: imgList as string[],
|
||||
index: index,
|
||||
});
|
||||
}
|
||||
return { handlePreview };
|
||||
setup() {
|
||||
const { prefixCls } = useDesign('basic-table-img');
|
||||
return { prefixCls };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-basic-table-img';
|
||||
|
||||
.@{prefix-cls} {
|
||||
display: flex;
|
||||
|
||||
.ant-image {
|
||||
margin-right: 4px;
|
||||
cursor: zoom-in;
|
||||
|
||||
img {
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,283 +0,0 @@
|
||||
<template>
|
||||
<div class="table-settings">
|
||||
<Divider type="vertical" />
|
||||
|
||||
<Tooltip placement="top" v-if="getSetting.redo">
|
||||
<template #title>
|
||||
<span>{{ t('component.table.settingRedo') }}</span>
|
||||
</template>
|
||||
<RedoOutlined @click="redo" />
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip placement="top" v-if="getSetting.size">
|
||||
<template #title>
|
||||
<span>{{ t('component.table.settingDens') }}</span>
|
||||
</template>
|
||||
<Dropdown placement="bottomCenter" :trigger="['click']">
|
||||
<ColumnHeightOutlined />
|
||||
<template #overlay>
|
||||
<Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef">
|
||||
<MenuItem key="default">
|
||||
<span>{{ t('component.table.settingDensDefault') }}</span>
|
||||
</MenuItem>
|
||||
<MenuItem key="middle">
|
||||
<span>{{ t('component.table.settingDensMiddle') }}</span>
|
||||
</MenuItem>
|
||||
<MenuItem key="small">
|
||||
<span>{{ t('component.table.settingDensSmall') }}</span>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip placement="top" v-if="getSetting.setting">
|
||||
<template #title>
|
||||
<span>{{ t('component.table.settingColumn') }}</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"
|
||||
>
|
||||
{{ t('component.table.settingColumnShow') }}
|
||||
</Checkbox>
|
||||
<a-button size="small" type="link" @click="reset">
|
||||
{{ t('component.table.settingReset') }}</a-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<SettingOutlined />
|
||||
</Popover>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip placement="top" v-if="getSetting.fullScreen">
|
||||
<template #title>
|
||||
<span>{{ t('component.table.settingFullScreen') }}</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, watchEffect } 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';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
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()]);
|
||||
|
||||
const plainOptions = ref<Options[]>([]);
|
||||
const state = reactive<State>({
|
||||
indeterminate: false,
|
||||
checkAll: true,
|
||||
checkedList: [],
|
||||
defaultCheckList: [],
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
watchEffect(() => {
|
||||
const columns = table.getColumns();
|
||||
if (columns.length) {
|
||||
init();
|
||||
}
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
});
|
||||
if (!plainOptions.value.length) {
|
||||
plainOptions.value = 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.value.map((item) => item.value);
|
||||
if (e.target.checked) {
|
||||
state.checkedList = checkList;
|
||||
table.setColumns(checkList);
|
||||
} else {
|
||||
state.checkedList = [];
|
||||
table.setColumns([]);
|
||||
}
|
||||
}
|
||||
|
||||
function onChange(checkedList: string[]) {
|
||||
const len = plainOptions.value.length;
|
||||
state.indeterminate = !!checkedList.length && checkedList.length < len;
|
||||
state.checkAll = checkedList.length === len;
|
||||
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,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
redo: () => table.reload(),
|
||||
handleTitleClick,
|
||||
selectedKeysRef,
|
||||
handleFullScreen,
|
||||
isFullscreenRef,
|
||||
onCheckAllChange,
|
||||
onChange,
|
||||
plainOptions,
|
||||
reset,
|
||||
getSetting,
|
||||
...toRefs(state),
|
||||
t,
|
||||
};
|
||||
},
|
||||
});
|
||||
</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>
|
@@ -1,15 +1,16 @@
|
||||
<template>
|
||||
<BasicTitle class="basic-table-title" v-if="tableTitle" :helpMessage="helpMessage">
|
||||
{{ tableTitle }}
|
||||
<BasicTitle :class="prefixCls" v-if="getTitle" :helpMessage="helpMessage">
|
||||
{{ getTitle }}
|
||||
</BasicTitle>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from 'vue';
|
||||
|
||||
import { BasicTitle } from '/@/components/Basic/index';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
export default defineComponent({
|
||||
name: 'TableTitle',
|
||||
name: 'BasicTableTitle',
|
||||
components: { BasicTitle },
|
||||
props: {
|
||||
title: {
|
||||
@@ -23,7 +24,9 @@
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const tableTitle = computed(() => {
|
||||
const { prefixCls } = useDesign('basic-table-title');
|
||||
|
||||
const getTitle = computed(() => {
|
||||
const { title, getSelectRows = () => {} } = props;
|
||||
let tit = title;
|
||||
|
||||
@@ -35,7 +38,16 @@
|
||||
return tit;
|
||||
});
|
||||
|
||||
return { tableTitle };
|
||||
return { getTitle, prefixCls };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-basic-table-title';
|
||||
|
||||
.@{prefix-cls} {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,64 +0,0 @@
|
||||
import { Table } from 'ant-design-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { unref, ComputedRef } from 'vue';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import type { BasicColumn, TableRowSelection } from '../types/table';
|
||||
|
||||
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 | 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}
|
||||
/>
|
||||
);
|
||||
};
|
@@ -1,29 +0,0 @@
|
||||
import { Slots } from 'vue';
|
||||
import TableTitle from './TableTitle.vue';
|
||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
||||
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> </span>
|
||||
)}
|
||||
{
|
||||
<div class="basic-table-toolbar">
|
||||
{slots.toolbar && getSlot(slots, 'toolbar')}
|
||||
{showTableSetting && <TableSettingComp setting={tableSetting} />}
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
430
src/components/Table/src/components/settings/ColumnSetting.vue
Normal file
430
src/components/Table/src/components/settings/ColumnSetting.vue
Normal file
@@ -0,0 +1,430 @@
|
||||
<template>
|
||||
<Tooltip placement="top">
|
||||
<template #title>
|
||||
<span>{{ t('component.table.settingColumn') }}</span>
|
||||
</template>
|
||||
<Popover
|
||||
:getPopupContainer="getPopupContainer"
|
||||
placement="bottomLeft"
|
||||
trigger="click"
|
||||
@visibleChange="handleVisibleChange"
|
||||
:overlayClassName="`${prefixCls}__cloumn-list`"
|
||||
>
|
||||
<template #title>
|
||||
<div :class="`${prefixCls}__popover-title`">
|
||||
<Checkbox
|
||||
:indeterminate="indeterminate"
|
||||
v-model:checked="checkAll"
|
||||
@change="onCheckAllChange"
|
||||
>
|
||||
{{ t('component.table.settingColumnShow') }}
|
||||
</Checkbox>
|
||||
|
||||
<Checkbox v-model:checked="checkIndex" @change="handleIndexCheckChange">
|
||||
{{ t('component.table.settingIndexColumnShow') }}
|
||||
</Checkbox>
|
||||
|
||||
<Checkbox
|
||||
v-model:checked="checkSelect"
|
||||
@change="handleSelectCheckChange"
|
||||
:disabled="!defaultRowSelection"
|
||||
>
|
||||
{{ t('component.table.settingSelectColumnShow') }}
|
||||
</Checkbox>
|
||||
|
||||
<a-button size="small" type="link" @click="reset">
|
||||
{{ t('component.table.settingReset') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<ScrollContainer>
|
||||
<CheckboxGroup v-model:value="checkedList" @change="onChange" ref="columnListRef">
|
||||
<template v-for="item in plainOptions" :key="item.value">
|
||||
<div :class="`${prefixCls}__check-item`">
|
||||
<DragOutlined class="table-coulmn-drag-icon" />
|
||||
<Checkbox :value="item.value"> {{ item.label }} </Checkbox>
|
||||
|
||||
<Tooltip placement="bottomLeft" :mouseLeaveDelay="0.4">
|
||||
<template #title> {{ t('component.table.settingFixedLeft') }}</template>
|
||||
<Icon
|
||||
icon="line-md:arrow-align-left"
|
||||
:class="[
|
||||
`${prefixCls}__fixed-left`,
|
||||
{
|
||||
active: item.fixed === 'left',
|
||||
disabled: !checkedList.includes(item.value),
|
||||
},
|
||||
]"
|
||||
@click="handleColumnFixed(item, 'left')"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Divider type="vertical" />
|
||||
<Tooltip placement="bottomLeft" :mouseLeaveDelay="0.4">
|
||||
<template #title> {{ t('component.table.settingFixedRight') }}</template>
|
||||
<Icon
|
||||
icon="line-md:arrow-align-left"
|
||||
:class="[
|
||||
`${prefixCls}__fixed-right`,
|
||||
{
|
||||
active: item.fixed === 'right',
|
||||
disabled: !checkedList.includes(item.value),
|
||||
},
|
||||
]"
|
||||
@click="handleColumnFixed(item, 'right')"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</CheckboxGroup>
|
||||
</ScrollContainer>
|
||||
</template>
|
||||
<SettingOutlined />
|
||||
</Popover>
|
||||
</Tooltip>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import {
|
||||
defineComponent,
|
||||
ref,
|
||||
reactive,
|
||||
toRefs,
|
||||
watchEffect,
|
||||
nextTick,
|
||||
unref,
|
||||
computed,
|
||||
} from 'vue';
|
||||
import { Tooltip, Popover, Checkbox, Divider } from 'ant-design-vue';
|
||||
import { SettingOutlined, DragOutlined } from '@ant-design/icons-vue';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import { ScrollContainer } from '/@/components/Container';
|
||||
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { useTableContext } from '../../hooks/useTableContext';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { useSortable } from '/@/hooks/web/useSortable';
|
||||
|
||||
import { isNullAndUnDef } from '/@/utils/is';
|
||||
import { getPopupContainer } from '/@/utils';
|
||||
|
||||
import type { BasicColumn } from '../../types/table';
|
||||
|
||||
interface State {
|
||||
indeterminate: boolean;
|
||||
checkAll: boolean;
|
||||
checkedList: string[];
|
||||
defaultCheckList: string[];
|
||||
}
|
||||
|
||||
interface Options {
|
||||
label: string;
|
||||
value: string;
|
||||
fixed?: boolean | 'left' | 'right';
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ColumnSetting',
|
||||
components: {
|
||||
SettingOutlined,
|
||||
Popover,
|
||||
Tooltip,
|
||||
Checkbox,
|
||||
CheckboxGroup: Checkbox.Group,
|
||||
DragOutlined,
|
||||
ScrollContainer,
|
||||
Divider,
|
||||
Icon,
|
||||
},
|
||||
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const table = useTableContext();
|
||||
|
||||
const defaultRowSelection = table.getRowSelection();
|
||||
let inited = false;
|
||||
|
||||
const cachePlainOptions = ref<Options[]>([]);
|
||||
const plainOptions = ref<Options[]>([]);
|
||||
|
||||
const plainSortOptions = ref<Options[]>([]);
|
||||
|
||||
const columnListRef = ref<ComponentRef>(null);
|
||||
|
||||
const state = reactive<State>({
|
||||
indeterminate: false,
|
||||
checkAll: true,
|
||||
checkedList: [],
|
||||
defaultCheckList: [],
|
||||
});
|
||||
|
||||
const checkIndex = ref(false);
|
||||
const checkSelect = ref(false);
|
||||
|
||||
const { prefixCls } = useDesign('basic-column-setting');
|
||||
|
||||
const getValues = computed(() => {
|
||||
return unref(table?.getBindValues) || {};
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
const columns = table.getColumns();
|
||||
if (columns.length) {
|
||||
init();
|
||||
}
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
const values = unref(getValues);
|
||||
checkIndex.value = !!values.showIndexColumn;
|
||||
checkSelect.value = !!values.rowSelection;
|
||||
});
|
||||
|
||||
function getColumns() {
|
||||
const ret: Options[] = [];
|
||||
table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => {
|
||||
ret.push({
|
||||
label: item.title as string,
|
||||
value: (item.dataIndex || item.title) as string,
|
||||
...item,
|
||||
});
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
function init() {
|
||||
const columns = getColumns();
|
||||
|
||||
const checkList = table
|
||||
.getColumns()
|
||||
.map((item) => {
|
||||
if (item.defaultHidden) {
|
||||
return '';
|
||||
}
|
||||
return item.dataIndex || item.title;
|
||||
})
|
||||
.filter(Boolean) as string[];
|
||||
|
||||
if (!plainOptions.value.length) {
|
||||
plainOptions.value = columns;
|
||||
plainSortOptions.value = columns;
|
||||
cachePlainOptions.value = columns;
|
||||
state.defaultCheckList = checkList;
|
||||
} else {
|
||||
const fixedColumns = columns.filter((item) =>
|
||||
Reflect.has(item, 'fixed')
|
||||
) as BasicColumn[];
|
||||
|
||||
unref(plainOptions).forEach((item: BasicColumn) => {
|
||||
const findItem = fixedColumns.find((fCol) => fCol.dataIndex === item.dataIndex);
|
||||
if (findItem) {
|
||||
item.fixed = findItem.fixed;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
state.checkedList = checkList;
|
||||
}
|
||||
|
||||
// checkAll change
|
||||
function onCheckAllChange(e: ChangeEvent) {
|
||||
state.indeterminate = false;
|
||||
const checkList = plainOptions.value.map((item) => item.value);
|
||||
if (e.target.checked) {
|
||||
state.checkedList = checkList;
|
||||
table.setColumns(checkList);
|
||||
} else {
|
||||
state.checkedList = [];
|
||||
table.setColumns([]);
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger when check/uncheck a column
|
||||
function onChange(checkedList: string[]) {
|
||||
const len = plainOptions.value.length;
|
||||
state.indeterminate = !!checkedList.length && checkedList.length < len;
|
||||
state.checkAll = checkedList.length === len;
|
||||
|
||||
const sortList = unref(plainSortOptions).map((item) => item.value);
|
||||
checkedList.sort((prev, next) => {
|
||||
return sortList.indexOf(prev) - sortList.indexOf(next);
|
||||
});
|
||||
table.setColumns(checkedList);
|
||||
}
|
||||
|
||||
// reset columns
|
||||
function reset() {
|
||||
state.checkedList = [...state.defaultCheckList];
|
||||
state.checkAll = true;
|
||||
state.indeterminate = false;
|
||||
plainOptions.value = unref(cachePlainOptions);
|
||||
plainSortOptions.value = unref(cachePlainOptions);
|
||||
table.setColumns(table.getCacheColumns());
|
||||
}
|
||||
|
||||
// Open the pop-up window for drag and drop initialization
|
||||
function handleVisibleChange() {
|
||||
if (inited) return;
|
||||
nextTick(() => {
|
||||
const columnListEl = unref(columnListRef);
|
||||
if (!columnListEl) return;
|
||||
const el = columnListEl.$el;
|
||||
if (!el) return;
|
||||
// Drag and drop sort
|
||||
const { initSortable } = useSortable(el, {
|
||||
handle: '.table-coulmn-drag-icon ',
|
||||
onEnd: (evt) => {
|
||||
const { oldIndex, newIndex } = evt;
|
||||
if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || oldIndex === newIndex) {
|
||||
return;
|
||||
}
|
||||
// Sort column
|
||||
const columns = getColumns();
|
||||
|
||||
if (oldIndex > newIndex) {
|
||||
columns.splice(newIndex, 0, columns[oldIndex]);
|
||||
columns.splice(oldIndex + 1, 1);
|
||||
} else {
|
||||
columns.splice(newIndex + 1, 0, columns[oldIndex]);
|
||||
columns.splice(oldIndex, 1);
|
||||
}
|
||||
|
||||
plainSortOptions.value = columns;
|
||||
plainOptions.value = columns;
|
||||
table.setColumns(columns);
|
||||
},
|
||||
});
|
||||
initSortable();
|
||||
inited = true;
|
||||
});
|
||||
}
|
||||
|
||||
// Control whether the serial number column is displayed
|
||||
function handleIndexCheckChange(e: ChangeEvent) {
|
||||
table.setProps({
|
||||
showIndexColumn: e.target.checked,
|
||||
});
|
||||
}
|
||||
|
||||
// Control whether the check box is displayed
|
||||
function handleSelectCheckChange(e: ChangeEvent) {
|
||||
table.setProps({
|
||||
rowSelection: e.target.checked ? defaultRowSelection : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
function handleColumnFixed(item: BasicColumn, fixed?: 'left' | 'right') {
|
||||
if (!state.checkedList.includes(item.dataIndex as string)) return;
|
||||
|
||||
const columns = getColumns() as BasicColumn[];
|
||||
const isFixed = item.fixed === fixed ? false : fixed;
|
||||
const index = columns.findIndex((col) => col.dataIndex === item.dataIndex);
|
||||
if (index !== -1) {
|
||||
columns[index].fixed = isFixed;
|
||||
}
|
||||
item.fixed = isFixed;
|
||||
|
||||
if (isFixed && !item.width) {
|
||||
item.width = 100;
|
||||
}
|
||||
|
||||
table.setColumns(columns);
|
||||
}
|
||||
|
||||
return {
|
||||
t,
|
||||
...toRefs(state),
|
||||
onCheckAllChange,
|
||||
onChange,
|
||||
plainOptions,
|
||||
reset,
|
||||
prefixCls,
|
||||
columnListRef,
|
||||
handleVisibleChange,
|
||||
checkIndex,
|
||||
checkSelect,
|
||||
handleIndexCheckChange,
|
||||
handleSelectCheckChange,
|
||||
defaultRowSelection,
|
||||
handleColumnFixed,
|
||||
getPopupContainer,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-basic-column-setting';
|
||||
|
||||
.table-coulmn-drag-icon {
|
||||
margin: 0 5px;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.@{prefix-cls} {
|
||||
&__popover-title {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__check-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 100%;
|
||||
padding: 4px 16px 8px 0;
|
||||
|
||||
.ant-checkbox-wrapper {
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__fixed-left,
|
||||
&__fixed-right {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
cursor: pointer;
|
||||
|
||||
&.active,
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: @disabled-color;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
&__fixed-right {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
&__cloumn-list {
|
||||
svg {
|
||||
width: 1em !important;
|
||||
height: 1em !important;
|
||||
}
|
||||
|
||||
.ant-popover-inner-content {
|
||||
// max-height: 360px;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
// overflow: auto;
|
||||
}
|
||||
|
||||
.ant-checkbox-group {
|
||||
width: 100%;
|
||||
min-width: 260px;
|
||||
// flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.scroll-container {
|
||||
height: 220px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<Tooltip placement="top">
|
||||
<template #title>
|
||||
<span>{{ t('component.table.settingFullScreen') }}</span>
|
||||
</template>
|
||||
<FullscreenOutlined @click="handleFullScreen" v-if="!isFullscreenRef" />
|
||||
<FullscreenExitOutlined @click="handleFullScreen" v-else />
|
||||
</Tooltip>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useTableContext } from '../../hooks/useTableContext';
|
||||
import { Tooltip } from 'ant-design-vue';
|
||||
import { FullscreenOutlined, FullscreenExitOutlined } from '@ant-design/icons-vue';
|
||||
import { useFullscreen } from '/@/hooks/web/useFullScreen';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FullScreenSetting',
|
||||
components: {
|
||||
FullscreenExitOutlined,
|
||||
FullscreenOutlined,
|
||||
Tooltip,
|
||||
},
|
||||
|
||||
setup() {
|
||||
const table = useTableContext();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { toggleFullscreen, isFullscreenRef } = useFullscreen(table.wrapRef);
|
||||
|
||||
function handleFullScreen() {
|
||||
toggleFullscreen();
|
||||
}
|
||||
|
||||
return {
|
||||
handleFullScreen,
|
||||
isFullscreenRef,
|
||||
t,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
37
src/components/Table/src/components/settings/RedoSetting.vue
Normal file
37
src/components/Table/src/components/settings/RedoSetting.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<Tooltip placement="top">
|
||||
<template #title>
|
||||
<span>{{ t('component.table.settingRedo') }}</span>
|
||||
</template>
|
||||
<RedoOutlined @click="redo" />
|
||||
</Tooltip>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useTableContext } from '../../hooks/useTableContext';
|
||||
import { Tooltip } from 'ant-design-vue';
|
||||
import { RedoOutlined } from '@ant-design/icons-vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RedoSetting',
|
||||
components: {
|
||||
RedoOutlined,
|
||||
Tooltip,
|
||||
},
|
||||
|
||||
setup() {
|
||||
const table = useTableContext();
|
||||
const { t } = useI18n();
|
||||
|
||||
function redo() {
|
||||
table.reload();
|
||||
}
|
||||
|
||||
return {
|
||||
redo,
|
||||
t,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
65
src/components/Table/src/components/settings/SizeSetting.vue
Normal file
65
src/components/Table/src/components/settings/SizeSetting.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<Tooltip placement="top">
|
||||
<template #title>
|
||||
<span>{{ t('component.table.settingDens') }}</span>
|
||||
</template>
|
||||
|
||||
<Dropdown placement="bottomCenter" :trigger="['click']" :getPopupContainer="getPopupContainer">
|
||||
<ColumnHeightOutlined />
|
||||
<template #overlay>
|
||||
<Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef">
|
||||
<MenuItem key="default">
|
||||
<span>{{ t('component.table.settingDensDefault') }}</span>
|
||||
</MenuItem>
|
||||
<MenuItem key="middle">
|
||||
<span>{{ t('component.table.settingDensMiddle') }}</span>
|
||||
</MenuItem>
|
||||
<MenuItem key="small">
|
||||
<span>{{ t('component.table.settingDensSmall') }}</span>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</Tooltip>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { useTableContext } from '../../hooks/useTableContext';
|
||||
import { Tooltip, Dropdown, Menu } from 'ant-design-vue';
|
||||
import { ColumnHeightOutlined } from '@ant-design/icons-vue';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { getPopupContainer } from '/@/utils';
|
||||
|
||||
import type { SizeType } from '../../types/table';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SizeSetting',
|
||||
components: {
|
||||
ColumnHeightOutlined,
|
||||
Tooltip,
|
||||
Dropdown,
|
||||
Menu,
|
||||
MenuItem: Menu.Item,
|
||||
},
|
||||
setup() {
|
||||
const table = useTableContext();
|
||||
const { t } = useI18n();
|
||||
|
||||
const selectedKeysRef = ref<SizeType[]>([table.getSize()]);
|
||||
|
||||
function handleTitleClick({ key }: { key: SizeType }) {
|
||||
selectedKeysRef.value = [key];
|
||||
table.setProps({
|
||||
size: key,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
handleTitleClick,
|
||||
selectedKeysRef,
|
||||
getPopupContainer,
|
||||
t,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
61
src/components/Table/src/components/settings/index.vue
Normal file
61
src/components/Table/src/components/settings/index.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="table-settings">
|
||||
<RedoSetting v-if="getSetting.size" />
|
||||
<SizeSetting v-if="getSetting.redo" />
|
||||
|
||||
<ColumnSetting v-if="getSetting.setting" />
|
||||
|
||||
<FullScreenSetting v-if="getSetting.fullScreen" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed } from 'vue';
|
||||
import type { TableSetting } from '../../types/table';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||
import ColumnSetting from './ColumnSetting.vue';
|
||||
export default defineComponent({
|
||||
name: 'TableSetting',
|
||||
components: {
|
||||
ColumnSetting,
|
||||
SizeSetting: createAsyncComponent(() => import('./SizeSetting.vue')),
|
||||
RedoSetting: createAsyncComponent(() => import('./RedoSetting.vue')),
|
||||
FullScreenSetting: createAsyncComponent(() => import('./FullScreenSetting.vue')),
|
||||
},
|
||||
props: {
|
||||
setting: {
|
||||
type: Object as PropType<TableSetting>,
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n();
|
||||
|
||||
const getSetting = computed(
|
||||
(): TableSetting => {
|
||||
return {
|
||||
redo: true,
|
||||
size: true,
|
||||
setting: true,
|
||||
fullScreen: true,
|
||||
...props.setting,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return { getSetting, t };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
.table-settings {
|
||||
& > * {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 1.3em;
|
||||
height: 1.3em;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,9 +1,10 @@
|
||||
import { BasicColumn, BasicTableProps, GetColumnsParams } from '../types/table';
|
||||
import { PaginationProps } from '../types/pagination';
|
||||
import { unref, ComputedRef, Ref, computed, watchEffect, ref, toRaw } from 'vue';
|
||||
import { isBoolean, isArray, isObject } from '/@/utils/is';
|
||||
import { isBoolean, isArray, isString } from '/@/utils/is';
|
||||
import { DEFAULT_ALIGN, PAGE_SIZE, INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG } from '../const';
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { isEqual, cloneDeep } from 'lodash-es';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -107,27 +108,31 @@ export function useColumns(
|
||||
|
||||
const getColumnsRef = computed(() => {
|
||||
const columns = unref(columnsRef);
|
||||
if (!columns) {
|
||||
return [];
|
||||
}
|
||||
|
||||
handleIndexColumn(propsRef, getPaginationRef, columns);
|
||||
handleActionColumn(propsRef, columns);
|
||||
|
||||
if (!columns) {
|
||||
return [];
|
||||
}
|
||||
return columns;
|
||||
});
|
||||
|
||||
const getSortFixedColumns = computed(() => {
|
||||
return useFixedColumn(unref(getColumnsRef));
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
const columns = toRaw(unref(propsRef).columns);
|
||||
columnsRef.value = columns;
|
||||
cacheColumns = columns;
|
||||
cacheColumns = columns?.filter((item) => !item.flag) ?? [];
|
||||
});
|
||||
|
||||
/**
|
||||
* set columns
|
||||
* @param columns key|column
|
||||
* @param columnList key|column
|
||||
*/
|
||||
function setColumns(columns: Partial<BasicColumn>[] | string[]) {
|
||||
function setColumns(columnList: Partial<BasicColumn>[] | string[]) {
|
||||
const columns = cloneDeep(columnList);
|
||||
if (!isArray(columns)) return;
|
||||
|
||||
if (columns.length <= 0) {
|
||||
@@ -137,20 +142,36 @@ export function useColumns(
|
||||
|
||||
const firstColumn = columns[0];
|
||||
|
||||
if (isObject(firstColumn)) {
|
||||
const cacheKeys = cacheColumns.map((item) => item.dataIndex);
|
||||
|
||||
if (!isString(firstColumn)) {
|
||||
columnsRef.value = columns as BasicColumn[];
|
||||
} else {
|
||||
const newColumns = cacheColumns.filter(
|
||||
(item) =>
|
||||
(item.dataIndex || `${item.key}`) &&
|
||||
(columns as string[]).includes(`${item.key}`! || item.dataIndex!)
|
||||
);
|
||||
const columnKeys = columns as string[];
|
||||
const newColumns: BasicColumn[] = [];
|
||||
cacheColumns.forEach((item) => {
|
||||
if (columnKeys.includes(`${item.key}`! || item.dataIndex!)) {
|
||||
newColumns.push({
|
||||
...item,
|
||||
defaultHidden: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
// Sort according to another array
|
||||
if (!isEqual(cacheKeys, columns)) {
|
||||
newColumns.sort((prev, next) => {
|
||||
return (
|
||||
columnKeys.indexOf(prev.dataIndex as string) -
|
||||
columnKeys.indexOf(next.dataIndex as string)
|
||||
);
|
||||
});
|
||||
}
|
||||
columnsRef.value = newColumns;
|
||||
}
|
||||
}
|
||||
|
||||
function getColumns(opt?: GetColumnsParams) {
|
||||
const { ignoreIndex, ignoreAction } = opt || {};
|
||||
const { ignoreIndex, ignoreAction, sort } = opt || {};
|
||||
let columns = toRaw(unref(getColumnsRef));
|
||||
if (ignoreIndex) {
|
||||
columns = columns.filter((item) => item.flag !== INDEX_COLUMN_FLAG);
|
||||
@@ -158,8 +179,38 @@ export function useColumns(
|
||||
if (ignoreAction) {
|
||||
columns = columns.filter((item) => item.flag !== ACTION_COLUMN_FLAG);
|
||||
}
|
||||
|
||||
if (sort) {
|
||||
columns = useFixedColumn(columns);
|
||||
}
|
||||
|
||||
return columns;
|
||||
}
|
||||
function getCacheColumns() {
|
||||
return cacheColumns;
|
||||
}
|
||||
|
||||
return { getColumnsRef, getColumns, setColumns };
|
||||
return { getColumnsRef, getCacheColumns, getColumns, setColumns, getSortFixedColumns };
|
||||
}
|
||||
|
||||
export function useFixedColumn(columns: BasicColumn[]) {
|
||||
const fixedLeftColumns: BasicColumn[] = [];
|
||||
const fixedRightColumns: BasicColumn[] = [];
|
||||
const defColumns: BasicColumn[] = [];
|
||||
for (const column of columns) {
|
||||
if (column.fixed === 'left') {
|
||||
fixedLeftColumns.push(column);
|
||||
continue;
|
||||
}
|
||||
if (column.fixed === 'right') {
|
||||
fixedRightColumns.push(column);
|
||||
continue;
|
||||
}
|
||||
defColumns.push(column);
|
||||
}
|
||||
const resultColumns = [...fixedLeftColumns, ...defColumns, ...fixedRightColumns].filter(
|
||||
(item) => !item.defaultHidden
|
||||
);
|
||||
|
||||
return resultColumns;
|
||||
}
|
||||
|
@@ -53,7 +53,12 @@ export function useRowSelection(propsRef: ComputedRef<BasicTableProps>, emit: Em
|
||||
return unref(selectedRowRef) as T[];
|
||||
}
|
||||
|
||||
function getRowSelection() {
|
||||
return unref(getRowSelectionRef)!;
|
||||
}
|
||||
|
||||
return {
|
||||
getRowSelection,
|
||||
getRowSelectionRef,
|
||||
getSelectRows,
|
||||
getSelectRowKeys,
|
||||
|
23
src/components/Table/src/hooks/useTableContext.ts
Normal file
23
src/components/Table/src/hooks/useTableContext.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { Ref } from 'vue';
|
||||
import type { BasicTableProps, TableActionType } from '../types/table';
|
||||
|
||||
import { provide, inject, ComputedRef } from 'vue';
|
||||
|
||||
const key = Symbol('basic-table');
|
||||
|
||||
type Instance = TableActionType & {
|
||||
wrapRef: Ref<Nullable<HTMLElement>>;
|
||||
getBindValues: ComputedRef<Recordable>;
|
||||
};
|
||||
|
||||
type RetInstance = Omit<Instance, 'getBindValues'> & {
|
||||
getBindValues: ComputedRef<BasicTableProps>;
|
||||
};
|
||||
|
||||
export function createTableContext(instance: Instance) {
|
||||
provide(key, instance);
|
||||
}
|
||||
|
||||
export function useTableContext(): RetInstance {
|
||||
return inject(key) as RetInstance;
|
||||
}
|
57
src/components/Table/src/hooks/useTableFooter.ts
Normal file
57
src/components/Table/src/hooks/useTableFooter.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import type { ComputedRef, Ref } from 'vue';
|
||||
import type { BasicTableProps } from '../types/table';
|
||||
import { unref, computed, h, nextTick, watchEffect } from 'vue';
|
||||
import TableFooter from '../components/TableFooter.vue';
|
||||
import { useEventListener } from '/@/hooks/event/useEventListener';
|
||||
|
||||
export function useTableFooter(
|
||||
propsRef: ComputedRef<BasicTableProps>,
|
||||
scrollRef: ComputedRef<{
|
||||
x: string | number | true;
|
||||
y: Nullable<number>;
|
||||
scrollToFirstRowOnChange: boolean;
|
||||
}>,
|
||||
tableElRef: Ref<ComponentRef>,
|
||||
getDataSourceRef: ComputedRef<Recordable>
|
||||
) {
|
||||
const getIsEmptyData = computed(() => {
|
||||
return (unref(getDataSourceRef) || []).length === 0;
|
||||
});
|
||||
|
||||
const getFooterProps = computed((): Recordable | undefined => {
|
||||
const { summaryFunc, showSummary } = unref(propsRef);
|
||||
return showSummary && !unref(getIsEmptyData)
|
||||
? () => h(TableFooter, { summaryFunc, scroll: unref(scrollRef) })
|
||||
: undefined;
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
handleSummary();
|
||||
});
|
||||
|
||||
function handleSummary() {
|
||||
const { showSummary } = unref(propsRef);
|
||||
if (!showSummary || unref(getIsEmptyData)) return;
|
||||
|
||||
nextTick(() => {
|
||||
const tableEl = unref(tableElRef);
|
||||
if (!tableEl) return;
|
||||
const bodyDomList = tableEl.$el.querySelectorAll('.ant-table-body');
|
||||
const bodyDom = bodyDomList[0];
|
||||
useEventListener({
|
||||
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,
|
||||
});
|
||||
});
|
||||
}
|
||||
return { getFooterProps };
|
||||
}
|
46
src/components/Table/src/hooks/useTableForm.ts
Normal file
46
src/components/Table/src/hooks/useTableForm.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import type { ComputedRef, Slots } from 'vue';
|
||||
import type { BasicTableProps, FetchParams } from '../types/table';
|
||||
import { unref, computed } from 'vue';
|
||||
import type { FormProps } from '/@/components/Form';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
export function useTableForm(
|
||||
propsRef: ComputedRef<BasicTableProps>,
|
||||
slots: Slots,
|
||||
fetch: (opt?: FetchParams | undefined) => Promise<void>
|
||||
) {
|
||||
const getFormProps = computed(
|
||||
(): Partial<FormProps> => {
|
||||
const { formConfig } = unref(propsRef);
|
||||
return {
|
||||
showAdvancedButton: true,
|
||||
...formConfig,
|
||||
compact: true,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const getFormSlotKeys = computed(() => {
|
||||
const keys = Object.keys(slots);
|
||||
return keys.map((item) => (item.startsWith('form-') ? item : null)).filter(Boolean);
|
||||
});
|
||||
|
||||
function replaceFormSlotKey(key: string) {
|
||||
if (!key) return '';
|
||||
return key?.replace?.(/form\-/, '') ?? '';
|
||||
}
|
||||
|
||||
function handleSearchInfoChange(info: Recordable) {
|
||||
const { handleSearchInfoFn } = unref(propsRef);
|
||||
if (handleSearchInfoFn && isFunction(handleSearchInfoFn)) {
|
||||
info = handleSearchInfoFn(info) || info;
|
||||
}
|
||||
fetch({ searchInfo: info, page: 1 });
|
||||
}
|
||||
|
||||
return {
|
||||
getFormProps,
|
||||
replaceFormSlotKey,
|
||||
getFormSlotKeys,
|
||||
handleSearchInfoChange,
|
||||
};
|
||||
}
|
46
src/components/Table/src/hooks/useTableHeader.ts
Normal file
46
src/components/Table/src/hooks/useTableHeader.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import type { ComputedRef, Slots } from 'vue';
|
||||
import type { BasicTableProps } from '../types/table';
|
||||
import { unref, computed, h } from 'vue';
|
||||
import { isString } from '/@/utils/is';
|
||||
import TableHeader from '../components/TableHeader.vue';
|
||||
import { getSlot } from '../../../../utils/helper/tsxHelper';
|
||||
|
||||
export function useTableHeader(propsRef: ComputedRef<BasicTableProps>, slots: Slots) {
|
||||
const getHeaderProps = computed(
|
||||
(): Recordable => {
|
||||
const { title, showTableSetting, titleHelpMessage, tableSetting } = unref(propsRef);
|
||||
const hideTitle = !slots.tableTitle && !title && !slots.toolbar && !showTableSetting;
|
||||
if (hideTitle && !isString(title)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
title: hideTitle
|
||||
? null
|
||||
: () =>
|
||||
h(
|
||||
TableHeader,
|
||||
{
|
||||
title,
|
||||
titleHelpMessage,
|
||||
showTableSetting,
|
||||
tableSetting,
|
||||
},
|
||||
{
|
||||
...(slots.toolbar
|
||||
? {
|
||||
toolbar: () => getSlot(slots, 'toolbar'),
|
||||
}
|
||||
: {}),
|
||||
...(slots.tableTitle
|
||||
? {
|
||||
tableTitle: () => getSlot(slots, 'tableTitle'),
|
||||
}
|
||||
: {}),
|
||||
}
|
||||
),
|
||||
};
|
||||
}
|
||||
);
|
||||
return { getHeaderProps };
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
import type { BasicTableProps } from '../types/table';
|
||||
import type { BasicTableProps, TableRowSelection } from '../types/table';
|
||||
import type { Ref, ComputedRef } from 'vue';
|
||||
import { computed, unref, ref, nextTick, watchEffect } from 'vue';
|
||||
|
||||
@@ -7,22 +7,29 @@ import { isBoolean } from '/@/utils/is';
|
||||
|
||||
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
|
||||
import { useModalContext } from '/@/components/Modal';
|
||||
import { useDebounce } from '/@/hooks/core/useDebounce';
|
||||
import type { BasicColumn } from '/@/components/Table';
|
||||
|
||||
export function useTableScroll(
|
||||
propsRef: ComputedRef<BasicTableProps>,
|
||||
tableElRef: Ref<ComponentRef>
|
||||
tableElRef: Ref<ComponentRef>,
|
||||
columnsRef: ComputedRef<BasicColumn[]>,
|
||||
rowSelectionRef: ComputedRef<TableRowSelection<any> | null>
|
||||
) {
|
||||
const tableHeightRef: Ref<Nullable<number>> = ref(null);
|
||||
|
||||
const modalFn = useModalContext();
|
||||
|
||||
// const [debounceCalcTableHeight] = useDebounce(calcTableHeight, 80);
|
||||
const [debounceRedoHeight] = useDebounce(redoHeight, 250);
|
||||
|
||||
const getCanResize = computed(() => {
|
||||
const { canResize, scroll } = unref(propsRef);
|
||||
return canResize && !(scroll || {}).y;
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
redoHeight();
|
||||
unref(getCanResize) && debounceRedoHeight();
|
||||
});
|
||||
|
||||
function redoHeight() {
|
||||
@@ -33,6 +40,12 @@ export function useTableScroll(
|
||||
}
|
||||
}
|
||||
|
||||
function setHeight(heigh: number) {
|
||||
tableHeightRef.value = heigh;
|
||||
// Solve the problem of modal adaptive height calculation when the form is placed in the modal
|
||||
modalFn?.redoModalHeight?.();
|
||||
}
|
||||
|
||||
// No need to repeat queries
|
||||
let paginationEl: HTMLElement | null;
|
||||
let footerEl: HTMLElement | null;
|
||||
@@ -87,7 +100,7 @@ export function useTableScroll(
|
||||
headerHeight = (headEl as HTMLElement).offsetHeight;
|
||||
}
|
||||
|
||||
const height =
|
||||
let height =
|
||||
bottomIncludeBody -
|
||||
(resizeHeightOffset || 0) -
|
||||
paddingHeight -
|
||||
@@ -96,21 +109,41 @@ export function useTableScroll(
|
||||
footerHeight -
|
||||
headerHeight;
|
||||
|
||||
setTimeout(() => {
|
||||
tableHeightRef.value = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
|
||||
// Solve the problem of modal adaptive height calculation when the form is placed in the modal
|
||||
modalFn?.redoModalHeight?.();
|
||||
}, 0);
|
||||
height = (height > maxHeight! ? (maxHeight as number) : height) ?? height;
|
||||
setHeight(height);
|
||||
}
|
||||
|
||||
useWindowSizeFn(calcTableHeight, 100);
|
||||
useWindowSizeFn(calcTableHeight, 200);
|
||||
|
||||
const getScrollX = computed(() => {
|
||||
let width = 0;
|
||||
if (unref(rowSelectionRef)) {
|
||||
width += 60;
|
||||
}
|
||||
|
||||
// TODO props
|
||||
const NORMAL_WIDTH = 150;
|
||||
|
||||
const columns = unref(columnsRef);
|
||||
|
||||
columns.forEach((item) => {
|
||||
width += Number.parseInt(item.width as string) || 0;
|
||||
});
|
||||
const unsetWidthColumns = columns.filter((item) => !Reflect.has(item, 'width'));
|
||||
|
||||
const len = unsetWidthColumns.length;
|
||||
if (len !== 0) {
|
||||
width += len * NORMAL_WIDTH;
|
||||
}
|
||||
return width;
|
||||
});
|
||||
|
||||
const getScrollRef = computed(() => {
|
||||
const tableHeight = unref(tableHeightRef);
|
||||
const { canResize, scroll } = unref(propsRef);
|
||||
|
||||
return {
|
||||
x: '100%',
|
||||
x: unref(getScrollX),
|
||||
y: canResize ? tableHeight : null,
|
||||
scrollToFirstRowOnChange: false,
|
||||
...scroll,
|
||||
|
@@ -2,14 +2,14 @@ import type { ComputedRef } from 'vue';
|
||||
import type { BasicTableProps, TableCustomRecord } from '../types/table';
|
||||
import { unref } from 'vue';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
export function useTableStyle(propsRef: ComputedRef<BasicTableProps>) {
|
||||
export function useTableStyle(propsRef: ComputedRef<BasicTableProps>, prefixCls: string) {
|
||||
function getRowClassName(record: TableCustomRecord, index: number) {
|
||||
const { striped, rowClassName } = unref(propsRef);
|
||||
if (!striped) return;
|
||||
if (rowClassName && isFunction(rowClassName)) {
|
||||
return rowClassName(record);
|
||||
}
|
||||
return (index || 0) % 2 === 1 ? 'basic-table-row__striped' : '';
|
||||
return (index || 0) % 2 === 1 ? `${prefixCls}-row__striped` : '';
|
||||
}
|
||||
|
||||
return {
|
||||
|
@@ -75,7 +75,7 @@ export const basicProps = {
|
||||
},
|
||||
columns: {
|
||||
type: [Array] as PropType<BasicColumn[]>,
|
||||
default: null,
|
||||
default: () => [],
|
||||
},
|
||||
showIndexColumn: propTypes.bool.def(true),
|
||||
indexColumnProps: {
|
||||
@@ -95,7 +95,7 @@ export const basicProps = {
|
||||
default: null,
|
||||
},
|
||||
title: {
|
||||
type: [String, Function] as PropType<string | ((data: any) => any)>,
|
||||
type: [String, Function] as PropType<string | ((data: Recordable) => string)>,
|
||||
default: null,
|
||||
},
|
||||
titleHelpMessage: {
|
||||
|
@@ -1,5 +1,3 @@
|
||||
@import (reference) '../../../../design/index.less';
|
||||
|
||||
@prefix-cls: ~'editable-cell';
|
||||
|
||||
.@{prefix-cls} {
|
||||
|
@@ -1,11 +1,21 @@
|
||||
@import (reference) '../../../../design/index.less';
|
||||
@border-color: #cecece4d;
|
||||
|
||||
.basic-table {
|
||||
&-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
@prefix-cls: ~'@{namespace}-basic-table';
|
||||
|
||||
.@{prefix-cls} {
|
||||
&-form-container {
|
||||
padding: 16px;
|
||||
|
||||
.ant-form {
|
||||
padding: 20px 20px 4px 12px;
|
||||
margin-bottom: 18px;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ant-table-wrapper {
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
&-row__striped {
|
||||
@@ -22,28 +32,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
&-action {
|
||||
display: flex;
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
&-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> * {
|
||||
margin-right: 10px;
|
||||
&--inset {
|
||||
.ant-table-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-table-wrapper {
|
||||
padding: 8px;
|
||||
background: #fff;
|
||||
border-radius: 2px;
|
||||
border-radius: 4px;
|
||||
|
||||
.ant-table-title {
|
||||
padding: 0 0 8px 0 !important;
|
||||
@@ -54,12 +52,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.inset {
|
||||
.ant-table-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
.ant-table {
|
||||
border: none;
|
||||
@@ -194,18 +186,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-form-container {
|
||||
padding: 16px;
|
||||
|
||||
.ant-form {
|
||||
padding: 20px 20px 4px 12px;
|
||||
margin-bottom: 18px;
|
||||
background: #fff;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.ant-table-wrapper {
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
@@ -82,6 +82,7 @@ export interface FetchParams {
|
||||
export interface GetColumnsParams {
|
||||
ignoreIndex?: boolean;
|
||||
ignoreAction?: boolean;
|
||||
sort?: boolean;
|
||||
}
|
||||
|
||||
export type SizeType = 'default' | 'middle' | 'small' | 'large';
|
||||
@@ -93,16 +94,18 @@ export interface TableActionType {
|
||||
getSelectRowKeys: () => string[];
|
||||
deleteSelectRowByKey: (key: string) => void;
|
||||
setPagination: (info: Partial<PaginationProps>) => void;
|
||||
setTableData: <T = any>(values: T[]) => void;
|
||||
setTableData: <T = Recordable>(values: T[]) => void;
|
||||
getColumns: (opt?: GetColumnsParams) => BasicColumn[];
|
||||
setColumns: (columns: BasicColumn[] | string[]) => void;
|
||||
getDataSource: <T = any>() => T[];
|
||||
getDataSource: <T = Recordable>() => T[];
|
||||
setLoading: (loading: boolean) => void;
|
||||
setProps: (props: Partial<BasicTableProps>) => void;
|
||||
redoHeight: () => void;
|
||||
setSelectedRowKeys: (rowKeys: string[] | number[]) => void;
|
||||
getPaginationRef: () => PaginationProps | boolean;
|
||||
getSize: () => SizeType;
|
||||
getRowSelection: () => TableRowSelection<Recordable>;
|
||||
getCacheColumns: () => BasicColumn[];
|
||||
}
|
||||
|
||||
export interface FetchSetting {
|
||||
@@ -308,7 +311,7 @@ export interface BasicTableProps<T = any> {
|
||||
* Table title renderer
|
||||
* @type Function | ScopedSlot
|
||||
*/
|
||||
title?: VNodeChild | JSX.Element;
|
||||
title?: VNodeChild | JSX.Element | string | ((data: Recordable) => string);
|
||||
|
||||
/**
|
||||
* Set props on per header row
|
||||
@@ -378,4 +381,6 @@ export interface BasicColumn extends ColumnProps {
|
||||
flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION';
|
||||
|
||||
slots?: Indexable;
|
||||
|
||||
defaultHidden?: boolean;
|
||||
}
|
||||
|
@@ -5,6 +5,8 @@ export interface ActionItem extends ButtonProps {
|
||||
color?: 'success' | 'error' | 'warning';
|
||||
icon?: string;
|
||||
popConfirm?: PopConfirm;
|
||||
disabled?: boolean;
|
||||
divider?: boolean;
|
||||
}
|
||||
|
||||
export interface PopConfirm {
|
||||
|
Reference in New Issue
Block a user