mirror of
https://gitee.com/bootx/dax-pay-ui.git
synced 2025-09-18 18:02:43 +00:00
@@ -1,5 +1,55 @@
|
|||||||
## 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 @@
|
|||||||
- 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 @@
|
|||||||
### 🎫 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 = (() => {
|
|||||||
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';
|
|||||||
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 @@
|
|||||||
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 @@
|
|||||||
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(
|
|||||||
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(
|
|||||||
}
|
}
|
||||||
|
|
||||||
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(
|
|||||||
}
|
}
|
||||||
|
|
||||||
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';
|
|||||||
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';
|
|||||||
|
|
||||||
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 @@
|
|||||||
<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 @@
|
|||||||
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 @@
|
|||||||
'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,32 +103,6 @@
|
|||||||
|
|
||||||
const { getLoading, setLoading } = useLoading(getProps);
|
const { getLoading, setLoading } = useLoading(getProps);
|
||||||
const { getPaginationInfo, getPagination, setPagination } = usePagination(getProps);
|
const { getPaginationInfo, getPagination, setPagination } = usePagination(getProps);
|
||||||
const {
|
|
||||||
getSortFixedColumns,
|
|
||||||
getColumns,
|
|
||||||
setColumns,
|
|
||||||
getColumnsRef,
|
|
||||||
getCacheColumns,
|
|
||||||
} = useColumns(getProps, getPaginationInfo);
|
|
||||||
|
|
||||||
const {
|
|
||||||
getDataSourceRef,
|
|
||||||
getDataSource,
|
|
||||||
setTableData,
|
|
||||||
fetch,
|
|
||||||
getRowKey,
|
|
||||||
reload,
|
|
||||||
getAutoCreateKey,
|
|
||||||
} = useDataSource(
|
|
||||||
getProps,
|
|
||||||
{
|
|
||||||
getPaginationInfo,
|
|
||||||
setLoading,
|
|
||||||
setPagination,
|
|
||||||
getFieldsValue: formActions.getFieldsValue,
|
|
||||||
},
|
|
||||||
emit
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getRowSelection,
|
getRowSelection,
|
||||||
@@ -133,6 +114,33 @@
|
|||||||
setSelectedRowKeys,
|
setSelectedRowKeys,
|
||||||
} = useRowSelection(getProps, emit);
|
} = useRowSelection(getProps, emit);
|
||||||
|
|
||||||
|
const {
|
||||||
|
handleTableChange,
|
||||||
|
getDataSourceRef,
|
||||||
|
getDataSource,
|
||||||
|
setTableData,
|
||||||
|
fetch,
|
||||||
|
getRowKey,
|
||||||
|
reload,
|
||||||
|
getAutoCreateKey,
|
||||||
|
updateTableData,
|
||||||
|
} = useDataSource(
|
||||||
|
getProps,
|
||||||
|
{
|
||||||
|
getPaginationInfo,
|
||||||
|
setLoading,
|
||||||
|
setPagination,
|
||||||
|
getFieldsValue: formActions.getFieldsValue,
|
||||||
|
clearSelectedRowKeys,
|
||||||
|
},
|
||||||
|
emit
|
||||||
|
);
|
||||||
|
|
||||||
|
const { getViewColumns, getColumns, setColumns, getColumnsRef, getCacheColumns } = useColumns(
|
||||||
|
getProps,
|
||||||
|
getPaginationInfo
|
||||||
|
);
|
||||||
|
|
||||||
const { getScrollRef, redoHeight } = useTableScroll(
|
const { getScrollRef, redoHeight } = useTableScroll(
|
||||||
getProps,
|
getProps,
|
||||||
tableElRef,
|
tableElRef,
|
||||||
@@ -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 @@
|
|||||||
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 @@
|
|||||||
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 @@
|
|||||||
replaceFormSlotKey,
|
replaceFormSlotKey,
|
||||||
getFormSlotKeys,
|
getFormSlotKeys,
|
||||||
prefixCls,
|
prefixCls,
|
||||||
|
columns: getViewColumns,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@@ -1,19 +1,19 @@
|
|||||||
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);
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<span>
|
<span>
|
||||||
|
<slot />
|
||||||
{{ title }}
|
{{ title }}
|
||||||
<FormOutlined class="ml-2" />
|
<FormOutlined />
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
55
src/components/Table/src/components/HeaderCell.vue
Normal file
55
src/components/Table/src/components/HeaderCell.vue
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<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>
|
@@ -1,14 +1,13 @@
|
|||||||
<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 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
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,
|
||||||
|
@@ -0,0 +1,33 @@
|
|||||||
|
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,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
359
src/components/Table/src/components/editable/EditableCell.vue
Normal file
359
src/components/Table/src/components/editable/EditableCell.vue
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
<template>
|
||||||
|
<div :class="prefixCls">
|
||||||
|
<div v-show="!isEdit" :class="`${prefixCls}__normal`" @click="handleEdit">
|
||||||
|
{{ value || ' ' }}
|
||||||
|
<FormOutlined :class="`${prefixCls}__normal-icon`" v-if="!column.editRow" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="isEdit" :class="`${prefixCls}__wrapper`" v-click-outside="onClickOutside">
|
||||||
|
<CellComponent
|
||||||
|
v-bind="getComponentProps"
|
||||||
|
:component="getComponent"
|
||||||
|
:style="getWrapperStyle"
|
||||||
|
:popoverVisible="getRuleVisible"
|
||||||
|
:rule="getRule"
|
||||||
|
:ruleMessage="ruleMessage"
|
||||||
|
size="small"
|
||||||
|
ref="elRef"
|
||||||
|
@change="handleChange"
|
||||||
|
@options-change="handleOptionsChange"
|
||||||
|
@pressEnter="handleSubmit"
|
||||||
|
>
|
||||||
|
</CellComponent>
|
||||||
|
<div :class="`${prefixCls}__action`" v-if="!getRowEditable">
|
||||||
|
<CheckOutlined :class="[`${prefixCls}__icon`, 'mx-2']" @click="handleSubmit" />
|
||||||
|
<CloseOutlined :class="`${prefixCls}__icon `" @click="handleCancel" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import type { CSSProperties, PropType } from 'vue';
|
||||||
|
import type { BasicColumn } from '../../types/table';
|
||||||
|
|
||||||
|
import { defineComponent, ref, unref, nextTick, computed, watchEffect, toRaw } from 'vue';
|
||||||
|
import { FormOutlined, CloseOutlined, CheckOutlined } from '@ant-design/icons-vue';
|
||||||
|
|
||||||
|
import { useDesign } from '/@/hooks/web/useDesign';
|
||||||
|
import { isString, isBoolean, isFunction, isNumber, isArray } from '/@/utils/is';
|
||||||
|
import clickOutside from '/@/directives/clickOutside';
|
||||||
|
|
||||||
|
import { CellComponent } from './CellComponent';
|
||||||
|
import { useTableContext } from '../../hooks/useTableContext';
|
||||||
|
import { propTypes } from '/@/utils/propTypes';
|
||||||
|
import { createPlaceholderMessage } from './helper';
|
||||||
|
|
||||||
|
import type { EditRecordRow } from './index';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'EditableCell',
|
||||||
|
components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent },
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: [String, Number, Boolean, Object] as PropType<string | number | boolean | Recordable>,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
record: {
|
||||||
|
type: Object as PropType<EditRecordRow>,
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
type: Object as PropType<BasicColumn>,
|
||||||
|
default: {},
|
||||||
|
},
|
||||||
|
index: propTypes.number,
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
clickOutside,
|
||||||
|
},
|
||||||
|
|
||||||
|
setup(props) {
|
||||||
|
const table = useTableContext();
|
||||||
|
const isEdit = ref(false);
|
||||||
|
const elRef = ref<any>(null);
|
||||||
|
const ruleVisible = ref(false);
|
||||||
|
const ruleMessage = ref('');
|
||||||
|
const optionsRef = ref<LabelValueOptions>([]);
|
||||||
|
const currentValueRef = ref<any>(props.value);
|
||||||
|
const defaultValueRef = ref<any>(props.value);
|
||||||
|
|
||||||
|
const { prefixCls } = useDesign('editable-cell');
|
||||||
|
|
||||||
|
const getComponent = computed(() => props.column?.editComponent || 'Input');
|
||||||
|
const getRule = computed(() => props.column?.editRule);
|
||||||
|
|
||||||
|
const getRuleVisible = computed(() => {
|
||||||
|
return unref(ruleMessage) && unref(ruleVisible);
|
||||||
|
});
|
||||||
|
|
||||||
|
const getIsCheckComp = computed(() => {
|
||||||
|
const component = unref(getComponent);
|
||||||
|
return ['Checkbox', 'Switch'].includes(component);
|
||||||
|
});
|
||||||
|
|
||||||
|
const getComponentProps = computed(() => {
|
||||||
|
const compProps = props.column?.editComponentProps ?? {};
|
||||||
|
const component = unref(getComponent);
|
||||||
|
const apiSelectProps: Recordable = {};
|
||||||
|
if (component === 'ApiSelect') {
|
||||||
|
apiSelectProps.cache = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCheckValue = unref(getIsCheckComp);
|
||||||
|
|
||||||
|
const valueField = isCheckValue ? 'checked' : 'value';
|
||||||
|
const val = unref(currentValueRef);
|
||||||
|
|
||||||
|
const value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val;
|
||||||
|
|
||||||
|
return {
|
||||||
|
placeholder: createPlaceholderMessage(unref(getComponent)),
|
||||||
|
...apiSelectProps,
|
||||||
|
...compProps,
|
||||||
|
[valueField]: value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const getValues = computed(() => {
|
||||||
|
const { editComponentProps, editValueMap } = props.column;
|
||||||
|
|
||||||
|
const value = unref(currentValueRef);
|
||||||
|
|
||||||
|
if (editValueMap && isFunction(editValueMap)) {
|
||||||
|
return editValueMap(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const component = unref(getComponent);
|
||||||
|
if (!component.includes('Select')) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
const options: LabelValueOptions = editComponentProps?.options ?? (unref(optionsRef) || []);
|
||||||
|
const option = options.find((item) => `${item.value}` === `${value}`);
|
||||||
|
return option?.label;
|
||||||
|
});
|
||||||
|
|
||||||
|
const getWrapperStyle = computed(
|
||||||
|
(): CSSProperties => {
|
||||||
|
if (unref(getIsCheckComp) || unref(getRowEditable)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
width: 'calc(100% - 48px)',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const getRowEditable = computed(() => {
|
||||||
|
const { editable } = props.record || {};
|
||||||
|
return !!editable;
|
||||||
|
});
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
defaultValueRef.value = props.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
const { editable } = props.column;
|
||||||
|
if (isBoolean(editable) || isBoolean(unref(getRowEditable))) {
|
||||||
|
isEdit.value = !!editable || unref(getRowEditable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleEdit() {
|
||||||
|
if (unref(getRowEditable) || unref(props.column?.editRow)) return;
|
||||||
|
ruleMessage.value = '';
|
||||||
|
isEdit.value = true;
|
||||||
|
nextTick(() => {
|
||||||
|
const el = unref(elRef);
|
||||||
|
el?.focus?.();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleChange(e: any) {
|
||||||
|
const component = unref(getComponent);
|
||||||
|
if (e?.target && Reflect.has(e.target, 'value')) {
|
||||||
|
currentValueRef.value = (e as ChangeEvent).target.value;
|
||||||
|
}
|
||||||
|
if (component === 'Checkbox') {
|
||||||
|
currentValueRef.value = (e as ChangeEvent).target.checked;
|
||||||
|
} else if (isString(e) || isBoolean(e) || isNumber(e)) {
|
||||||
|
currentValueRef.value = e;
|
||||||
|
}
|
||||||
|
handleSubmiRule();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmiRule() {
|
||||||
|
const { column, record } = props;
|
||||||
|
const { editRule } = column;
|
||||||
|
const currentValue = unref(currentValueRef);
|
||||||
|
|
||||||
|
if (editRule) {
|
||||||
|
if (isBoolean(editRule) && !currentValue && !isNumber(currentValue)) {
|
||||||
|
ruleVisible.value = true;
|
||||||
|
const component = unref(getComponent);
|
||||||
|
const message = createPlaceholderMessage(component);
|
||||||
|
ruleMessage.value = message;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isFunction(editRule)) {
|
||||||
|
const res = await editRule(currentValue, record as Recordable);
|
||||||
|
if (!!res) {
|
||||||
|
ruleMessage.value = res;
|
||||||
|
ruleVisible.value = true;
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
ruleMessage.value = '';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ruleMessage.value = '';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
const isPass = await handleSubmiRule();
|
||||||
|
if (!isPass) return false;
|
||||||
|
const { column, index } = props;
|
||||||
|
const { key, dataIndex } = column;
|
||||||
|
// const value = unref(currentValueRef);
|
||||||
|
if (!key || !dataIndex) return;
|
||||||
|
const dataKey = (dataIndex || key) as string;
|
||||||
|
|
||||||
|
const record = await table.updateTableData(index, dataKey, unref(getValues));
|
||||||
|
table.emit?.('edit-end', { record, index, key, value: unref(currentValueRef) });
|
||||||
|
isEdit.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
isEdit.value = false;
|
||||||
|
currentValueRef.value = defaultValueRef.value;
|
||||||
|
table.emit?.('edit-cancel', unref(currentValueRef));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClickOutside() {
|
||||||
|
if (props.column?.editable || unref(getRowEditable)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const component = unref(getComponent);
|
||||||
|
|
||||||
|
if (component.includes('Input')) {
|
||||||
|
handleCancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only ApiSelect
|
||||||
|
function handleOptionsChange(options: LabelValueOptions) {
|
||||||
|
optionsRef.value = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle: Fn) {
|
||||||
|
if (props.record) {
|
||||||
|
/* eslint-disable */
|
||||||
|
isArray(props.record[cbs])
|
||||||
|
? props.record[cbs].push(handle)
|
||||||
|
: (props.record[cbs] = [handle]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.record) {
|
||||||
|
initCbs('submitCbs', handleSubmit);
|
||||||
|
initCbs('validCbs', handleSubmiRule);
|
||||||
|
initCbs('cancelCbs', handleCancel);
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
|
props.record.onCancelEdit = () => {
|
||||||
|
isArray(props.record?.cancelCbs) && props.record?.cancelCbs.forEach((fn) => fn());
|
||||||
|
};
|
||||||
|
/* eslint-disable */
|
||||||
|
props.record.onSubmitEdit = async () => {
|
||||||
|
if (isArray(props.record?.submitCbs)) {
|
||||||
|
const validFns = props.record?.validCbs || [];
|
||||||
|
|
||||||
|
const res = await Promise.all(validFns.map((fn) => fn()));
|
||||||
|
const pass = res.every((item) => !!item);
|
||||||
|
|
||||||
|
if (!pass) return;
|
||||||
|
const submitFns = props.record?.submitCbs || [];
|
||||||
|
submitFns.forEach((fn) => fn());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// isArray(props.record?.submitCbs) && props.record?.submitCbs.forEach((fn) => fn());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isEdit,
|
||||||
|
prefixCls,
|
||||||
|
handleEdit,
|
||||||
|
currentValueRef,
|
||||||
|
handleSubmit,
|
||||||
|
handleChange,
|
||||||
|
handleCancel,
|
||||||
|
elRef,
|
||||||
|
getComponent,
|
||||||
|
getRule,
|
||||||
|
onClickOutside,
|
||||||
|
ruleMessage,
|
||||||
|
getRuleVisible,
|
||||||
|
getComponentProps,
|
||||||
|
handleOptionsChange,
|
||||||
|
getWrapperStyle,
|
||||||
|
getRowEditable,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="less">
|
||||||
|
@prefix-cls: ~'@{namespace}-editable-cell';
|
||||||
|
|
||||||
|
.edit-cell-rule-popover {
|
||||||
|
// .ant-popover-arrow {
|
||||||
|
// // border-color: transparent @error-color @error-color transparent !important;
|
||||||
|
// }
|
||||||
|
|
||||||
|
.ant-popover-inner-content {
|
||||||
|
padding: 4px 8px;
|
||||||
|
color: @error-color;
|
||||||
|
// border: 1px solid @error-color;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.@{prefix-cls} {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
26
src/components/Table/src/components/editable/helper.ts
Normal file
26
src/components/Table/src/components/editable/helper.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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 '';
|
||||||
|
}
|
52
src/components/Table/src/components/editable/index.ts
Normal file
52
src/components/Table/src/components/editable/index.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
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;
|
@@ -1,241 +0,0 @@
|
|||||||
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;
|
|
@@ -1,14 +0,0 @@
|
|||||||
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 @@
|
|||||||
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) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function DEFAULT_FILTER_FN(data: Partial<Recordable<string[]>>) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
// 表格单元格默认布局
|
// 表格单元格默认布局
|
||||||
export const DEFAULT_ALIGN = 'center';
|
export const DEFAULT_ALIGN = 'center';
|
||||||
|
|
||||||
|
@@ -1,10 +1,13 @@
|
|||||||
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(
|
|||||||
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(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sort) {
|
if (sort) {
|
||||||
columns = useFixedColumn(columns);
|
columns = sortFixedColumn(columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
return columns;
|
return columns;
|
||||||
@@ -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[]) {
|
|||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
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 {
|
|||||||
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(
|
|||||||
!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(
|
|||||||
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(
|
|||||||
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(
|
|||||||
...(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(
|
|||||||
getAutoCreateKey,
|
getAutoCreateKey,
|
||||||
fetch,
|
fetch,
|
||||||
reload,
|
reload,
|
||||||
|
updateTableData,
|
||||||
|
handleTableChange,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -1,19 +1,27 @@
|
|||||||
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(
|
|||||||
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(
|
|||||||
},
|
},
|
||||||
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(
|
|||||||
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(
|
|||||||
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(
|
|||||||
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 {
|
|||||||
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),
|
||||||
|
@@ -1,39 +0,0 @@
|
|||||||
@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,7 +133,10 @@
|
|||||||
overflow-y: scroll !important;
|
overflow-y: scroll !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-table-fixed-right .ant-table-header {
|
.ant-table-fixed-right {
|
||||||
|
right: -1px;
|
||||||
|
|
||||||
|
.ant-table-header {
|
||||||
border-left: 1px solid @border-color !important;
|
border-left: 1px solid @border-color !important;
|
||||||
|
|
||||||
.ant-table-fixed {
|
.ant-table-fixed {
|
||||||
@@ -144,6 +147,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ant-table-fixed-left {
|
.ant-table-fixed-left {
|
||||||
.ant-table-header {
|
.ant-table-header {
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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';
|
|||||||
|
|
||||||
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 {
|
|||||||
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> {
|
|||||||
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> {
|
|||||||
// 是否自动生成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> {
|
|||||||
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 @@
|
|||||||
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 = {
|
|||||||
{
|
{
|
||||||
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 = {
|
|||||||
{
|
{
|
||||||
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';
|
|||||||
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) => {
|
||||||
|
@@ -1,45 +1,108 @@
|
|||||||
<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',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '地址',
|
label: 'Option2',
|
||||||
dataIndex: 'address',
|
value: '2',
|
||||||
sorter: true,
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '远程下拉',
|
||||||
|
dataIndex: 'name4',
|
||||||
|
edit: true,
|
||||||
|
editComponent: 'ApiSelect',
|
||||||
|
editComponentProps: {
|
||||||
|
api: optionsListApi,
|
||||||
|
},
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '勾选框',
|
||||||
|
dataIndex: 'name5',
|
||||||
|
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 @@
|
|||||||
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 @@
|
|||||||
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: 'id',
|
dataIndex: 'name',
|
||||||
customRender: renderEditableRow({ dataIndex: 'id' }),
|
editRow: true,
|
||||||
|
editComponentProps: {
|
||||||
|
prefix: '$',
|
||||||
|
},
|
||||||
|
width: 200,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '姓名',
|
title: '默认输入状态',
|
||||||
dataIndex: 'name',
|
dataIndex: 'name7',
|
||||||
customRender: renderEditableRow({
|
editRow: true,
|
||||||
dataIndex: 'name',
|
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',
|
||||||
|
editRow: true,
|
||||||
|
editRule: true,
|
||||||
|
editComponent: 'InputNumber',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '下拉框',
|
||||||
|
dataIndex: 'name3',
|
||||||
|
editRow: true,
|
||||||
|
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 @@
|
|||||||
|
|
||||||
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) {
|
||||||
|
const pass = await record.onEdit?.(false, true);
|
||||||
|
if (pass) {
|
||||||
currentEditKeyRef.value = '';
|
currentEditKeyRef.value = '';
|
||||||
record.editable = false;
|
}
|
||||||
record.onSubmit && record.onSubmit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createActions(record: EditRecordRow, column: BasicColumn): ActionItem[] {
|
function createActions(record: EditRecordRow, column: BasicColumn): ActionItem[] {
|
||||||
|
@@ -41,7 +41,6 @@
|
|||||||
{
|
{
|
||||||
title: '地址',
|
title: '地址',
|
||||||
dataIndex: 'address',
|
dataIndex: 'address',
|
||||||
width: 260,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '编号',
|
title: '编号',
|
||||||
@@ -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[] {
|
|||||||
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[] {
|
|||||||
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',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user