提交 9c2f3f30 编写于 作者: V vben

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

上级 f3a70eed
## 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,15 +103,19 @@
const { getLoading, setLoading } = useLoading(getProps);
const { getPaginationInfo, getPagination, setPagination } = usePagination(getProps);
const {
getSortFixedColumns,
getColumns,
setColumns,
getColumnsRef,
getCacheColumns,
} = useColumns(getProps, getPaginationInfo);
getRowSelection,
getRowSelectionRef,
getSelectRows,
clearSelectedRowKeys,
getSelectRowKeys,
deleteSelectRowByKey,
setSelectedRowKeys,
} = useRowSelection(getProps, emit);
const {
handleTableChange,
getDataSourceRef,
getDataSource,
setTableData,
......@@ -112,6 +123,7 @@
getRowKey,
reload,
getAutoCreateKey,
updateTableData,
} = useDataSource(
getProps,
{
......@@ -119,19 +131,15 @@
setLoading,
setPagination,
getFieldsValue: formActions.getFieldsValue,
clearSelectedRowKeys,
},
emit
);
const {
getRowSelection,
getRowSelectionRef,
getSelectRows,
clearSelectedRowKeys,
getSelectRowKeys,
deleteSelectRowByKey,
setSelectedRowKeys,
} = useRowSelection(getProps, emit);
const { getViewColumns, getColumns, setColumns, getColumnsRef, getCacheColumns } = useColumns(
getProps,
getPaginationInfo
);
const { getScrollRef, redoHeight } = useTableScroll(
getProps,
......@@ -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,
};
},
});
......
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);
......
<template>
<span>
<slot />
{{ title }}
<FormOutlined class="ml-2" />
<FormOutlined />
</span>
</template>
<script lang="ts">
......
<template>
<EditTableHeaderCell v-if="getIsEdit">
{{ getTitle }}
</EditTableHeaderCell>
<span v-else>{{ getTitle }}</span>
<BasicHelp v-if="getHelpMessage" :text="getHelpMessage" :class="`${prefixCls}__help`" />
</template>
<script lang="ts">
import type { PropType } from 'vue';
import type { BasicColumn } from '../types/table';
import { defineComponent, computed } from 'vue';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
import { useDesign } from '/@/hooks/web/useDesign';
export default defineComponent({
name: 'TableHeaderCell',
components: {
EditTableHeaderCell: createAsyncComponent(() => import('./EditTableHeaderIcon.vue')),
BasicHelp: createAsyncComponent(() => import('/@/components/Basic/src/BasicHelp.vue')),
},
props: {
column: {
type: Object as PropType<BasicColumn>,
default: {},
},
},
setup(props) {
const { prefixCls } = useDesign('basic-table-header-cell');
const getIsEdit = computed(() => {
return !!props.column?.edit;
});
const getTitle = computed(() => {
return props.column?.customTitle;
});
const getHelpMessage = computed(() => {
return props.column?.helpMessage;
});
return { prefixCls, getIsEdit, getTitle, getHelpMessage };
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-table-header-cell';
.@{prefix-cls} {
&__help {
margin-left: 8px;
color: rgba(0, 0, 0, 0.65) !important;
}
}
</style>
<template>
<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,
......
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,
}
);
};
<template>
<div :class="prefixCls">
<div v-show="!isEdit" :class="`${prefixCls}__normal`" @click="handleEdit">
{{ value || '&nbsp;' }}
<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>
import { ComponentType } from '../../types/componentType';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
/**
* @description: 生成placeholder
*/
export function createPlaceholderMessage(component: ComponentType) {
if (component.includes('Input')) {
return t('component.form.input');
}
if (component.includes('Picker')) {
return t('component.form.choose');
}
if (
component.includes('Select') ||
component.includes('Checkbox') ||
component.includes('Radio') ||
component.includes('Switch')
) {
return t('component.form.choose');
}
return '';
}
import type { BasicColumn } from '/@/components/Table/src/types/table';
import { h } from 'vue';
import EditableCell from './EditableCell.vue';
interface Params {
text: string;
record: Recordable;
index: number;
}
export function renderEditCell(column: BasicColumn) {
return ({ text: value, record, index }: Params) => {
record.onEdit = async (edit: boolean, submit = false) => {
if (!submit) {
record.editable = edit;
}
if (!edit && submit) {
const res = await record.onSubmitEdit?.();
if (res) {
record.editable = false;
return true;
}
return false;
}
// cancel
if (!edit && !submit) {
record.onCancelEdit?.();
}
return true;
};
return h(EditableCell, {
value,
record,
column,
index,
});
};
}
export type EditRecordRow<T = Hash<any>> = {
onEdit: (editable: boolean, submit?: boolean) => Promise<boolean>;
editable: boolean;
onCancel: Fn;
onSubmit: Fn;
submitCbs: Fn[];
cancelCbs: Fn[];
validCbs: Fn[];
} & T;
import '../style/editable-cell.less';
import { defineComponent, PropType, ref, unref, nextTick, watchEffect } from 'vue';
import { ClickOutSide } from '/@/components/ClickOutSide';
import { RenderEditableCellParams } from '../types/table';
import { ComponentType } from '../types/componentType';
import { componentMap } from '../componentMap';
import { isString, isBoolean, isArray } from '/@/utils/is';
import { FormOutlined, CloseOutlined, CheckOutlined } from '@ant-design/icons-vue';
const prefixCls = 'editable-cell';
const EditableCell = defineComponent({
name: 'EditableCell',
props: {
value: {
type: String as PropType<string>,
default: '',
},
componentProps: {
type: Object as PropType<any>,
default: null,
},
dataKey: {
type: String as PropType<string>,
default: '',
},
dataIndex: {
type: String as PropType<string>,
default: '',
},
component: {
type: String as PropType<ComponentType>,
default: 'Input',
},
editable: {
type: Boolean as PropType<boolean>,
default: false,
},
editRow: {
type: Boolean as PropType<boolean>,
default: false,
},
record: {
type: Object as PropType<EditRecordRow>,
},
placeholder: {
type: String as PropType<string>,
default: '',
},
},
emits: ['submit', 'cancel'],
setup(props, { attrs, emit }) {
const elRef = ref<any>(null);
const isEditRef = ref(false);
const currentValueRef = ref<string | boolean>(props.value);
const defaultValueRef = ref<string | boolean>(props.value);
watchEffect(() => {
defaultValueRef.value = props.value;
if (isBoolean(props.editable)) {
isEditRef.value = props.editable;
}
});
function handleChange(e: any) {
if (e && e.target && Reflect.has(e.target, 'value')) {
currentValueRef.value = (e as ChangeEvent).target.value;
}
if (isString(e) || isBoolean(e)) {
currentValueRef.value = e;
}
}
function handleEdit() {
isEditRef.value = true;
nextTick(() => {
const el = unref(elRef);
el && el.focus();
});
}
function handleCancel() {
isEditRef.value = false;
currentValueRef.value = defaultValueRef.value;
emit('cancel');
}
if (props.record) {
/* eslint-disable */
isArray(props.record.submitCbs)
? props.record.submitCbs.push(handleSubmit)
: (props.record.submitCbs = [handleSubmit]);
/* eslint-disable */
isArray(props.record.cancelCbs)
? props.record.cancelCbs.push(handleCancel)
: (props.record.cancelCbs = [handleCancel]);
/* eslint-disable */
props.record.onCancel = () => {
isArray(props.record?.cancelCbs) && props.record?.cancelCbs.forEach((fn) => fn());
};
/* eslint-disable */
props.record.onSubmit = () => {
isArray(props.record?.submitCbs) && props.record?.submitCbs.forEach((fn) => fn());
};
}
function handleSubmit() {
const { dataKey, dataIndex } = props;
if (!dataKey || !dataIndex) return;
if (props.record) {
/* eslint-disable */
props.record[dataIndex] = unref(currentValueRef) as string;
}
isEditRef.value = false;
}
function onClickOutside() {
if (props.editRow) return;
const { component } = props;
if (component && component.includes('Input')) {
handleCancel();
}
}
function renderValue() {
const { value } = props;
if (props.editRow) {
return !unref(isEditRef) ? value : null;
}
return (
!unref(isEditRef) && (
<div class={`${prefixCls}__normal`} onClick={handleEdit}>
{value}
<FormOutlined class={`${prefixCls}__normal-icon`} />
</div>
)
);
}
return () => {
const { component, componentProps = {} } = props;
const Comp = componentMap.get(component!) as any;
return (
<div class={prefixCls}>
{unref(isEditRef) && (
<ClickOutSide onClickOutside={onClickOutside}>
{() => (
<div class={`${prefixCls}__wrapper`}>
<Comp
placeholder={props.placeholder}
{...{
...attrs,
...componentProps,
}}
style={{ width: 'calc(100% - 48px)' }}
ref={elRef}
value={unref(currentValueRef)}
size="small"
onChange={handleChange}
onPressEnter={handleSubmit}
/>
{!props.editRow && (
<div class={`${prefixCls}__action`}>
<CheckOutlined
class={[`${prefixCls}__icon`, 'mx-2']}
onClick={handleSubmit}
/>
<CloseOutlined class={[`${prefixCls}__icon `]} onClick={handleCancel} />
</div>
)}
</div>
)}
</ClickOutSide>
)}
{renderValue()}
</div>
);
};
},
});
export function renderEditableCell({
dataIndex,
component,
componentProps = {},
placeholder,
}: RenderEditableCellParams) {
return ({ text, record }: { text: string; record: EditRecordRow }) => {
return (
<EditableCell
{...componentProps}
placeholder={placeholder}
value={text}
record={record}
dataKey={record.key}
dataIndex={dataIndex}
component={component}
/>
);
};
}
export function renderEditableRow({
dataIndex,
component,
componentProps = {},
placeholder,
}: RenderEditableCellParams) {
return ({ text, record }: { text: string; record: EditRecordRow }) => {
return (
<EditableCell
{...componentProps}
value={text}
placeholder={placeholder}
editRow={true}
editable={record.editable}
dataKey={record.key}
record={record}
dataIndex={dataIndex}
component={component}
/>
);
};
}
export type EditRecordRow<T = Hash<any>> = {
editable: boolean;
onCancel: Fn;
onSubmit: Fn;
submitCbs: Fn[];
cancelCbs: Fn[];
} & T;
import { BasicArrow } from '/@/components/Basic';
export default () => {
return (props: Recordable) => {
return (
<BasicArrow
onClick={(e: Event) => {
props.onExpand(props.record, e);
}}
expand={props.expanded}
/>
);
};
};
......@@ -184,7 +184,7 @@
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';
......
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;
}
}
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,
};
}
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),
......
@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-fixed {
border-bottom: none;
.ant-table-thead th {
background: rgb(241, 243, 244);
.ant-table-thead th {
background: rgb(241, 243, 244);
}
}
}
}
......
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) => {
......
<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',
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',
slots: { title: 'customId' },
customRender: renderEditableCell({ dataIndex: 'id' }),
edit: true,
editRule: true,
editComponent: 'InputNumber',
width: 200,
},
{
// title: '姓名',
dataIndex: 'name',
slots: { title: 'customName' },
customRender: renderEditableCell({
dataIndex: 'name',
}),
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: 'address',
sorter: true,
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',
title: '输入框',
dataIndex: 'name',
editRow: true,
editComponentProps: {
prefix: '$',
},
width: 200,
},
{
title: '默认输入状态',
dataIndex: 'name7',
editRow: true,
width: 200,
},
{
title: '输入框校验',
dataIndex: 'name1',
editRow: true,
// 默认必填校验
editRule: true,
width: 200,
},
{
title: '输入框函数校验',
dataIndex: 'name2',
editRow: true,
editRule: async (text) => {
if (text === '2') {
return '不能输入该值';
}
return '';
},
width: 200,
},
{
title: '数字输入框',
dataIndex: 'id',
customRender: renderEditableRow({ dataIndex: 'id' }),
editRow: true,
editRule: true,
editComponent: 'InputNumber',
width: 200,
},
{
title: '姓名',
dataIndex: 'name',
customRender: renderEditableRow({
dataIndex: 'name',
}),
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',
},
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册