mirror of
https://gitee.com/bootx/dax-pay-ui.git
synced 2025-09-07 12:48:06 +00:00
@@ -1,5 +1,55 @@
|
||||
## 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
|
||||
|
||||
- 新增 `v-ripple`水波纹指令
|
||||
@@ -12,14 +62,6 @@
|
||||
- form: 新增远程下拉`ApiSelect`及示例
|
||||
- form: 新增`autoFocusFirstItem`配置。用于配置是否聚焦表单第一个输入框
|
||||
- useForm: 支持动态改变参数。可以传入`Ref`类型与`Computed`类型进行动态更改
|
||||
- table: 新增`clickToRowSelect`属性。用于控制点击行是否选中勾选狂
|
||||
- table: 监听行点击事件
|
||||
- table: 表格列配置按钮增加 列拖拽,列固定功能。
|
||||
- table:表格列配置新增`defaultHidden` 属性。用于默认隐藏。可在表格列配置勾选显示
|
||||
|
||||
### ✨ Refactor
|
||||
|
||||
- 重构表单,解决已知 bug
|
||||
|
||||
### ⚡ Performance Improvements
|
||||
|
||||
@@ -30,6 +72,7 @@
|
||||
### 🎫 Chores
|
||||
|
||||
- 升级`ant-design-vue`到`2.0.0-rc.7`
|
||||
- 升级`vue`到`3.0.5`
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
|
@@ -10,6 +10,14 @@ const demoList = (() => {
|
||||
endTime: '@datetime',
|
||||
address: '@city()',
|
||||
name: '@cname()',
|
||||
name1: '@cname()',
|
||||
name2: '@cname()',
|
||||
name3: '@cname()',
|
||||
name4: '@cname()',
|
||||
name5: '@cname()',
|
||||
name6: '@cname()',
|
||||
name7: '@cname()',
|
||||
name8: '@cname()',
|
||||
'no|100000-10000000': 100000,
|
||||
'status|1': ['normal', 'enable', 'disable'],
|
||||
});
|
||||
|
@@ -9,4 +9,7 @@ export * from './src/types/formItem';
|
||||
export { useComponentRegister } from './src/hooks/useComponentRegister';
|
||||
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 };
|
||||
|
@@ -50,7 +50,8 @@
|
||||
labelField: propTypes.string.def('label'),
|
||||
valueField: propTypes.string.def('value'),
|
||||
},
|
||||
setup(props) {
|
||||
emits: ['options-change', 'change'],
|
||||
setup(props, { emit }) {
|
||||
const options = ref<OptionsItem[]>([]);
|
||||
const loading = ref(false);
|
||||
const attrs = useAttrs();
|
||||
@@ -86,11 +87,13 @@
|
||||
const res = await api(props.params);
|
||||
if (Array.isArray(res)) {
|
||||
options.value = res;
|
||||
emit('options-change', unref(options));
|
||||
return;
|
||||
}
|
||||
if (props.resultField) {
|
||||
options.value = get(res, props.resultField) || [];
|
||||
}
|
||||
emit('options-change', unref(options));
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
} finally {
|
||||
|
@@ -15,7 +15,7 @@ export function useOpenKeys(
|
||||
mode: Ref<MenuModeEnum>,
|
||||
accordion: Ref<boolean>
|
||||
) {
|
||||
const { getCollapsed } = useMenuSetting();
|
||||
const { getCollapsed, getIsMixSidebar } = useMenuSetting();
|
||||
|
||||
function setOpenKeys(path: string) {
|
||||
if (mode.value === MenuModeEnum.HORIZONTAL) {
|
||||
@@ -30,7 +30,9 @@ export function useOpenKeys(
|
||||
}
|
||||
|
||||
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[]) {
|
||||
if (unref(mode) === MenuModeEnum.HORIZONTAL || !unref(accordion)) {
|
||||
if (unref(mode) === MenuModeEnum.HORIZONTAL || !unref(accordion) || unref(getIsMixSidebar)) {
|
||||
menuState.openKeys = openKeys;
|
||||
} else {
|
||||
// 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 TableAction } from './src/components/TableAction.vue';
|
||||
// export { default as TableImg } from './src/components/TableImg.vue';
|
||||
export { renderEditableCell, renderEditableRow } from './src/components/renderEditable';
|
||||
export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue';
|
||||
|
||||
export const TableImg = createAsyncComponent(() => import('./src/components/TableImg.vue'));
|
||||
@@ -17,4 +16,4 @@ export { useTable } from './src/hooks/useTable';
|
||||
|
||||
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)">
|
||||
<slot :name="item" v-bind="data" />
|
||||
</template>
|
||||
<template #[`header-${column.dataIndex}`] v-for="column in columns" :key="column.dataIndex">
|
||||
<HeaderCell :column="column" />
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { BasicTableProps, TableActionType, SizeType, SorterResult } from './types/table';
|
||||
import { PaginationProps } from './types/pagination';
|
||||
import type { BasicTableProps, TableActionType, SizeType } from './types/table';
|
||||
|
||||
import { defineComponent, ref, computed, unref } from 'vue';
|
||||
import { Table } from 'ant-design-vue';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
|
||||
import { isFunction } from '/@/utils/is';
|
||||
|
||||
import { omit } from 'lodash-es';
|
||||
|
||||
import { usePagination } from './hooks/usePagination';
|
||||
@@ -61,15 +61,20 @@
|
||||
import { createTableContext } from './hooks/useTableContext';
|
||||
import { useTableFooter } from './hooks/useTableFooter';
|
||||
import { useTableForm } from './hooks/useTableForm';
|
||||
import { useExpose } from '/@/hooks/core/useExpose';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
|
||||
import { basicProps } from './props';
|
||||
import { useExpose } from '/@/hooks/core/useExpose';
|
||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||
|
||||
import './style/index.less';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
export default defineComponent({
|
||||
props: basicProps,
|
||||
components: { Table, BasicForm },
|
||||
components: {
|
||||
Table,
|
||||
BasicForm,
|
||||
HeaderCell: createAsyncComponent(() => import('./components/HeaderCell.vue')),
|
||||
},
|
||||
emits: [
|
||||
'fetch-success',
|
||||
'fetch-error',
|
||||
@@ -80,6 +85,8 @@
|
||||
'row-contextmenu',
|
||||
'row-mouseenter',
|
||||
'row-mouseleave',
|
||||
'edit-end',
|
||||
'edit-cancel',
|
||||
],
|
||||
setup(props, { attrs, emit, slots }) {
|
||||
const tableElRef = ref<ComponentRef>(null);
|
||||
@@ -96,32 +103,6 @@
|
||||
|
||||
const { getLoading, setLoading } = useLoading(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 {
|
||||
getRowSelection,
|
||||
@@ -133,6 +114,33 @@
|
||||
setSelectedRowKeys,
|
||||
} = 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(
|
||||
getProps,
|
||||
tableElRef,
|
||||
@@ -178,7 +186,7 @@
|
||||
tableLayout: 'fixed',
|
||||
rowSelection: unref(getRowSelectionRef),
|
||||
rowKey: unref(getRowKey),
|
||||
columns: unref(getSortFixedColumns),
|
||||
columns: unref(getViewColumns),
|
||||
pagination: unref(getPaginationInfo),
|
||||
dataSource: unref(getDataSourceRef),
|
||||
footer: unref(getFooterProps),
|
||||
@@ -197,26 +205,6 @@
|
||||
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>) {
|
||||
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
|
||||
}
|
||||
@@ -239,6 +227,8 @@
|
||||
getPaginationRef: getPagination,
|
||||
getColumns,
|
||||
getCacheColumns,
|
||||
emit,
|
||||
updateTableData,
|
||||
getSize: () => {
|
||||
return unref(getBindValues).size as SizeType;
|
||||
},
|
||||
@@ -265,6 +255,7 @@
|
||||
replaceFormSlotKey,
|
||||
getFormSlotKeys,
|
||||
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 { ComponentType } from './types/componentType';
|
||||
import type { ComponentType } from './types/componentType';
|
||||
import { ApiSelect } from '/@/components/Form';
|
||||
|
||||
const componentMap = new Map<ComponentType, Component>();
|
||||
|
||||
componentMap.set('Input', Input);
|
||||
componentMap.set('InputPassword', Input.Password);
|
||||
componentMap.set('InputNumber', InputNumber);
|
||||
|
||||
componentMap.set('Select', Select);
|
||||
componentMap.set('ApiSelect', ApiSelect);
|
||||
componentMap.set('Switch', Switch);
|
||||
componentMap.set('Checkbox', Checkbox);
|
||||
componentMap.set('CheckboxGroup', Checkbox.Group);
|
||||
|
||||
export function add(compName: ComponentType, component: Component) {
|
||||
componentMap.set(compName, component);
|
||||
|
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<span>
|
||||
<slot />
|
||||
{{ title }}
|
||||
<FormOutlined class="ml-2" />
|
||||
<FormOutlined />
|
||||
</span>
|
||||
</template>
|
||||
<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>
|
||||
<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">
|
||||
<Icon :icon="action.icon" class="mr-1" v-if="action.icon" />
|
||||
{{ action.label }}
|
||||
</PopConfirmButton>
|
||||
<Divider type="vertical" v-if="divider && index < getActions.length" />
|
||||
</template>
|
||||
|
||||
<Dropdown :trigger="['hover']" :dropMenuList="getDropList">
|
||||
<Dropdown :trigger="['hover']" :dropMenuList="getDropList" v-if="dropDownActions">
|
||||
<slot name="more" />
|
||||
<a-button type="link" size="small" v-if="!$slots.more">
|
||||
<MoreOutlined class="icon-more" />
|
||||
@@ -61,7 +60,7 @@
|
||||
});
|
||||
|
||||
const getDropList = computed(() => {
|
||||
return props.dropDownActions.map((action, index) => {
|
||||
return (props.dropDownActions || []).map((action, index) => {
|
||||
const { label } = action;
|
||||
return {
|
||||
...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[] = [];
|
||||
table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => {
|
||||
ret.push({
|
||||
label: item.title as string,
|
||||
label: (item.title as string) || (item.customTitle as string),
|
||||
value: (item.dataIndex || item.title) as string,
|
||||
...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';
|
||||
|
||||
|
@@ -1,10 +1,13 @@
|
||||
import { BasicColumn, BasicTableProps, GetColumnsParams } from '../types/table';
|
||||
import { PaginationProps } from '../types/pagination';
|
||||
import type { BasicColumn, BasicTableProps, CellFormat, GetColumnsParams } from '../types/table';
|
||||
import type { PaginationProps } from '../types/pagination';
|
||||
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 { useI18n } from '/@/hooks/web/useI18n';
|
||||
import { isEqual, cloneDeep } from 'lodash-es';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
import { formatToDate } from '/@/utils/dateUtil';
|
||||
import { renderEditCell } from '../components/editable';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -127,8 +130,30 @@ export function useColumns(
|
||||
return columns;
|
||||
});
|
||||
|
||||
const getSortFixedColumns = computed(() => {
|
||||
return useFixedColumn(unref(getColumnsRef));
|
||||
const getViewColumns = computed(() => {
|
||||
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(() => {
|
||||
@@ -191,7 +216,7 @@ export function useColumns(
|
||||
}
|
||||
|
||||
if (sort) {
|
||||
columns = useFixedColumn(columns);
|
||||
columns = sortFixedColumn(columns);
|
||||
}
|
||||
|
||||
return columns;
|
||||
@@ -200,10 +225,10 @@ export function useColumns(
|
||||
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 fixedRightColumns: BasicColumn[] = [];
|
||||
const defColumns: BasicColumn[] = [];
|
||||
@@ -224,3 +249,35 @@ export function useFixedColumn(columns: BasicColumn[]) {
|
||||
|
||||
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 { ref, unref, ComputedRef, computed, onMounted, watchEffect } from 'vue';
|
||||
import { ref, unref, ComputedRef, computed, onMounted, watchEffect, reactive } from 'vue';
|
||||
|
||||
import { useTimeoutFn } from '/@/hooks/core/useTimeout';
|
||||
|
||||
@@ -16,12 +16,28 @@ interface ActionType {
|
||||
setPagination: (info: Partial<PaginationProps>) => void;
|
||||
setLoading: (loading: boolean) => void;
|
||||
getFieldsValue: () => Recordable;
|
||||
clearSelectedRowKeys: () => void;
|
||||
}
|
||||
|
||||
interface SearchState {
|
||||
sortInfo: Recordable;
|
||||
filterInfo: Record<string, string[]>;
|
||||
}
|
||||
export function useDataSource(
|
||||
propsRef: ComputedRef<BasicTableProps>,
|
||||
{ getPaginationInfo, setPagination, setLoading, getFieldsValue }: ActionType,
|
||||
{
|
||||
getPaginationInfo,
|
||||
setPagination,
|
||||
setLoading,
|
||||
getFieldsValue,
|
||||
clearSelectedRowKeys,
|
||||
}: ActionType,
|
||||
emit: EmitType
|
||||
) {
|
||||
const searchState = reactive<SearchState>({
|
||||
sortInfo: {},
|
||||
filterInfo: {},
|
||||
});
|
||||
const dataSourceRef = ref<Recordable[]>([]);
|
||||
|
||||
watchEffect(() => {
|
||||
@@ -29,6 +45,32 @@ export function useDataSource(
|
||||
!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[]) {
|
||||
if (!items || !Array.isArray(items)) return;
|
||||
items.forEach((item) => {
|
||||
@@ -75,6 +117,14 @@ export function useDataSource(
|
||||
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) {
|
||||
const { api, searchInfo, fetchSetting, beforeFetch, afterFetch, useSearchForm } = unref(
|
||||
propsRef
|
||||
@@ -94,6 +144,8 @@ export function useDataSource(
|
||||
pageParams[sizeField] = pageSize;
|
||||
}
|
||||
|
||||
const { sortInfo = {}, filterInfo } = searchState;
|
||||
|
||||
let params: Recordable = {
|
||||
...pageParams,
|
||||
...(useSearchForm ? getFieldsValue() : {}),
|
||||
@@ -101,6 +153,8 @@ export function useDataSource(
|
||||
...(opt ? opt.searchInfo : {}),
|
||||
...(opt ? opt.sortInfo : {}),
|
||||
...(opt ? opt.filterInfo : {}),
|
||||
...sortInfo,
|
||||
...filterInfo,
|
||||
};
|
||||
if (beforeFetch && isFunction(beforeFetch)) {
|
||||
params = beforeFetch(params) || params;
|
||||
@@ -175,5 +229,7 @@ export function useDataSource(
|
||||
getAutoCreateKey,
|
||||
fetch,
|
||||
reload,
|
||||
updateTableData,
|
||||
handleTableChange,
|
||||
};
|
||||
}
|
||||
|
@@ -1,19 +1,27 @@
|
||||
import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table';
|
||||
import type { PaginationProps } from '../types/pagination';
|
||||
import type { DynamicProps } from '/@/types/utils';
|
||||
import { getDynamicProps } from '/@/utils';
|
||||
|
||||
import { ref, onUnmounted, unref } from 'vue';
|
||||
import { isProdMode } from '/@/utils/env';
|
||||
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(
|
||||
tableProps?: Partial<BasicTableProps>
|
||||
): [(instance: TableActionType) => void, TableActionType] {
|
||||
tableProps?: Props
|
||||
): [(instance: TableActionType, formInstance: FormActionType) => void, TableActionType] {
|
||||
isInSetup();
|
||||
|
||||
const tableRef = ref<Nullable<TableActionType>>(null);
|
||||
const loadedRef = ref<Nullable<boolean>>(false);
|
||||
const formRef = ref<Nullable<FormActionType>>(null);
|
||||
|
||||
function register(instance: TableActionType) {
|
||||
function register(instance: TableActionType, formInstance: FormActionType) {
|
||||
isProdMode() &&
|
||||
onUnmounted(() => {
|
||||
tableRef.value = null;
|
||||
@@ -24,20 +32,29 @@ export function useTable(
|
||||
return;
|
||||
}
|
||||
tableRef.value = instance;
|
||||
tableProps && instance.setProps(tableProps);
|
||||
formRef.value = formInstance;
|
||||
// tableProps && instance.setProps(tableProps);
|
||||
loadedRef.value = true;
|
||||
|
||||
watchEffect(() => {
|
||||
tableProps && instance.setProps(getDynamicProps(tableProps));
|
||||
});
|
||||
}
|
||||
|
||||
function getTableInstance(): TableActionType {
|
||||
const table = unref(tableRef);
|
||||
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 = {
|
||||
reload: (opt?: FetchParams) => {
|
||||
const methods: TableActionType & {
|
||||
getForm: () => FormActionType;
|
||||
} = {
|
||||
reload: async (opt?: FetchParams) => {
|
||||
getTableInstance().reload(opt);
|
||||
},
|
||||
setProps: (props: Partial<BasicTableProps>) => {
|
||||
@@ -54,7 +71,6 @@ export function useTable(
|
||||
},
|
||||
getColumns: ({ ignoreIndex = false }: { ignoreIndex?: boolean } = {}) => {
|
||||
const columns = getTableInstance().getColumns({ ignoreIndex }) || [];
|
||||
|
||||
return columns;
|
||||
},
|
||||
setColumns: (columns: BasicColumn[]) => {
|
||||
@@ -87,7 +103,19 @@ export function useTable(
|
||||
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];
|
||||
}
|
||||
|
@@ -121,7 +121,7 @@ export function useTableScroll(
|
||||
width += 60;
|
||||
}
|
||||
|
||||
// TODO props
|
||||
// TODO propsdth ?? 0;
|
||||
const NORMAL_WIDTH = 150;
|
||||
|
||||
const columns = unref(columnsRef);
|
||||
@@ -135,7 +135,10 @@ export function useTableScroll(
|
||||
if (len !== 0) {
|
||||
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(() => {
|
||||
|
@@ -9,21 +9,29 @@ import type {
|
||||
TableRowSelection,
|
||||
} from './types/table';
|
||||
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';
|
||||
|
||||
// 注释看 types/table
|
||||
export const basicProps = {
|
||||
clickToRowSelect: propTypes.bool.def(true),
|
||||
|
||||
tableSetting: {
|
||||
type: Object as PropType<TableSetting>,
|
||||
},
|
||||
|
||||
inset: propTypes.bool,
|
||||
|
||||
sortFn: {
|
||||
type: Function as PropType<(sortInfo: SorterResult) => any>,
|
||||
default: DEFAULT_SORT_FN,
|
||||
},
|
||||
|
||||
filterFn: {
|
||||
type: Function as PropType<(data: Partial<Recordable<string[]>>) => any>,
|
||||
default: DEFAULT_FILTER_FN,
|
||||
},
|
||||
|
||||
showTableSetting: propTypes.bool,
|
||||
autoCreateKey: 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,14 +133,18 @@
|
||||
overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
.ant-table-fixed-right .ant-table-header {
|
||||
border-left: 1px solid @border-color !important;
|
||||
.ant-table-fixed-right {
|
||||
right: -1px;
|
||||
|
||||
.ant-table-fixed {
|
||||
border-bottom: none;
|
||||
.ant-table-header {
|
||||
border-left: 1px solid @border-color !important;
|
||||
|
||||
.ant-table-thead th {
|
||||
background: rgb(241, 243, 244);
|
||||
.ant-table-fixed {
|
||||
border-bottom: none;
|
||||
|
||||
.ant-table-thead th {
|
||||
background: rgb(241, 243, 244);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,7 @@
|
||||
export type ComponentType =
|
||||
| 'Input'
|
||||
| 'InputPassword'
|
||||
| 'InputNumber'
|
||||
| 'Select'
|
||||
| 'ApiSelect'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'Switch';
|
||||
|
@@ -6,9 +6,10 @@ import type {
|
||||
TableRowSelection as ITableRowSelection,
|
||||
} from 'ant-design-vue/lib/table/interface';
|
||||
import { ComponentType } from './componentType';
|
||||
import { VueNode } from '/@/utils/propTypes';
|
||||
// import { ColumnProps } from './column';
|
||||
export declare type SortOrder = 'ascend' | 'descend';
|
||||
export interface TableCurrentDataSource<T = any> {
|
||||
export interface TableCurrentDataSource<T = Recordable> {
|
||||
currentDataSource: T[];
|
||||
}
|
||||
|
||||
@@ -53,7 +54,7 @@ export interface ColumnFilterItem {
|
||||
children?: any;
|
||||
}
|
||||
|
||||
export interface TableCustomRecord<T = any> {
|
||||
export interface TableCustomRecord<T = Recordable> {
|
||||
record?: T;
|
||||
index?: number;
|
||||
}
|
||||
@@ -65,18 +66,11 @@ export interface SorterResult {
|
||||
columnKey: string;
|
||||
}
|
||||
|
||||
export interface RenderEditableCellParams {
|
||||
dataIndex: string;
|
||||
component?: ComponentType;
|
||||
componentProps?: any;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export interface FetchParams {
|
||||
searchInfo?: any;
|
||||
searchInfo?: Recordable;
|
||||
page?: number;
|
||||
sortInfo?: any;
|
||||
filterInfo?: any;
|
||||
sortInfo?: Recordable;
|
||||
filterInfo?: Recordable;
|
||||
}
|
||||
|
||||
export interface GetColumnsParams {
|
||||
@@ -89,7 +83,7 @@ export type SizeType = 'default' | 'middle' | 'small' | 'large';
|
||||
|
||||
export interface TableActionType {
|
||||
reload: (opt?: FetchParams) => Promise<void>;
|
||||
getSelectRows: <T = any>() => T[];
|
||||
getSelectRows: <T = Recordable>() => T[];
|
||||
clearSelectedRowKeys: () => void;
|
||||
getSelectRowKeys: () => string[];
|
||||
deleteSelectRowByKey: (key: string) => void;
|
||||
@@ -106,6 +100,8 @@ export interface TableActionType {
|
||||
getSize: () => SizeType;
|
||||
getRowSelection: () => TableRowSelection<Recordable>;
|
||||
getCacheColumns: () => BasicColumn[];
|
||||
emit?: EmitType;
|
||||
updateTableData: (index: number, key: string, value: any) => Recordable;
|
||||
}
|
||||
|
||||
export interface FetchSetting {
|
||||
@@ -131,6 +127,8 @@ export interface BasicTableProps<T = any> {
|
||||
clickToRowSelect?: boolean;
|
||||
// 自定义排序方法
|
||||
sortFn?: (sortInfo: SorterResult) => any;
|
||||
// 排序方法
|
||||
filterFn?: (data: Partial<Recordable<string[]>>) => any;
|
||||
// 取消表格的默认padding
|
||||
inset?: boolean;
|
||||
// 显示表格设置
|
||||
@@ -141,7 +139,7 @@ export interface BasicTableProps<T = any> {
|
||||
// 是否自动生成key
|
||||
autoCreateKey?: boolean;
|
||||
// 计算合计行的方法
|
||||
summaryFunc?: (...arg: any) => any[];
|
||||
summaryFunc?: (...arg: any) => Recordable[];
|
||||
// 是否显示合计行
|
||||
showSummary?: boolean;
|
||||
// 是否可拖拽列
|
||||
@@ -374,13 +372,43 @@ export interface BasicTableProps<T = any> {
|
||||
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 {
|
||||
children?: BasicColumn[];
|
||||
filters?: {
|
||||
text: string;
|
||||
value: string;
|
||||
children?:
|
||||
| unknown[]
|
||||
| (((props: Record<string, unknown>) => unknown[]) & (() => unknown[]) & (() => unknown[]));
|
||||
}[];
|
||||
|
||||
//
|
||||
flag?: 'INDEX' | 'DEFAULT' | 'CHECKBOX' | 'RADIO' | 'ACTION';
|
||||
customTitle?: VueNode;
|
||||
|
||||
slots?: Indexable;
|
||||
|
||||
// Whether to hide the column by default, it can be displayed in the column configuration
|
||||
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;
|
||||
top: 10px;
|
||||
right: 30px;
|
||||
|
||||
&--dot {
|
||||
top: 50%;
|
||||
margin-top: -3px;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
|
@@ -52,6 +52,9 @@ const menu: MenuModule = {
|
||||
{
|
||||
path: 'table',
|
||||
name: t('routes.demo.table.table'),
|
||||
tag: {
|
||||
dot: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'basic',
|
||||
@@ -108,10 +111,16 @@ const menu: MenuModule = {
|
||||
{
|
||||
path: 'editCellTable',
|
||||
name: t('routes.demo.table.editCellTable'),
|
||||
tag: {
|
||||
dot: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '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_FORMAT = 'YYYY-MM-DD ';
|
||||
|
||||
export function formatToDateTime(date: moment.MomentInput = null): string {
|
||||
return moment(date).format(DATE_TIME_FORMAT);
|
||||
export function formatToDateTime(
|
||||
date: moment.MomentInput = null,
|
||||
format = DATE_TIME_FORMAT
|
||||
): string {
|
||||
return moment(date).format(format);
|
||||
}
|
||||
|
||||
export function formatToDate(date: moment.MomentInput = null): string {
|
||||
return moment(date).format(DATE_FORMAT);
|
||||
export function formatToDate(date: moment.MomentInput = null, format = DATE_FORMAT): string {
|
||||
return moment(date).format(format);
|
||||
}
|
||||
|
||||
export const formatAgo = (str: string | number) => {
|
||||
|
@@ -1,45 +1,108 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<BasicTable @register="registerTable">
|
||||
<template #customId>
|
||||
<EditTableHeaderIcon title="Id" />
|
||||
</template>
|
||||
<template #customName>
|
||||
<EditTableHeaderIcon title="姓名" />
|
||||
</template>
|
||||
<BasicTable @register="registerTable" @edit-end="handleEditEnd" @edit-cancel="handleEditCancel">
|
||||
</BasicTable>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import {
|
||||
BasicTable,
|
||||
useTable,
|
||||
BasicColumn,
|
||||
renderEditableCell,
|
||||
EditTableHeaderIcon,
|
||||
} from '/@/components/Table';
|
||||
import { BasicTable, useTable, BasicColumn, EditTableHeaderIcon } from '/@/components/Table';
|
||||
import { optionsListApi } from '/@/api/demo/select';
|
||||
|
||||
import { demoListApi } from '/@/api/demo/table';
|
||||
const columns: BasicColumn[] = [
|
||||
{
|
||||
// title: 'ID',
|
||||
dataIndex: 'id',
|
||||
slots: { title: 'customId' },
|
||||
customRender: renderEditableCell({ dataIndex: 'id' }),
|
||||
},
|
||||
{
|
||||
// title: '姓名',
|
||||
title: '输入框',
|
||||
dataIndex: 'name',
|
||||
slots: { title: 'customName' },
|
||||
customRender: renderEditableCell({
|
||||
dataIndex: 'name',
|
||||
}),
|
||||
edit: true,
|
||||
editComponentProps: {
|
||||
prefix: '$',
|
||||
},
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '地址',
|
||||
dataIndex: 'address',
|
||||
sorter: true,
|
||||
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',
|
||||
edit: true,
|
||||
editRule: true,
|
||||
editComponent: 'InputNumber',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '下拉框',
|
||||
dataIndex: 'name3',
|
||||
edit: true,
|
||||
editComponent: 'Select',
|
||||
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: '勾选框',
|
||||
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({
|
||||
@@ -50,10 +113,21 @@
|
||||
api: demoListApi,
|
||||
columns: columns,
|
||||
showIndexColumn: false,
|
||||
bordered: true,
|
||||
});
|
||||
|
||||
function handleEditEnd({ record, index, key, value }: Recordable) {
|
||||
console.log(record, index, key, value);
|
||||
}
|
||||
|
||||
function handleEditCancel() {
|
||||
console.log('cancel');
|
||||
}
|
||||
|
||||
return {
|
||||
registerTable,
|
||||
handleEditEnd,
|
||||
handleEditCancel,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@@ -15,24 +15,105 @@
|
||||
TableAction,
|
||||
BasicColumn,
|
||||
ActionItem,
|
||||
renderEditableRow,
|
||||
EditTableHeaderIcon,
|
||||
EditRecordRow,
|
||||
} from '/@/components/Table';
|
||||
import { optionsListApi } from '/@/api/demo/select';
|
||||
|
||||
import { demoListApi } from '/@/api/demo/table';
|
||||
const columns: BasicColumn[] = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
customRender: renderEditableRow({ dataIndex: 'id' }),
|
||||
title: '输入框',
|
||||
dataIndex: 'name',
|
||||
editRow: true,
|
||||
editComponentProps: {
|
||||
prefix: '$',
|
||||
},
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name',
|
||||
customRender: renderEditableRow({
|
||||
dataIndex: 'name',
|
||||
}),
|
||||
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',
|
||||
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({
|
||||
@@ -55,19 +136,19 @@
|
||||
|
||||
function handleEdit(record: EditRecordRow) {
|
||||
currentEditKeyRef.value = record.key;
|
||||
record.editable = true;
|
||||
record.onEdit?.(true);
|
||||
}
|
||||
|
||||
function handleCancel(record: EditRecordRow) {
|
||||
currentEditKeyRef.value = '';
|
||||
record.editable = false;
|
||||
record.onCancel && record.onCancel();
|
||||
record.onEdit?.(false, true);
|
||||
}
|
||||
|
||||
function handleSave(record: EditRecordRow) {
|
||||
currentEditKeyRef.value = '';
|
||||
record.editable = false;
|
||||
record.onSubmit && record.onSubmit();
|
||||
async function handleSave(record: EditRecordRow) {
|
||||
const pass = await record.onEdit?.(false, true);
|
||||
if (pass) {
|
||||
currentEditKeyRef.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function createActions(record: EditRecordRow, column: BasicColumn): ActionItem[] {
|
||||
|
@@ -41,7 +41,6 @@
|
||||
{
|
||||
title: '地址',
|
||||
dataIndex: 'address',
|
||||
width: 260,
|
||||
},
|
||||
{
|
||||
title: '编号',
|
||||
@@ -67,6 +66,7 @@
|
||||
api: demoListApi,
|
||||
columns: columns,
|
||||
rowSelection: { type: 'radio' },
|
||||
bordered: true,
|
||||
actionColumn: {
|
||||
width: 160,
|
||||
title: 'Action',
|
||||
|
@@ -7,12 +7,16 @@ export function getBasicColumns(): BasicColumn[] {
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
fixed: 'left',
|
||||
width: 400,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name',
|
||||
width: 150,
|
||||
filters: [
|
||||
{ text: 'Male', value: 'male' },
|
||||
{ text: 'Female', value: 'female' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '地址',
|
||||
@@ -22,11 +26,13 @@ export function getBasicColumns(): BasicColumn[] {
|
||||
title: '编号',
|
||||
dataIndex: 'no',
|
||||
width: 150,
|
||||
sorter: true,
|
||||
defaultHidden: true,
|
||||
},
|
||||
{
|
||||
title: '开始时间',
|
||||
width: 120,
|
||||
sorter: true,
|
||||
dataIndex: 'beginTime',
|
||||
},
|
||||
{
|
||||
|
Reference in New Issue
Block a user