提交 9c2f3f30 作者: vben

refactor(table): refactor table #150 #148 #146 #130 #76

上级 f3a70eed
## Wip ## Wip
### ✨ 表格破坏性更新
- 重构了可编辑单元格及可编辑行。具体看示例。写法已改变。针对可编辑表格。
- 表格编辑支持表单校验
- 在表格列配置增加了以下配置
```bash
{
# 默认是否显示列。不显示的可以在列配置打开
defaultHidden?: boolean;
# 列头右侧帮助文本
helpMessage?: string | string[];
# 自定义格式化 单元格内容。 支持时间/枚举自动转化
format?: CellFormat;
# Editable
# 是否是可编辑单元格
edit?: boolean;
# 是否是可编辑行
editRow?: boolean;
# 编辑状态。
editable?: boolean;
# 编辑组件
editComponent?: ComponentType;
# 所对应组件的参数
editComponentProps?: Recordable;
# 校验
editRule?: boolean | ((text: string, record: Recordable) => Promise<string>);
# 值枚举转化
editValueMap?: (value: any) => string;
# 触发编辑正航
record.onEditRow?: () => void;
}
```
### ✨ 表格重构
- 新增`clickToRowSelect`属性。用于控制点击行是否选中勾选框
- 监听行点击事件
- 表格列配置按钮增加 列拖拽,列固定功能。
- 表格列配置新增`defaultHidden` 属性。用于默认隐藏。可在表格列配置勾选显示
- 更强大的列配置
- useTable:支持动态改变参数。可以传入`Ref`类型与`Computed`类型进行动态更改
- useTable:新增返回 `getForm`函数。可以用于操作表格内的表单
- 修复表格已知的问题
### ✨ Features ### ✨ Features
- 新增 `v-ripple`水波纹指令 - 新增 `v-ripple`水波纹指令
...@@ -12,14 +62,6 @@ ...@@ -12,14 +62,6 @@
- form: 新增远程下拉`ApiSelect`及示例 - form: 新增远程下拉`ApiSelect`及示例
- form: 新增`autoFocusFirstItem`配置。用于配置是否聚焦表单第一个输入框 - form: 新增`autoFocusFirstItem`配置。用于配置是否聚焦表单第一个输入框
- useForm: 支持动态改变参数。可以传入`Ref`类型与`Computed`类型进行动态更改 - useForm: 支持动态改变参数。可以传入`Ref`类型与`Computed`类型进行动态更改
- table: 新增`clickToRowSelect`属性。用于控制点击行是否选中勾选狂
- table: 监听行点击事件
- table: 表格列配置按钮增加 列拖拽,列固定功能。
- table:表格列配置新增`defaultHidden` 属性。用于默认隐藏。可在表格列配置勾选显示
### ✨ Refactor
- 重构表单,解决已知 bug
### ⚡ Performance Improvements ### ⚡ Performance Improvements
...@@ -30,6 +72,7 @@ ...@@ -30,6 +72,7 @@
### 🎫 Chores ### 🎫 Chores
- 升级`ant-design-vue``2.0.0-rc.7` - 升级`ant-design-vue``2.0.0-rc.7`
- 升级`vue``3.0.5`
### 🐛 Bug Fixes ### 🐛 Bug Fixes
......
...@@ -10,6 +10,14 @@ const demoList = (() => { ...@@ -10,6 +10,14 @@ const demoList = (() => {
endTime: '@datetime', endTime: '@datetime',
address: '@city()', address: '@city()',
name: '@cname()', name: '@cname()',
name1: '@cname()',
name2: '@cname()',
name3: '@cname()',
name4: '@cname()',
name5: '@cname()',
name6: '@cname()',
name7: '@cname()',
name8: '@cname()',
'no|100000-10000000': 100000, 'no|100000-10000000': 100000,
'status|1': ['normal', 'enable', 'disable'], 'status|1': ['normal', 'enable', 'disable'],
}); });
......
...@@ -9,4 +9,7 @@ export * from './src/types/formItem'; ...@@ -9,4 +9,7 @@ export * from './src/types/formItem';
export { useComponentRegister } from './src/hooks/useComponentRegister'; export { useComponentRegister } from './src/hooks/useComponentRegister';
export { useForm } from './src/hooks/useForm'; export { useForm } from './src/hooks/useForm';
export { default as ApiSelect } from './src/components/ApiSelect.vue';
export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue';
export { BasicForm }; export { BasicForm };
...@@ -50,7 +50,8 @@ ...@@ -50,7 +50,8 @@
labelField: propTypes.string.def('label'), labelField: propTypes.string.def('label'),
valueField: propTypes.string.def('value'), valueField: propTypes.string.def('value'),
}, },
setup(props) { emits: ['options-change', 'change'],
setup(props, { emit }) {
const options = ref<OptionsItem[]>([]); const options = ref<OptionsItem[]>([]);
const loading = ref(false); const loading = ref(false);
const attrs = useAttrs(); const attrs = useAttrs();
...@@ -86,11 +87,13 @@ ...@@ -86,11 +87,13 @@
const res = await api(props.params); const res = await api(props.params);
if (Array.isArray(res)) { if (Array.isArray(res)) {
options.value = res; options.value = res;
emit('options-change', unref(options));
return; return;
} }
if (props.resultField) { if (props.resultField) {
options.value = get(res, props.resultField) || []; options.value = get(res, props.resultField) || [];
} }
emit('options-change', unref(options));
} catch (error) { } catch (error) {
console.warn(error); console.warn(error);
} finally { } finally {
......
...@@ -15,7 +15,7 @@ export function useOpenKeys( ...@@ -15,7 +15,7 @@ export function useOpenKeys(
mode: Ref<MenuModeEnum>, mode: Ref<MenuModeEnum>,
accordion: Ref<boolean> accordion: Ref<boolean>
) { ) {
const { getCollapsed } = useMenuSetting(); const { getCollapsed, getIsMixSidebar } = useMenuSetting();
function setOpenKeys(path: string) { function setOpenKeys(path: string) {
if (mode.value === MenuModeEnum.HORIZONTAL) { if (mode.value === MenuModeEnum.HORIZONTAL) {
...@@ -30,7 +30,9 @@ export function useOpenKeys( ...@@ -30,7 +30,9 @@ export function useOpenKeys(
} }
const getOpenKeys = computed(() => { const getOpenKeys = computed(() => {
return unref(getCollapsed) ? menuState.collapsedOpenKeys : menuState.openKeys; const collapse = unref(getIsMixSidebar) ? false : unref(getCollapsed);
return collapse ? menuState.collapsedOpenKeys : menuState.openKeys;
}); });
/** /**
...@@ -42,7 +44,7 @@ export function useOpenKeys( ...@@ -42,7 +44,7 @@ export function useOpenKeys(
} }
function handleOpenChange(openKeys: string[]) { function handleOpenChange(openKeys: string[]) {
if (unref(mode) === MenuModeEnum.HORIZONTAL || !unref(accordion)) { if (unref(mode) === MenuModeEnum.HORIZONTAL || !unref(accordion) || unref(getIsMixSidebar)) {
menuState.openKeys = openKeys; menuState.openKeys = openKeys;
} else { } else {
// const menuList = toRaw(menus.value); // const menuList = toRaw(menus.value);
......
...@@ -3,7 +3,6 @@ import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; ...@@ -3,7 +3,6 @@ import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
export { default as BasicTable } from './src/BasicTable.vue'; export { default as BasicTable } from './src/BasicTable.vue';
export { default as TableAction } from './src/components/TableAction.vue'; export { default as TableAction } from './src/components/TableAction.vue';
// export { default as TableImg } from './src/components/TableImg.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 { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue';
export const TableImg = createAsyncComponent(() => import('./src/components/TableImg.vue')); export const TableImg = createAsyncComponent(() => import('./src/components/TableImg.vue'));
...@@ -17,4 +16,4 @@ export { useTable } from './src/hooks/useTable'; ...@@ -17,4 +16,4 @@ export { useTable } from './src/hooks/useTable';
export type { FormSchema, FormProps } from '/@/components/Form/src/types/form'; export type { FormSchema, FormProps } from '/@/components/Form/src/types/form';
export type { EditRecordRow } from './src/components/renderEditable'; export type { EditRecordRow } from './src/components/editable';
...@@ -34,19 +34,19 @@ ...@@ -34,19 +34,19 @@
<template #[item]="data" v-for="item in Object.keys($slots)"> <template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data" /> <slot :name="item" v-bind="data" />
</template> </template>
<template #[`header-${column.dataIndex}`] v-for="column in columns" :key="column.dataIndex">
<HeaderCell :column="column" />
</template>
</Table> </Table>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { BasicTableProps, TableActionType, SizeType, SorterResult } from './types/table'; import type { BasicTableProps, TableActionType, SizeType } from './types/table';
import { PaginationProps } from './types/pagination';
import { defineComponent, ref, computed, unref } from 'vue'; import { defineComponent, ref, computed, unref } from 'vue';
import { Table } from 'ant-design-vue'; import { Table } from 'ant-design-vue';
import { BasicForm, useForm } from '/@/components/Form/index'; import { BasicForm, useForm } from '/@/components/Form/index';
import { isFunction } from '/@/utils/is';
import { omit } from 'lodash-es'; import { omit } from 'lodash-es';
import { usePagination } from './hooks/usePagination'; import { usePagination } from './hooks/usePagination';
...@@ -61,15 +61,20 @@ ...@@ -61,15 +61,20 @@
import { createTableContext } from './hooks/useTableContext'; import { createTableContext } from './hooks/useTableContext';
import { useTableFooter } from './hooks/useTableFooter'; import { useTableFooter } from './hooks/useTableFooter';
import { useTableForm } from './hooks/useTableForm'; import { useTableForm } from './hooks/useTableForm';
import { useExpose } from '/@/hooks/core/useExpose';
import { useDesign } from '/@/hooks/web/useDesign';
import { basicProps } from './props'; import { basicProps } from './props';
import { useExpose } from '/@/hooks/core/useExpose'; import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
import './style/index.less'; import './style/index.less';
import { useDesign } from '/@/hooks/web/useDesign';
export default defineComponent({ export default defineComponent({
props: basicProps, props: basicProps,
components: { Table, BasicForm }, components: {
Table,
BasicForm,
HeaderCell: createAsyncComponent(() => import('./components/HeaderCell.vue')),
},
emits: [ emits: [
'fetch-success', 'fetch-success',
'fetch-error', 'fetch-error',
...@@ -80,6 +85,8 @@ ...@@ -80,6 +85,8 @@
'row-contextmenu', 'row-contextmenu',
'row-mouseenter', 'row-mouseenter',
'row-mouseleave', 'row-mouseleave',
'edit-end',
'edit-cancel',
], ],
setup(props, { attrs, emit, slots }) { setup(props, { attrs, emit, slots }) {
const tableElRef = ref<ComponentRef>(null); const tableElRef = ref<ComponentRef>(null);
...@@ -96,15 +103,19 @@ ...@@ -96,15 +103,19 @@
const { getLoading, setLoading } = useLoading(getProps); const { getLoading, setLoading } = useLoading(getProps);
const { getPaginationInfo, getPagination, setPagination } = usePagination(getProps); const { getPaginationInfo, getPagination, setPagination } = usePagination(getProps);
const { const {
getSortFixedColumns, getRowSelection,
getColumns, getRowSelectionRef,
setColumns, getSelectRows,
getColumnsRef, clearSelectedRowKeys,
getCacheColumns, getSelectRowKeys,
} = useColumns(getProps, getPaginationInfo); deleteSelectRowByKey,
setSelectedRowKeys,
} = useRowSelection(getProps, emit);
const { const {
handleTableChange,
getDataSourceRef, getDataSourceRef,
getDataSource, getDataSource,
setTableData, setTableData,
...@@ -112,6 +123,7 @@ ...@@ -112,6 +123,7 @@
getRowKey, getRowKey,
reload, reload,
getAutoCreateKey, getAutoCreateKey,
updateTableData,
} = useDataSource( } = useDataSource(
getProps, getProps,
{ {
...@@ -119,19 +131,15 @@ ...@@ -119,19 +131,15 @@
setLoading, setLoading,
setPagination, setPagination,
getFieldsValue: formActions.getFieldsValue, getFieldsValue: formActions.getFieldsValue,
clearSelectedRowKeys,
}, },
emit emit
); );
const { const { getViewColumns, getColumns, setColumns, getColumnsRef, getCacheColumns } = useColumns(
getRowSelection, getProps,
getRowSelectionRef, getPaginationInfo
getSelectRows, );
clearSelectedRowKeys,
getSelectRowKeys,
deleteSelectRowByKey,
setSelectedRowKeys,
} = useRowSelection(getProps, emit);
const { getScrollRef, redoHeight } = useTableScroll( const { getScrollRef, redoHeight } = useTableScroll(
getProps, getProps,
...@@ -178,7 +186,7 @@ ...@@ -178,7 +186,7 @@
tableLayout: 'fixed', tableLayout: 'fixed',
rowSelection: unref(getRowSelectionRef), rowSelection: unref(getRowSelectionRef),
rowKey: unref(getRowKey), rowKey: unref(getRowKey),
columns: unref(getSortFixedColumns), columns: unref(getViewColumns),
pagination: unref(getPaginationInfo), pagination: unref(getPaginationInfo),
dataSource: unref(getDataSourceRef), dataSource: unref(getDataSourceRef),
footer: unref(getFooterProps), footer: unref(getFooterProps),
...@@ -197,26 +205,6 @@ ...@@ -197,26 +205,6 @@
return !!unref(getDataSourceRef).length; return !!unref(getDataSourceRef).length;
}); });
function handleTableChange(
pagination: PaginationProps,
// @ts-ignore
filters: Partial<Recordable<string[]>>,
sorter: SorterResult
) {
const { clearSelectOnPageChange, sortFn } = unref(getProps);
if (clearSelectOnPageChange) {
clearSelectedRowKeys();
}
setPagination(pagination);
if (sorter && isFunction(sortFn)) {
const sortInfo = sortFn(sorter);
fetch({ sortInfo });
return;
}
fetch();
}
function setProps(props: Partial<BasicTableProps>) { function setProps(props: Partial<BasicTableProps>) {
innerPropsRef.value = { ...unref(innerPropsRef), ...props }; innerPropsRef.value = { ...unref(innerPropsRef), ...props };
} }
...@@ -239,6 +227,8 @@ ...@@ -239,6 +227,8 @@
getPaginationRef: getPagination, getPaginationRef: getPagination,
getColumns, getColumns,
getCacheColumns, getCacheColumns,
emit,
updateTableData,
getSize: () => { getSize: () => {
return unref(getBindValues).size as SizeType; return unref(getBindValues).size as SizeType;
}, },
...@@ -265,6 +255,7 @@ ...@@ -265,6 +255,7 @@
replaceFormSlotKey, replaceFormSlotKey,
getFormSlotKeys, getFormSlotKeys,
prefixCls, prefixCls,
columns: getViewColumns,
}; };
}, },
}); });
......
import { Component } from 'vue'; import type { Component } from 'vue';
import { Input, Select, Checkbox, InputNumber, Switch } from 'ant-design-vue'; import { Input, Select, Checkbox, InputNumber, Switch } from 'ant-design-vue';
import { ComponentType } from './types/componentType'; import type { ComponentType } from './types/componentType';
import { ApiSelect } from '/@/components/Form';
const componentMap = new Map<ComponentType, Component>(); const componentMap = new Map<ComponentType, Component>();
componentMap.set('Input', Input); componentMap.set('Input', Input);
componentMap.set('InputPassword', Input.Password);
componentMap.set('InputNumber', InputNumber); componentMap.set('InputNumber', InputNumber);
componentMap.set('Select', Select); componentMap.set('Select', Select);
componentMap.set('ApiSelect', ApiSelect);
componentMap.set('Switch', Switch); componentMap.set('Switch', Switch);
componentMap.set('Checkbox', Checkbox); componentMap.set('Checkbox', Checkbox);
componentMap.set('CheckboxGroup', Checkbox.Group);
export function add(compName: ComponentType, component: Component) { export function add(compName: ComponentType, component: Component) {
componentMap.set(compName, component); componentMap.set(compName, component);
......
<template> <template>
<span> <span>
<slot />
{{ title }} {{ title }}
<FormOutlined class="ml-2" /> <FormOutlined />
</span> </span>
</template> </template>
<script lang="ts"> <script lang="ts">
......
<template>
<EditTableHeaderCell v-if="getIsEdit">
{{ getTitle }}
</EditTableHeaderCell>
<span v-else>{{ getTitle }}</span>
<BasicHelp v-if="getHelpMessage" :text="getHelpMessage" :class="`${prefixCls}__help`" />
</template>
<script lang="ts">
import type { PropType } from 'vue';
import type { BasicColumn } from '../types/table';
import { defineComponent, computed } from 'vue';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
import { useDesign } from '/@/hooks/web/useDesign';
export default defineComponent({
name: 'TableHeaderCell',
components: {
EditTableHeaderCell: createAsyncComponent(() => import('./EditTableHeaderIcon.vue')),
BasicHelp: createAsyncComponent(() => import('/@/components/Basic/src/BasicHelp.vue')),
},
props: {
column: {
type: Object as PropType<BasicColumn>,
default: {},
},
},
setup(props) {
const { prefixCls } = useDesign('basic-table-header-cell');
const getIsEdit = computed(() => {
return !!props.column?.edit;
});
const getTitle = computed(() => {
return props.column?.customTitle;
});
const getHelpMessage = computed(() => {
return props.column?.helpMessage;
});
return { prefixCls, getIsEdit, getTitle, getHelpMessage };
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-table-header-cell';
.@{prefix-cls} {
&__help {
margin-left: 8px;
color: rgba(0, 0, 0, 0.65) !important;
}
}
</style>
<template> <template>
<div :class="[prefixCls, getAlign]"> <div :class="[prefixCls, getAlign]">
<template v-for="(action, index) in getActions" :key="`${index}`"> <template v-for="(action, index) in getActions" :key="`${index}-${action.label}`">
<PopConfirmButton v-bind="action"> <PopConfirmButton v-bind="action">
<Icon :icon="action.icon" class="mr-1" v-if="action.icon" /> <Icon :icon="action.icon" class="mr-1" v-if="action.icon" />
{{ action.label }} {{ action.label }}
</PopConfirmButton> </PopConfirmButton>
<Divider type="vertical" v-if="divider && index < getActions.length" /> <Divider type="vertical" v-if="divider && index < getActions.length" />
</template> </template>
<Dropdown :trigger="['hover']" :dropMenuList="getDropList" v-if="dropDownActions">
<Dropdown :trigger="['hover']" :dropMenuList="getDropList">
<slot name="more" /> <slot name="more" />
<a-button type="link" size="small" v-if="!$slots.more"> <a-button type="link" size="small" v-if="!$slots.more">
<MoreOutlined class="icon-more" /> <MoreOutlined class="icon-more" />
...@@ -61,7 +60,7 @@ ...@@ -61,7 +60,7 @@
}); });
const getDropList = computed(() => { const getDropList = computed(() => {
return props.dropDownActions.map((action, index) => { return (props.dropDownActions || []).map((action, index) => {
const { label } = action; const { label } = action;
return { return {
...action, ...action,
......
import type { FunctionalComponent, defineComponent } from 'vue';
import type { ComponentType } from '../../types/componentType';
import { componentMap } from '/@/components/Table/src/componentMap';
import { Popover } from 'ant-design-vue';
import { h } from 'vue';
export interface ComponentProps {
component: ComponentType;
rule: boolean;
popoverVisible: boolean;
ruleMessage: string;
}
export const CellComponent: FunctionalComponent = (
{ component = 'Input', rule = true, ruleMessage, popoverVisible }: ComponentProps,
{ attrs }
) => {
const Comp = componentMap.get(component) as typeof defineComponent;
const DefaultComp = h(Comp, attrs);
if (!rule) {
return DefaultComp;
}
return h(
Popover,
{ overlayClassName: 'edit-cell-rule-popover', visible: !!popoverVisible },
{
default: () => DefaultComp,
content: () => ruleMessage,
}
);
};
import { ComponentType } from '../../types/componentType';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
/**
* @description: 生成placeholder
*/
export function createPlaceholderMessage(component: ComponentType) {
if (component.includes('Input')) {
return t('component.form.input');
}
if (component.includes('Picker')) {
return t('component.form.choose');
}
if (
component.includes('Select') ||
component.includes('Checkbox') ||
component.includes('Radio') ||
component.includes('Switch')
) {
return t('component.form.choose');
}
return '';
}
import type { BasicColumn } from '/@/components/Table/src/types/table';
import { h } from 'vue';
import EditableCell from './EditableCell.vue';
interface Params {
text: string;
record: Recordable;
index: number;
}
export function renderEditCell(column: BasicColumn) {
return ({ text: value, record, index }: Params) => {
record.onEdit = async (edit: boolean, submit = false) => {
if (!submit) {
record.editable = edit;
}
if (!edit && submit) {
const res = await record.onSubmitEdit?.();
if (res) {
record.editable = false;
return true;
}
return false;
}
// cancel
if (!edit && !submit) {
record.onCancelEdit?.();
}
return true;
};
return h(EditableCell, {
value,
record,
column,
index,
});
};
}
export type EditRecordRow<T = Hash<any>> = {
onEdit: (editable: boolean, submit?: boolean) => Promise<boolean>;
editable: boolean;
onCancel: Fn;
onSubmit: Fn;
submitCbs: Fn[];
cancelCbs: Fn[];
validCbs: Fn[];
} & T;
import '../style/editable-cell.less';
import { defineComponent, PropType, ref, unref, nextTick, watchEffect } from 'vue';
import { ClickOutSide } from '/@/components/ClickOutSide';
import { RenderEditableCellParams } from '../types/table';
import { ComponentType } from '../types/componentType';
import { componentMap } from '../componentMap';
import { isString, isBoolean, isArray } 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',
},
editable: {
type: Boolean as PropType<boolean>,
default: false,
},
editRow: {
type: Boolean as PropType<boolean>,
default: false,
},
record: {
type: Object as PropType<EditRecordRow>,
},
placeholder: {
type: String as PropType<string>,
default: '',
},
},
emits: ['submit', 'cancel'],
setup(props, { attrs, emit }) {
const elRef = ref<any>(null);
const isEditRef = ref(false);
const currentValueRef = ref<string | boolean>(props.value);
const defaultValueRef = ref<string | boolean>(props.value);
watchEffect(() => {
defaultValueRef.value = props.value;
if (isBoolean(props.editable)) {
isEditRef.value = props.editable;
}
});
function handleChange(e: any) {
if (e && e.target && Reflect.has(e.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();
});
}
function handleCancel() {
isEditRef.value = false;
currentValueRef.value = defaultValueRef.value;
emit('cancel');
}
if (props.record) {
/* eslint-disable */
isArray(props.record.submitCbs)
? props.record.submitCbs.push(handleSubmit)
: (props.record.submitCbs = [handleSubmit]);
/* eslint-disable */
isArray(props.record.cancelCbs)
? props.record.cancelCbs.push(handleCancel)
: (props.record.cancelCbs = [handleCancel]);
/* eslint-disable */
props.record.onCancel = () => {
isArray(props.record?.cancelCbs) && props.record?.cancelCbs.forEach((fn) => fn());
};
/* eslint-disable */
props.record.onSubmit = () => {
isArray(props.record?.submitCbs) && props.record?.submitCbs.forEach((fn) => fn());
};
}
function handleSubmit() {
const { dataKey, dataIndex } = props;
if (!dataKey || !dataIndex) return;
if (props.record) {
/* eslint-disable */
props.record[dataIndex] = unref(currentValueRef) as string;
}
isEditRef.value = false;
}
function onClickOutside() {
if (props.editRow) return;
const { component } = props;
if (component && component.includes('Input')) {
handleCancel();
}
}
function renderValue() {
const { value } = props;
if (props.editRow) {
return !unref(isEditRef) ? value : null;
}
return (
!unref(isEditRef) && (
<div class={`${prefixCls}__normal`} onClick={handleEdit}>
{value}
<FormOutlined class={`${prefixCls}__normal-icon`} />
</div>
)
);
}
return () => {
const { component, componentProps = {} } = props;
const Comp = componentMap.get(component!) as any;
return (
<div class={prefixCls}>
{unref(isEditRef) && (
<ClickOutSide onClickOutside={onClickOutside}>
{() => (
<div class={`${prefixCls}__wrapper`}>
<Comp
placeholder={props.placeholder}
{...{
...attrs,
...componentProps,
}}
style={{ width: 'calc(100% - 48px)' }}
ref={elRef}
value={unref(currentValueRef)}
size="small"
onChange={handleChange}
onPressEnter={handleSubmit}
/>
{!props.editRow && (
<div class={`${prefixCls}__action`}>
<CheckOutlined
class={[`${prefixCls}__icon`, 'mx-2']}
onClick={handleSubmit}
/>
<CloseOutlined class={[`${prefixCls}__icon `]} onClick={handleCancel} />
</div>
)}
</div>
)}
</ClickOutSide>
)}
{renderValue()}
</div>
);
};
},
});
export function renderEditableCell({
dataIndex,
component,
componentProps = {},
placeholder,
}: RenderEditableCellParams) {
return ({ text, record }: { text: string; record: EditRecordRow }) => {
return (
<EditableCell
{...componentProps}
placeholder={placeholder}
value={text}
record={record}
dataKey={record.key}
dataIndex={dataIndex}
component={component}
/>
);
};
}
export function renderEditableRow({
dataIndex,
component,
componentProps = {},
placeholder,
}: RenderEditableCellParams) {
return ({ text, record }: { text: string; record: EditRecordRow }) => {
return (
<EditableCell
{...componentProps}
value={text}
placeholder={placeholder}
editRow={true}
editable={record.editable}
dataKey={record.key}
record={record}
dataIndex={dataIndex}
component={component}
/>
);
};
}
export type EditRecordRow<T = Hash<any>> = {
editable: boolean;
onCancel: Fn;
onSubmit: Fn;
submitCbs: Fn[];
cancelCbs: Fn[];
} & T;
import { BasicArrow } from '/@/components/Basic';
export default () => {
return (props: Recordable) => {
return (
<BasicArrow
onClick={(e: Event) => {
props.onExpand(props.record, e);
}}
expand={props.expanded}
/>
);
};
};
...@@ -184,7 +184,7 @@ ...@@ -184,7 +184,7 @@
const ret: Options[] = []; const ret: Options[] = [];
table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => { table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => {
ret.push({ ret.push({
label: item.title as string, label: (item.title as string) || (item.customTitle as string),
value: (item.dataIndex || item.title) as string, value: (item.dataIndex || item.title) as string,
...item, ...item,
}); });
......
...@@ -32,6 +32,10 @@ export function DEFAULT_SORT_FN(sortInfo: SorterResult) { ...@@ -32,6 +32,10 @@ export function DEFAULT_SORT_FN(sortInfo: SorterResult) {
}; };
} }
export function DEFAULT_FILTER_FN(data: Partial<Recordable<string[]>>) {
return data;
}
// 表格单元格默认布局 // 表格单元格默认布局
export const DEFAULT_ALIGN = 'center'; export const DEFAULT_ALIGN = 'center';
......
import { BasicColumn, BasicTableProps, GetColumnsParams } from '../types/table'; import type { BasicColumn, BasicTableProps, CellFormat, GetColumnsParams } from '../types/table';
import { PaginationProps } from '../types/pagination'; import type { PaginationProps } from '../types/pagination';
import { unref, ComputedRef, Ref, computed, watchEffect, ref, toRaw } from 'vue'; import { unref, ComputedRef, Ref, computed, watchEffect, ref, toRaw } from 'vue';
import { isBoolean, isArray, isString } from '/@/utils/is'; import { isBoolean, isArray, isString, isObject } from '/@/utils/is';
import { DEFAULT_ALIGN, PAGE_SIZE, INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG } from '../const'; import { DEFAULT_ALIGN, PAGE_SIZE, INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG } from '../const';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import { isEqual, cloneDeep } from 'lodash-es'; import { isEqual, cloneDeep } from 'lodash-es';
import { isFunction } from '/@/utils/is';
import { formatToDate } from '/@/utils/dateUtil';
import { renderEditCell } from '../components/editable';
const { t } = useI18n(); const { t } = useI18n();
...@@ -127,8 +130,30 @@ export function useColumns( ...@@ -127,8 +130,30 @@ export function useColumns(
return columns; return columns;
}); });
const getSortFixedColumns = computed(() => { const getViewColumns = computed(() => {
return useFixedColumn(unref(getColumnsRef)); const viewColumns = sortFixedColumn(unref(getColumnsRef));
viewColumns.forEach((column) => {
const { slots, dataIndex, customRender, format, edit, editRow, flag } = column;
if (!slots || !slots?.title) {
column.slots = { title: `header-${dataIndex}`, ...(slots || {}) };
column.customTitle = column.title;
Reflect.deleteProperty(column, 'title');
}
const isDefaultAction = [INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG].includes(flag!);
if (!customRender && format && !edit && !isDefaultAction) {
column.customRender = ({ text, record, index }) => {
return formatCell(text, format, record, index);
};
}
// edit table
if ((edit || editRow) && !isDefaultAction) {
column.customRender = renderEditCell(column);
}
});
return viewColumns;
}); });
watchEffect(() => { watchEffect(() => {
...@@ -191,7 +216,7 @@ export function useColumns( ...@@ -191,7 +216,7 @@ export function useColumns(
} }
if (sort) { if (sort) {
columns = useFixedColumn(columns); columns = sortFixedColumn(columns);
} }
return columns; return columns;
...@@ -200,10 +225,10 @@ export function useColumns( ...@@ -200,10 +225,10 @@ export function useColumns(
return cacheColumns; return cacheColumns;
} }
return { getColumnsRef, getCacheColumns, getColumns, setColumns, getSortFixedColumns }; return { getColumnsRef, getCacheColumns, getColumns, setColumns, getViewColumns };
} }
export function useFixedColumn(columns: BasicColumn[]) { function sortFixedColumn(columns: BasicColumn[]) {
const fixedLeftColumns: BasicColumn[] = []; const fixedLeftColumns: BasicColumn[] = [];
const fixedRightColumns: BasicColumn[] = []; const fixedRightColumns: BasicColumn[] = [];
const defColumns: BasicColumn[] = []; const defColumns: BasicColumn[] = [];
...@@ -224,3 +249,35 @@ export function useFixedColumn(columns: BasicColumn[]) { ...@@ -224,3 +249,35 @@ export function useFixedColumn(columns: BasicColumn[]) {
return resultColumns; return resultColumns;
} }
// format cell
export function formatCell(text: string, format: CellFormat, record: Recordable, index: number) {
if (!format) {
return text;
}
// custom function
if (isFunction(format)) {
return format(text, record, index);
}
try {
// date type
const DATE_FORMAT_PREFIX = 'date|';
if (isString(format) && format.startsWith(DATE_FORMAT_PREFIX)) {
const dateFormat = format.replace(DATE_FORMAT_PREFIX, '');
if (!dateFormat) {
return text;
}
return formatToDate(text, dateFormat);
}
// enum
if (isObject(format) && Reflect.has(format, 'size')) {
return format.get(text);
}
} catch (error) {
return text;
}
}
import type { BasicTableProps, FetchParams } from '../types/table'; import type { BasicTableProps, FetchParams, SorterResult } from '../types/table';
import type { PaginationProps } from '../types/pagination'; import type { PaginationProps } from '../types/pagination';
import { ref, unref, ComputedRef, computed, onMounted, watchEffect } from 'vue'; import { ref, unref, ComputedRef, computed, onMounted, watchEffect, reactive } from 'vue';
import { useTimeoutFn } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '/@/hooks/core/useTimeout';
...@@ -16,12 +16,28 @@ interface ActionType { ...@@ -16,12 +16,28 @@ interface ActionType {
setPagination: (info: Partial<PaginationProps>) => void; setPagination: (info: Partial<PaginationProps>) => void;
setLoading: (loading: boolean) => void; setLoading: (loading: boolean) => void;
getFieldsValue: () => Recordable; getFieldsValue: () => Recordable;
clearSelectedRowKeys: () => void;
}
interface SearchState {
sortInfo: Recordable;
filterInfo: Record<string, string[]>;
} }
export function useDataSource( export function useDataSource(
propsRef: ComputedRef<BasicTableProps>, propsRef: ComputedRef<BasicTableProps>,
{ getPaginationInfo, setPagination, setLoading, getFieldsValue }: ActionType, {
getPaginationInfo,
setPagination,
setLoading,
getFieldsValue,
clearSelectedRowKeys,
}: ActionType,
emit: EmitType emit: EmitType
) { ) {
const searchState = reactive<SearchState>({
sortInfo: {},
filterInfo: {},
});
const dataSourceRef = ref<Recordable[]>([]); const dataSourceRef = ref<Recordable[]>([]);
watchEffect(() => { watchEffect(() => {
...@@ -29,6 +45,32 @@ export function useDataSource( ...@@ -29,6 +45,32 @@ export function useDataSource(
!api && dataSource && (dataSourceRef.value = dataSource); !api && dataSource && (dataSourceRef.value = dataSource);
}); });
function handleTableChange(
pagination: PaginationProps,
filters: Partial<Recordable<string[]>>,
sorter: SorterResult
) {
const { clearSelectOnPageChange, sortFn, filterFn } = unref(propsRef);
if (clearSelectOnPageChange) {
clearSelectedRowKeys();
}
setPagination(pagination);
const params: Recordable = {};
if (sorter && isFunction(sortFn)) {
const sortInfo = sortFn(sorter);
searchState.sortInfo = sortInfo;
params.sortInfo = sortInfo;
}
if (filters && isFunction(filterFn)) {
const filterInfo = filterFn(filters);
searchState.filterInfo = filterInfo;
params.filterInfo = filterInfo;
}
fetch(params);
}
function setTableKey(items: any[]) { function setTableKey(items: any[]) {
if (!items || !Array.isArray(items)) return; if (!items || !Array.isArray(items)) return;
items.forEach((item) => { items.forEach((item) => {
...@@ -75,6 +117,14 @@ export function useDataSource( ...@@ -75,6 +117,14 @@ export function useDataSource(
return unref(dataSourceRef); return unref(dataSourceRef);
}); });
async function updateTableData(index: number, key: string, value: any) {
const record = dataSourceRef.value[index];
if (record) {
dataSourceRef.value[index][key] = value;
}
return dataSourceRef.value[index];
}
async function fetch(opt?: FetchParams) { async function fetch(opt?: FetchParams) {
const { api, searchInfo, fetchSetting, beforeFetch, afterFetch, useSearchForm } = unref( const { api, searchInfo, fetchSetting, beforeFetch, afterFetch, useSearchForm } = unref(
propsRef propsRef
...@@ -94,6 +144,8 @@ export function useDataSource( ...@@ -94,6 +144,8 @@ export function useDataSource(
pageParams[sizeField] = pageSize; pageParams[sizeField] = pageSize;
} }
const { sortInfo = {}, filterInfo } = searchState;
let params: Recordable = { let params: Recordable = {
...pageParams, ...pageParams,
...(useSearchForm ? getFieldsValue() : {}), ...(useSearchForm ? getFieldsValue() : {}),
...@@ -101,6 +153,8 @@ export function useDataSource( ...@@ -101,6 +153,8 @@ export function useDataSource(
...(opt ? opt.searchInfo : {}), ...(opt ? opt.searchInfo : {}),
...(opt ? opt.sortInfo : {}), ...(opt ? opt.sortInfo : {}),
...(opt ? opt.filterInfo : {}), ...(opt ? opt.filterInfo : {}),
...sortInfo,
...filterInfo,
}; };
if (beforeFetch && isFunction(beforeFetch)) { if (beforeFetch && isFunction(beforeFetch)) {
params = beforeFetch(params) || params; params = beforeFetch(params) || params;
...@@ -175,5 +229,7 @@ export function useDataSource( ...@@ -175,5 +229,7 @@ export function useDataSource(
getAutoCreateKey, getAutoCreateKey,
fetch, fetch,
reload, reload,
updateTableData,
handleTableChange,
}; };
} }
import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table'; import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table';
import type { PaginationProps } from '../types/pagination'; import type { PaginationProps } from '../types/pagination';
import type { DynamicProps } from '/@/types/utils';
import { getDynamicProps } from '/@/utils';
import { ref, onUnmounted, unref } from 'vue'; import { ref, onUnmounted, unref } from 'vue';
import { isProdMode } from '/@/utils/env'; import { isProdMode } from '/@/utils/env';
import { isInSetup } from '/@/utils/helper/vueHelper'; import { isInSetup } from '/@/utils/helper/vueHelper';
import { error } from '/@/utils/log';
import { watchEffect } from 'vue';
import type { FormActionType } from '/@/components/Form';
type Props = Partial<DynamicProps<BasicTableProps>>;
export function useTable( export function useTable(
tableProps?: Partial<BasicTableProps> tableProps?: Props
): [(instance: TableActionType) => void, TableActionType] { ): [(instance: TableActionType, formInstance: FormActionType) => void, TableActionType] {
isInSetup(); isInSetup();
const tableRef = ref<Nullable<TableActionType>>(null); const tableRef = ref<Nullable<TableActionType>>(null);
const loadedRef = ref<Nullable<boolean>>(false); const loadedRef = ref<Nullable<boolean>>(false);
const formRef = ref<Nullable<FormActionType>>(null);
function register(instance: TableActionType) { function register(instance: TableActionType, formInstance: FormActionType) {
isProdMode() && isProdMode() &&
onUnmounted(() => { onUnmounted(() => {
tableRef.value = null; tableRef.value = null;
...@@ -24,20 +32,29 @@ export function useTable( ...@@ -24,20 +32,29 @@ export function useTable(
return; return;
} }
tableRef.value = instance; tableRef.value = instance;
tableProps && instance.setProps(tableProps); formRef.value = formInstance;
// tableProps && instance.setProps(tableProps);
loadedRef.value = true; loadedRef.value = true;
watchEffect(() => {
tableProps && instance.setProps(getDynamicProps(tableProps));
});
} }
function getTableInstance(): TableActionType { function getTableInstance(): TableActionType {
const table = unref(tableRef); const table = unref(tableRef);
if (!table) { if (!table) {
throw new Error('table is undefined!'); error(
'The table instance has not been obtained yet, please make sure the table is presented when performing the table operation!'
);
} }
return table; return table as TableActionType;
} }
const methods: TableActionType = { const methods: TableActionType & {
reload: (opt?: FetchParams) => { getForm: () => FormActionType;
} = {
reload: async (opt?: FetchParams) => {
getTableInstance().reload(opt); getTableInstance().reload(opt);
}, },
setProps: (props: Partial<BasicTableProps>) => { setProps: (props: Partial<BasicTableProps>) => {
...@@ -54,7 +71,6 @@ export function useTable( ...@@ -54,7 +71,6 @@ export function useTable(
}, },
getColumns: ({ ignoreIndex = false }: { ignoreIndex?: boolean } = {}) => { getColumns: ({ ignoreIndex = false }: { ignoreIndex?: boolean } = {}) => {
const columns = getTableInstance().getColumns({ ignoreIndex }) || []; const columns = getTableInstance().getColumns({ ignoreIndex }) || [];
return columns; return columns;
}, },
setColumns: (columns: BasicColumn[]) => { setColumns: (columns: BasicColumn[]) => {
...@@ -87,7 +103,19 @@ export function useTable( ...@@ -87,7 +103,19 @@ export function useTable(
getSize: () => { getSize: () => {
return getTableInstance().getSize(); return getTableInstance().getSize();
}, },
} as TableActionType; updateTableData: (index: number, key: string, value: any) => {
return getTableInstance().updateTableData(index, key, value);
},
getRowSelection: () => {
return getTableInstance().getRowSelection();
},
getCacheColumns: () => {
return getTableInstance().getCacheColumns();
},
getForm: () => {
return unref(formRef) as FormActionType;
},
};
return [register, methods]; return [register, methods];
} }
...@@ -121,7 +121,7 @@ export function useTableScroll( ...@@ -121,7 +121,7 @@ export function useTableScroll(
width += 60; width += 60;
} }
// TODO props // TODO propsdth ?? 0;
const NORMAL_WIDTH = 150; const NORMAL_WIDTH = 150;
const columns = unref(columnsRef); const columns = unref(columnsRef);
...@@ -135,7 +135,10 @@ export function useTableScroll( ...@@ -135,7 +135,10 @@ export function useTableScroll(
if (len !== 0) { if (len !== 0) {
width += len * NORMAL_WIDTH; width += len * NORMAL_WIDTH;
} }
return width;
const table = unref(tableElRef);
const tableWidth = table?.$el?.offsetWidth ?? 0;
return tableWidth > width ? tableWidth - 24 : width;
}); });
const getScrollRef = computed(() => { const getScrollRef = computed(() => {
......
...@@ -9,21 +9,29 @@ import type { ...@@ -9,21 +9,29 @@ import type {
TableRowSelection, TableRowSelection,
} from './types/table'; } from './types/table';
import type { FormProps } from '/@/components/Form'; import type { FormProps } from '/@/components/Form';
import { DEFAULT_SORT_FN, FETCH_SETTING } from './const'; import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING } from './const';
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes';
// 注释看 types/table // 注释看 types/table
export const basicProps = { export const basicProps = {
clickToRowSelect: propTypes.bool.def(true), clickToRowSelect: propTypes.bool.def(true),
tableSetting: { tableSetting: {
type: Object as PropType<TableSetting>, type: Object as PropType<TableSetting>,
}, },
inset: propTypes.bool, inset: propTypes.bool,
sortFn: { sortFn: {
type: Function as PropType<(sortInfo: SorterResult) => any>, type: Function as PropType<(sortInfo: SorterResult) => any>,
default: DEFAULT_SORT_FN, default: DEFAULT_SORT_FN,
}, },
filterFn: {
type: Function as PropType<(data: Partial<Recordable<string[]>>) => any>,
default: DEFAULT_FILTER_FN,
},
showTableSetting: propTypes.bool, showTableSetting: propTypes.bool,
autoCreateKey: propTypes.bool.def(true), autoCreateKey: propTypes.bool.def(true),
striped: propTypes.bool.def(true), striped: propTypes.bool.def(true),
......
@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;
}
}
}
...@@ -133,14 +133,18 @@ ...@@ -133,14 +133,18 @@
overflow-y: scroll !important; overflow-y: scroll !important;
} }
.ant-table-fixed-right .ant-table-header { .ant-table-fixed-right {
border-left: 1px solid @border-color !important; right: -1px;
.ant-table-fixed { .ant-table-header {
border-bottom: none; border-left: 1px solid @border-color !important;
.ant-table-fixed {
border-bottom: none;
.ant-table-thead th { .ant-table-thead th {
background: rgb(241, 243, 244); background: rgb(241, 243, 244);
}
} }
} }
} }
......
export type ComponentType = export type ComponentType =
| 'Input' | 'Input'
| 'InputPassword'
| 'InputNumber' | 'InputNumber'
| 'Select' | 'Select'
| 'ApiSelect'
| 'Checkbox' | 'Checkbox'
| 'CheckboxGroup'
| 'Switch'; | 'Switch';
...@@ -6,9 +6,10 @@ import type { ...@@ -6,9 +6,10 @@ import type {
TableRowSelection as ITableRowSelection, TableRowSelection as ITableRowSelection,
} from 'ant-design-vue/lib/table/interface'; } from 'ant-design-vue/lib/table/interface';
import { ComponentType } from './componentType'; import { ComponentType } from './componentType';
import { VueNode } from '/@/utils/propTypes';
// import { ColumnProps } from './column'; // import { ColumnProps } from './column';
export declare type SortOrder = 'ascend' | 'descend'; export declare type SortOrder = 'ascend' | 'descend';
export interface TableCurrentDataSource<T = any> { export interface TableCurrentDataSource<T = Recordable> {
currentDataSource: T[]; currentDataSource: T[];
} }
...@@ -53,7 +54,7 @@ export interface ColumnFilterItem { ...@@ -53,7 +54,7 @@ export interface ColumnFilterItem {
children?: any; children?: any;
} }
export interface TableCustomRecord<T = any> { export interface TableCustomRecord<T = Recordable> {
record?: T; record?: T;
index?: number; index?: number;
} }
...@@ -65,18 +66,11 @@ export interface SorterResult { ...@@ -65,18 +66,11 @@ export interface SorterResult {
columnKey: string; columnKey: string;
} }
export interface RenderEditableCellParams {
dataIndex: string;
component?: ComponentType;
componentProps?: any;
placeholder?: string;
}
export interface FetchParams { export interface FetchParams {
searchInfo?: any; searchInfo?: Recordable;
page?: number; page?: number;
sortInfo?: any; sortInfo?: Recordable;
filterInfo?: any; filterInfo?: Recordable;
} }
export interface GetColumnsParams { export interface GetColumnsParams {
...@@ -89,7 +83,7 @@ export type SizeType = 'default' | 'middle' | 'small' | 'large'; ...@@ -89,7 +83,7 @@ export type SizeType = 'default' | 'middle' | 'small' | 'large';
export interface TableActionType { export interface TableActionType {
reload: (opt?: FetchParams) => Promise<void>; reload: (opt?: FetchParams) => Promise<void>;
getSelectRows: <T = any>() => T[]; getSelectRows: <T = Recordable>() => T[];
clearSelectedRowKeys: () => void; clearSelectedRowKeys: () => void;
getSelectRowKeys: () => string[]; getSelectRowKeys: () => string[];
deleteSelectRowByKey: (key: string) => void; deleteSelectRowByKey: (key: string) => void;
...@@ -106,6 +100,8 @@ export interface TableActionType { ...@@ -106,6 +100,8 @@ export interface TableActionType {
getSize: () => SizeType; getSize: () => SizeType;
getRowSelection: () => TableRowSelection<Recordable>; getRowSelection: () => TableRowSelection<Recordable>;
getCacheColumns: () => BasicColumn[]; getCacheColumns: () => BasicColumn[];
emit?: EmitType;
updateTableData: (index: number, key: string, value: any) => Recordable;
} }
export interface FetchSetting { export interface FetchSetting {
...@@ -131,6 +127,8 @@ export interface BasicTableProps<T = any> { ...@@ -131,6 +127,8 @@ export interface BasicTableProps<T = any> {
clickToRowSelect?: boolean; clickToRowSelect?: boolean;
// 自定义排序方法 // 自定义排序方法
sortFn?: (sortInfo: SorterResult) => any; sortFn?: (sortInfo: SorterResult) => any;
// 排序方法
filterFn?: (data: Partial<Recordable<string[]>>) => any;
// 取消表格的默认padding // 取消表格的默认padding
inset?: boolean; inset?: boolean;
// 显示表格设置 // 显示表格设置
...@@ -141,7 +139,7 @@ export interface BasicTableProps<T = any> { ...@@ -141,7 +139,7 @@ export interface BasicTableProps<T = any> {
// 是否自动生成key // 是否自动生成key
autoCreateKey?: boolean; autoCreateKey?: boolean;
// 计算合计行的方法 // 计算合计行的方法
summaryFunc?: (...arg: any) => any[]; summaryFunc?: (...arg: any) => Recordable[];
// 是否显示合计行 // 是否显示合计行
showSummary?: boolean; showSummary?: boolean;
// 是否可拖拽列 // 是否可拖拽列
...@@ -374,13 +372,43 @@ export interface BasicTableProps<T = any> { ...@@ -374,13 +372,43 @@ export interface BasicTableProps<T = any> {
onExpandedRowsChange?: (expandedRows: string[] | number[]) => void; onExpandedRowsChange?: (expandedRows: string[] | number[]) => void;
} }
export type CellFormat =
| string
| ((text: string, record: Recordable, index: number) => string | number)
| Map<string | number, any>;
// @ts-ignore
export interface BasicColumn extends ColumnProps { export interface BasicColumn extends ColumnProps {
children?: BasicColumn[]; children?: BasicColumn[];
filters?: {
text: string;
value: string;
children?:
| unknown[]
| (((props: Record<string, unknown>) => unknown[]) & (() => unknown[]) & (() => unknown[]));
}[];
// //
flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION'; flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION';
customTitle?: VueNode;
slots?: Indexable; slots?: Indexable;
// Whether to hide the column by default, it can be displayed in the column configuration
defaultHidden?: boolean; defaultHidden?: boolean;
// Help text for table column header
helpMessage?: string | string[];
format?: CellFormat;
// Editable
edit?: boolean;
editRow?: boolean;
editable?: boolean;
editComponent?: ComponentType;
editComponentProps?: Recordable;
editRule?: boolean | ((text: string, record: Recordable) => Promise<string>);
editValueMap?: (value: any) => string;
onEditRow?: () => void;
} }
...@@ -351,6 +351,11 @@ ...@@ -351,6 +351,11 @@
position: absolute; position: absolute;
top: 10px; top: 10px;
right: 30px; right: 30px;
&--dot {
top: 50%;
margin-top: -3px;
}
} }
&__title { &__title {
......
...@@ -52,6 +52,9 @@ const menu: MenuModule = { ...@@ -52,6 +52,9 @@ const menu: MenuModule = {
{ {
path: 'table', path: 'table',
name: t('routes.demo.table.table'), name: t('routes.demo.table.table'),
tag: {
dot: true,
},
children: [ children: [
{ {
path: 'basic', path: 'basic',
...@@ -108,10 +111,16 @@ const menu: MenuModule = { ...@@ -108,10 +111,16 @@ const menu: MenuModule = {
{ {
path: 'editCellTable', path: 'editCellTable',
name: t('routes.demo.table.editCellTable'), name: t('routes.demo.table.editCellTable'),
tag: {
dot: true,
},
}, },
{ {
path: 'editRowTable', path: 'editRowTable',
name: t('routes.demo.table.editRowTable'), name: t('routes.demo.table.editRowTable'),
tag: {
dot: true,
},
}, },
], ],
}, },
......
...@@ -3,12 +3,15 @@ import moment from 'moment'; ...@@ -3,12 +3,15 @@ import moment from 'moment';
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm'; const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
const DATE_FORMAT = 'YYYY-MM-DD '; const DATE_FORMAT = 'YYYY-MM-DD ';
export function formatToDateTime(date: moment.MomentInput = null): string { export function formatToDateTime(
return moment(date).format(DATE_TIME_FORMAT); date: moment.MomentInput = null,
format = DATE_TIME_FORMAT
): string {
return moment(date).format(format);
} }
export function formatToDate(date: moment.MomentInput = null): string { export function formatToDate(date: moment.MomentInput = null, format = DATE_FORMAT): string {
return moment(date).format(DATE_FORMAT); return moment(date).format(format);
} }
export const formatAgo = (str: string | number) => { export const formatAgo = (str: string | number) => {
......
<template> <template>
<div class="p-4"> <div class="p-4">
<BasicTable @register="registerTable"> <BasicTable @register="registerTable" @edit-end="handleEditEnd" @edit-cancel="handleEditCancel">
<template #customId>
<EditTableHeaderIcon title="Id" />
</template>
<template #customName>
<EditTableHeaderIcon title="姓名" />
</template>
</BasicTable> </BasicTable>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { import { BasicTable, useTable, BasicColumn, EditTableHeaderIcon } from '/@/components/Table';
BasicTable, import { optionsListApi } from '/@/api/demo/select';
useTable,
BasicColumn,
renderEditableCell,
EditTableHeaderIcon,
} from '/@/components/Table';
import { demoListApi } from '/@/api/demo/table'; import { demoListApi } from '/@/api/demo/table';
const columns: BasicColumn[] = [ const columns: BasicColumn[] = [
{ {
// title: 'ID', title: '输入框',
dataIndex: 'name',
edit: true,
editComponentProps: {
prefix: '$',
},
width: 200,
},
{
title: '默认输入状态',
dataIndex: 'name7',
edit: true,
editable: true,
width: 200,
},
{
title: '输入框校验',
dataIndex: 'name1',
edit: true,
// 默认必填校验
editRule: true,
width: 200,
},
{
title: '输入框函数校验',
dataIndex: 'name2',
edit: true,
editRule: async (text) => {
if (text === '2') {
return '不能输入该值';
}
return '';
},
width: 200,
},
{
title: '数字输入框',
dataIndex: 'id', dataIndex: 'id',
slots: { title: 'customId' }, edit: true,
customRender: renderEditableCell({ dataIndex: 'id' }), editRule: true,
editComponent: 'InputNumber',
width: 200,
}, },
{ {
// title: '姓名', title: '下拉框',
dataIndex: 'name', dataIndex: 'name3',
slots: { title: 'customName' }, edit: true,
customRender: renderEditableCell({ editComponent: 'Select',
dataIndex: 'name', editComponentProps: {
}), options: [
{
label: 'Option1',
value: '1',
},
{
label: 'Option2',
value: '2',
},
],
},
width: 200,
},
{
title: '远程下拉',
dataIndex: 'name4',
edit: true,
editComponent: 'ApiSelect',
editComponentProps: {
api: optionsListApi,
},
width: 200,
}, },
{ {
title: '地址', title: '勾选框',
dataIndex: 'address', dataIndex: 'name5',
sorter: true, edit: true,
editComponent: 'Checkbox',
editValueMap: (value) => {
return value ? '是' : '否';
},
width: 200,
},
{
title: '开关',
dataIndex: 'name6',
edit: true,
editComponent: 'Switch',
editValueMap: (value) => {
return value ? '开' : '关';
},
width: 200,
}, },
]; ];
export default defineComponent({ export default defineComponent({
...@@ -50,10 +113,21 @@ ...@@ -50,10 +113,21 @@
api: demoListApi, api: demoListApi,
columns: columns, columns: columns,
showIndexColumn: false, showIndexColumn: false,
bordered: true,
}); });
function handleEditEnd({ record, index, key, value }: Recordable) {
console.log(record, index, key, value);
}
function handleEditCancel() {
console.log('cancel');
}
return { return {
registerTable, registerTable,
handleEditEnd,
handleEditCancel,
}; };
}, },
}); });
......
...@@ -15,24 +15,105 @@ ...@@ -15,24 +15,105 @@
TableAction, TableAction,
BasicColumn, BasicColumn,
ActionItem, ActionItem,
renderEditableRow,
EditTableHeaderIcon, EditTableHeaderIcon,
EditRecordRow, EditRecordRow,
} from '/@/components/Table'; } from '/@/components/Table';
import { optionsListApi } from '/@/api/demo/select';
import { demoListApi } from '/@/api/demo/table'; import { demoListApi } from '/@/api/demo/table';
const columns: BasicColumn[] = [ const columns: BasicColumn[] = [
{ {
title: 'ID', title: '输入框',
dataIndex: 'name',
editRow: true,
editComponentProps: {
prefix: '$',
},
width: 200,
},
{
title: '默认输入状态',
dataIndex: 'name7',
editRow: true,
width: 200,
},
{
title: '输入框校验',
dataIndex: 'name1',
editRow: true,
// 默认必填校验
editRule: true,
width: 200,
},
{
title: '输入框函数校验',
dataIndex: 'name2',
editRow: true,
editRule: async (text) => {
if (text === '2') {
return '不能输入该值';
}
return '';
},
width: 200,
},
{
title: '数字输入框',
dataIndex: 'id', dataIndex: 'id',
customRender: renderEditableRow({ dataIndex: 'id' }), editRow: true,
editRule: true,
editComponent: 'InputNumber',
width: 200,
}, },
{ {
title: '姓名', title: '下拉框',
dataIndex: 'name', dataIndex: 'name3',
customRender: renderEditableRow({ editRow: true,
dataIndex: 'name', editComponent: 'Select',
}), editComponentProps: {
options: [
{
label: 'Option1',
value: '1',
},
{
label: 'Option2',
value: '2',
},
],
},
width: 200,
},
{
title: '远程下拉',
dataIndex: 'name4',
editRow: true,
editComponent: 'ApiSelect',
editComponentProps: {
api: optionsListApi,
},
width: 200,
},
{
title: '勾选框',
dataIndex: 'name5',
editRow: true,
editComponent: 'Checkbox',
editValueMap: (value) => {
return value ? '是' : '否';
},
width: 200,
},
{
title: '开关',
dataIndex: 'name6',
editRow: true,
editComponent: 'Switch',
editValueMap: (value) => {
return value ? '开' : '关';
},
width: 200,
}, },
]; ];
export default defineComponent({ export default defineComponent({
...@@ -55,19 +136,19 @@ ...@@ -55,19 +136,19 @@
function handleEdit(record: EditRecordRow) { function handleEdit(record: EditRecordRow) {
currentEditKeyRef.value = record.key; currentEditKeyRef.value = record.key;
record.editable = true; record.onEdit?.(true);
} }
function handleCancel(record: EditRecordRow) { function handleCancel(record: EditRecordRow) {
currentEditKeyRef.value = ''; currentEditKeyRef.value = '';
record.editable = false; record.onEdit?.(false, true);
record.onCancel && record.onCancel();
} }
function handleSave(record: EditRecordRow) { async function handleSave(record: EditRecordRow) {
currentEditKeyRef.value = ''; const pass = await record.onEdit?.(false, true);
record.editable = false; if (pass) {
record.onSubmit && record.onSubmit(); currentEditKeyRef.value = '';
}
} }
function createActions(record: EditRecordRow, column: BasicColumn): ActionItem[] { function createActions(record: EditRecordRow, column: BasicColumn): ActionItem[] {
......
...@@ -41,7 +41,6 @@ ...@@ -41,7 +41,6 @@
{ {
title: '地址', title: '地址',
dataIndex: 'address', dataIndex: 'address',
width: 260,
}, },
{ {
title: '编号', title: '编号',
...@@ -67,6 +66,7 @@ ...@@ -67,6 +66,7 @@
api: demoListApi, api: demoListApi,
columns: columns, columns: columns,
rowSelection: { type: 'radio' }, rowSelection: { type: 'radio' },
bordered: true,
actionColumn: { actionColumn: {
width: 160, width: 160,
title: 'Action', title: 'Action',
......
...@@ -7,12 +7,16 @@ export function getBasicColumns(): BasicColumn[] { ...@@ -7,12 +7,16 @@ export function getBasicColumns(): BasicColumn[] {
title: 'ID', title: 'ID',
dataIndex: 'id', dataIndex: 'id',
fixed: 'left', fixed: 'left',
width: 400, width: 200,
}, },
{ {
title: '姓名', title: '姓名',
dataIndex: 'name', dataIndex: 'name',
width: 150, width: 150,
filters: [
{ text: 'Male', value: 'male' },
{ text: 'Female', value: 'female' },
],
}, },
{ {
title: '地址', title: '地址',
...@@ -22,11 +26,13 @@ export function getBasicColumns(): BasicColumn[] { ...@@ -22,11 +26,13 @@ export function getBasicColumns(): BasicColumn[] {
title: '编号', title: '编号',
dataIndex: 'no', dataIndex: 'no',
width: 150, width: 150,
sorter: true,
defaultHidden: true, defaultHidden: true,
}, },
{ {
title: '开始时间', title: '开始时间',
width: 120, width: 120,
sorter: true,
dataIndex: 'beginTime', dataIndex: 'beginTime',
}, },
{ {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论