更新vxetable框架并升级前端组件框架
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<template>
|
||||
<template>
|
||||
<el-form ref="formRef" :model="{ tableData }" :rules="rules" class="editable-table-form">
|
||||
<div
|
||||
v-if="showAddButton || showDeleteButton || searchFields.length > 0"
|
||||
@@ -33,22 +33,25 @@
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
<el-table
|
||||
<vxe-table
|
||||
ref="tableRef"
|
||||
:data="filteredTableData"
|
||||
:border="border"
|
||||
:border="border ? 'full' : false"
|
||||
:stripe="stripe"
|
||||
:max-height="maxHeight || undefined"
|
||||
:min-height="minHeight || undefined"
|
||||
:height="!maxHeight && !minHeight ? '100%' : undefined"
|
||||
:row-key="getRowKey"
|
||||
:virtualized="useVirtualized"
|
||||
:row-config="{ keyField: '_etKey' }"
|
||||
:scroll-x="{ enabled: true }"
|
||||
:scroll-y="{ enabled: true }"
|
||||
:show-overflow="true"
|
||||
v-bind="$attrs"
|
||||
@selection-change="handleSelectionChange"
|
||||
@checkbox-change="handleSelectionChange"
|
||||
@checkbox-all="handleSelectionChange"
|
||||
class="editable-table-inner"
|
||||
>
|
||||
<el-table-column v-if="showSelection" type="selection" width="55" align="center" />
|
||||
<el-table-column
|
||||
<vxe-column v-if="showSelection" type="checkbox" width="55" align="center" />
|
||||
<vxe-column
|
||||
v-if="showRowActions"
|
||||
:width="rowActionsColumnWidth"
|
||||
align="center"
|
||||
@@ -65,14 +68,14 @@
|
||||
</div>
|
||||
<span v-else></span>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<template #default="{ row, rowIndex }">
|
||||
<el-button
|
||||
v-if="showRowAddButton"
|
||||
type="primary"
|
||||
link
|
||||
icon="CirclePlus"
|
||||
class="action-btn"
|
||||
@click="handleAdd(scope.$index)"
|
||||
@click="handleAdd(rowIndex)"
|
||||
title="增加"
|
||||
/>
|
||||
<el-button
|
||||
@@ -81,38 +84,37 @@
|
||||
link
|
||||
icon="Delete"
|
||||
class="action-btn"
|
||||
@click="handleDelete(scope.$index)"
|
||||
@click="handleDelete(rowIndex)"
|
||||
title="删除"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</vxe-column>
|
||||
|
||||
<el-table-column
|
||||
<vxe-column
|
||||
v-for="col in filteredColumns"
|
||||
:key="col.prop"
|
||||
:prop="col.prop"
|
||||
:label="col.label"
|
||||
:field="col.prop"
|
||||
:title="col.label"
|
||||
:width="col.width"
|
||||
:min-width="col.minWidth"
|
||||
:fixed="col.fixed"
|
||||
:align="col.align || 'center'"
|
||||
:formatter="col.formatter"
|
||||
>
|
||||
<template #default="scope">
|
||||
<template #default="{ row, rowIndex }">
|
||||
<template v-if="col.type === 'input'">
|
||||
<el-form-item
|
||||
:prop="`tableData.${scope.$index}.${col.prop}`"
|
||||
:prop="`tableData.${rowIndex}.${col.prop}`"
|
||||
:rules="col.rules"
|
||||
style="margin-bottom: 0"
|
||||
>
|
||||
<el-input
|
||||
v-model="scope.row[col.prop]"
|
||||
v-model="row[col.prop]"
|
||||
:placeholder="col.placeholder || `请输入${col.label}`"
|
||||
:disabled="col.disabled"
|
||||
:clearable="col.clearable !== false"
|
||||
@blur="col.onBlur && col.onBlur(scope.row, scope.$index)"
|
||||
@input="col.onInput && col.onInput(scope.row, scope.$index)"
|
||||
@change="col.onChange && col.onChange(scope.row, scope.$index)"
|
||||
@blur="col.onBlur && col.onBlur(row, rowIndex)"
|
||||
@input="col.onInput && col.onInput(row, rowIndex)"
|
||||
@change="col.onChange && col.onChange(row, rowIndex)"
|
||||
>
|
||||
<template v-if="col.suffix" #suffix>{{ col.suffix }}</template>
|
||||
</el-input>
|
||||
@@ -121,12 +123,12 @@
|
||||
|
||||
<template v-else-if="col.type === 'number'">
|
||||
<el-form-item
|
||||
:prop="`tableData.${scope.$index}.${col.prop}`"
|
||||
:prop="`tableData.${rowIndex}.${col.prop}`"
|
||||
:rules="col.rules"
|
||||
style="margin-bottom: 0"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="scope.row[col.prop]"
|
||||
v-model="row[col.prop]"
|
||||
:placeholder="col.placeholder || `请输入${col.label}`"
|
||||
:disabled="col.disabled"
|
||||
:min="col.min"
|
||||
@@ -134,49 +136,49 @@
|
||||
:precision="col.precision"
|
||||
:controls="false"
|
||||
style="width: 100%"
|
||||
@change="col.onChange && col.onChange(scope.row, scope.$index)"
|
||||
@change="col.onChange && col.onChange(row, rowIndex)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<template v-else-if="col.type === 'select'">
|
||||
<el-form-item
|
||||
:prop="`tableData.${scope.$index}.${col.prop}`"
|
||||
:prop="`tableData.${rowIndex}.${col.prop}`"
|
||||
:rules="col.rules"
|
||||
style="margin-bottom: 0"
|
||||
>
|
||||
<el-select
|
||||
v-model="scope.row[col.prop]"
|
||||
v-model="row[col.prop]"
|
||||
:placeholder="col.placeholder || `请选择${col.label}`"
|
||||
:disabled="col.disabled"
|
||||
:clearable="col.clearable !== false"
|
||||
:filterable="col.filterable"
|
||||
:multiple="col.multiple"
|
||||
style="width: 100%"
|
||||
:class="scope.row.error ? 'error-border' : ''"
|
||||
:class="row.error ? 'error-border' : ''"
|
||||
@change="
|
||||
async (value) => {
|
||||
const checkBeforeChange = col.extraprops?.checkBeforeChange;
|
||||
if (checkBeforeChange && typeof checkBeforeChange === 'function') {
|
||||
const result = await checkBeforeChange(scope.row, scope.$index, value);
|
||||
const result = await checkBeforeChange(row, rowIndex, value);
|
||||
if (result === false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (col.onChange) {
|
||||
col.onChange(scope.row, scope.$index, value);
|
||||
col.onChange(row, rowIndex, value);
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in typeof col.options === 'function'
|
||||
? col.options(scope.row, scope.$index)
|
||||
? col.options(row, rowIndex)
|
||||
: col.options || []"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
@click="option.onClick && option.onClick(scope.row, option)"
|
||||
@click="option.onClick && option.onClick(row, option)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -184,43 +186,43 @@
|
||||
|
||||
<template v-else-if="col.type === 'date'">
|
||||
<el-form-item
|
||||
:prop="`tableData.${scope.$index}.${col.prop}`"
|
||||
:prop="`tableData.${rowIndex}.${col.prop}`"
|
||||
:rules="col.rules"
|
||||
style="margin-bottom: 0"
|
||||
>
|
||||
<el-date-picker
|
||||
v-model="scope.row[col.prop]"
|
||||
v-model="row[col.prop]"
|
||||
:type="col.dateType || 'date'"
|
||||
:placeholder="col.placeholder || `请选择${col.label}`"
|
||||
:disabled="col.disabled"
|
||||
:clearable="col.clearable !== false"
|
||||
:value-format="col.valueFormat || 'YYYY-MM-DD'"
|
||||
style="width: 100%"
|
||||
@change="col.onChange && col.onChange(scope.row, scope.$index)"
|
||||
@change="col.onChange && col.onChange(row, rowIndex)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<template v-else-if="col.type === 'slot'">
|
||||
<el-form-item
|
||||
:prop="`tableData.${scope.$index}.${col.prop}`"
|
||||
:prop="`tableData.${rowIndex}.${col.prop}`"
|
||||
:rules="col.rules"
|
||||
style="margin-bottom: 0"
|
||||
>
|
||||
<slot :name="col.slot || col.prop" :row="scope.row" :index="scope.$index" />
|
||||
<slot :name="col.slot || col.prop" :row="row" :index="rowIndex" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<span>{{
|
||||
col.formatter
|
||||
? col.formatter(scope.row, scope.column, scope.row[col.prop])
|
||||
: scope.row[col.prop]
|
||||
? col.formatter(row, { property: col.prop }, row[col.prop])
|
||||
: row[col.prop]
|
||||
}}</span>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
<div v-if="$slots.footer" class="editable-table-footer">
|
||||
<slot name="footer" :tableData="tableData" />
|
||||
</div>
|
||||
@@ -261,8 +263,8 @@ const emit = defineEmits<{
|
||||
'toolbar-delete': [rows: Record<string, any>[]];
|
||||
}>();
|
||||
|
||||
const formRef = ref<InstanceType<typeof import('element-plus').ElForm> | null>(null);
|
||||
const tableRef = ref<InstanceType<typeof import('element-plus').ElTable> | null>(null);
|
||||
const formRef = ref<any>(null);
|
||||
const tableRef = ref<any>(null);
|
||||
const selectedRows = ref<Record<string, any>[]>([]);
|
||||
const searchKeyword = ref('');
|
||||
|
||||
@@ -293,13 +295,12 @@ const filteredColumns = computed(() => {
|
||||
return props.columns.filter((col) => !col.vIf || col.vIf());
|
||||
});
|
||||
|
||||
// 行操作列宽度:同时显示“增加+删除”则宽一点;只显示一个则缩窄
|
||||
// 行操作列宽度:同时显示"增加+删除"则宽一点;只显示一个则缩窄
|
||||
const rowActionsColumnWidth = computed(() => {
|
||||
const showAdd = !!props.showRowAddButton;
|
||||
const showDel = !!props.showRowDeleteButton;
|
||||
if (showAdd && showDel) return 100;
|
||||
if (showAdd || showDel) return 60;
|
||||
// 如果两者都不显示,列也不会渲染;这里给个兜底
|
||||
return 0;
|
||||
});
|
||||
|
||||
@@ -323,7 +324,7 @@ const searchPlaceholder = computed(() => {
|
||||
return `请输入${fieldLabels[0]}`;
|
||||
}
|
||||
|
||||
return `请输入${fieldLabels.join('|')}`;
|
||||
return `请输入${fieldLabels.join('|')}`;
|
||||
});
|
||||
|
||||
// 根据搜索关键词过滤表格数据
|
||||
@@ -383,9 +384,9 @@ const handleDelete = (index) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectionChange = (selection) => {
|
||||
selectedRows.value = selection;
|
||||
emit('selection-change', selection);
|
||||
const handleSelectionChange = ({ records }: { records: Record<string, any>[] }) => {
|
||||
selectedRows.value = records;
|
||||
emit('selection-change', records);
|
||||
};
|
||||
|
||||
// 删除所有选中的行
|
||||
@@ -418,7 +419,7 @@ const handleDeleteSelected = () => {
|
||||
|
||||
// 清空选中状态
|
||||
if (tableRef.value) {
|
||||
tableRef.value.clearSelection();
|
||||
tableRef.value.clearCheckboxRow();
|
||||
}
|
||||
selectedRows.value = [];
|
||||
};
|
||||
@@ -499,69 +500,20 @@ defineExpose({
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.toolbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table.editable-table-inner) {
|
||||
.editable-table-inner {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.el-table__body-wrapper {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.el-table__cell {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
vertical-align: top;
|
||||
|
||||
.cell {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
:deep(.el-table__cell) {
|
||||
overflow: visible;
|
||||
vertical-align: top;
|
||||
|
||||
.cell {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
// 错误信息往下撑开行高,不影响上面布局
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
|
||||
.el-form-item__error {
|
||||
position: static;
|
||||
line-height: 1.5;
|
||||
padding-top: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--el-color-danger);
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.editable-table-footer {
|
||||
margin-top: 16px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
margin: 4px;
|
||||
:deep(.el-icon) {
|
||||
font-size: 18px;
|
||||
}
|
||||
padding: 2px 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.editable-table-footer {
|
||||
flex-shrink: 0;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -1,79 +1,82 @@
|
||||
<template>
|
||||
<template>
|
||||
<div class="table-container">
|
||||
<div ref="tableWrapperRef" class="table-wrapper">
|
||||
<el-table
|
||||
<vxe-table
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
:data="computedTableData"
|
||||
:border="border"
|
||||
:border="border ? 'full' : false"
|
||||
:stripe="stripe"
|
||||
:size="size"
|
||||
:size="size === 'large' ? 'medium' : size === 'small' ? 'mini' : 'small'"
|
||||
:height="computedTableHeight"
|
||||
:row-key="rowKey"
|
||||
:row-config="{ keyField: rowKey || 'id', isHover: true }"
|
||||
:highlight-current-row="highlightCurrentRow"
|
||||
@row-click="handleRowClick"
|
||||
@selection-change="handleSelectionChange"
|
||||
:show-overflow="true"
|
||||
:show-header-overflow="title"
|
||||
:auto-resize="true"
|
||||
:scroll-x="{ enabled: true, gt: 20 }"
|
||||
:scroll-y="{ enabled: true, gt: 50 }"
|
||||
@cell-click="handleRowClick"
|
||||
@checkbox-change="handleSelectionChange"
|
||||
@checkbox-all="handleSelectionAll"
|
||||
@sort-change="handleSortChange"
|
||||
style="width: 100%; height: 100%"
|
||||
>
|
||||
<!-- 通过配置数组生成的列 -->
|
||||
<template v-for="column in tableColumns" :key="column.prop || column.type">
|
||||
<el-table-column
|
||||
v-if="column.type && column.type !== 'expand'"
|
||||
:type="column.type"
|
||||
:width="column.width"
|
||||
<!-- 选择列 -->
|
||||
<vxe-column
|
||||
v-if="column.type === 'selection'"
|
||||
type="checkbox"
|
||||
:width="column.width || 50"
|
||||
:min-width="column.minWidth"
|
||||
:align="column.align || 'center'"
|
||||
:fixed="
|
||||
column.type === 'selection'
|
||||
? column.fixed !== undefined
|
||||
? column.fixed
|
||||
: 'left'
|
||||
: column.fixed
|
||||
"
|
||||
:selectable="column.selectable"
|
||||
:fixed="column.fixed !== undefined ? column.fixed : 'left'"
|
||||
:select-config="column.selectable ? { checkMethod: ({ row }) => column.selectable(row, 0) } : undefined"
|
||||
/>
|
||||
<!-- 展开列,支持自定义插槽内容 -->
|
||||
<el-table-column
|
||||
<!-- 序号列 -->
|
||||
<vxe-column
|
||||
v-else-if="column.type === 'index'"
|
||||
type="seq"
|
||||
:title="column.label || '序号'"
|
||||
:width="column.width || 60"
|
||||
:align="column.align || 'center'"
|
||||
:fixed="column.fixed"
|
||||
/>
|
||||
<!-- 展开列 -->
|
||||
<vxe-column
|
||||
v-else-if="column.type === 'expand'"
|
||||
type="expand"
|
||||
:width="column.width"
|
||||
:min-width="column.minWidth"
|
||||
:fixed="column.fixed"
|
||||
>
|
||||
<template #default="scope">
|
||||
<slot :name="column.slot || 'expand'" :row="scope.row" :scope="scope" />
|
||||
<template #content="{ row }">
|
||||
<slot :name="column.slot || 'expand'" :row="row" :scope="{ row }" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</vxe-column>
|
||||
<!-- 普通数据列 -->
|
||||
<el-table-column
|
||||
<vxe-column
|
||||
v-else
|
||||
:prop="column.prop"
|
||||
:label="column.label"
|
||||
:field="column.prop"
|
||||
:title="column.label"
|
||||
:width="column.width"
|
||||
:min-width="column.minWidth"
|
||||
:min-width="column.minWidth || calcMinWidth(column)"
|
||||
:align="column.align || 'left'"
|
||||
:fixed="column.fixed"
|
||||
:show-overflow-tooltip="column.showOverflowTooltip !== false"
|
||||
:show-overflow="column.showOverflowTooltip !== false"
|
||||
>
|
||||
<template v-if="column.slot" #default="scope">
|
||||
<slot :name="column.slot" :row="scope.row" :scope="scope" />
|
||||
<template v-if="column.slot" #default="{ row }">
|
||||
<slot :name="column.slot" :row="row" :scope="{ row }" />
|
||||
</template>
|
||||
<template v-else-if="column.formatter" #default="scope">
|
||||
{{
|
||||
column.formatter(
|
||||
scope.row,
|
||||
scope.column,
|
||||
column.prop ? scope.row[column.prop] : undefined,
|
||||
scope.$index
|
||||
)
|
||||
}}
|
||||
<template v-else-if="column.formatter" #default="{ row }">
|
||||
{{ column.formatter(row, { property: column.prop }, column.prop ? row[column.prop] : undefined, 0) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</vxe-column>
|
||||
</template>
|
||||
<!-- 通过插槽自定义的列 -->
|
||||
<slot name="table" />
|
||||
</el-table>
|
||||
</vxe-table>
|
||||
</div>
|
||||
<div v-if="showPagination" ref="paginationWrapperRef" class="pagination-wrapper">
|
||||
<div
|
||||
@@ -126,11 +129,22 @@ const props = withDefaults(defineProps<TableProps>(), {
|
||||
|
||||
const emit = defineEmits<{
|
||||
'row-click': [row: Record<string, any>, column: any, event: Event];
|
||||
'cell-click': [row: Record<string, any>, column: any, event: Event];
|
||||
'selection-change': [selection: Record<string, any>[]];
|
||||
'sort-change': [sortInfo: { column: any; prop: string; order: string }];
|
||||
pagination: [pagination: { page: number; limit: number }];
|
||||
}>();
|
||||
|
||||
// 根据列标题和内容特征估算最小宽度
|
||||
const calcMinWidth = (column: any) => {
|
||||
if (column.width) return undefined; // 有固定宽度就不限
|
||||
const labelLen = (column.label || '').length;
|
||||
// 中文字符约 14px,英文约 8px,加 padding 24px
|
||||
const estimated = labelLen * 14 + 40;
|
||||
// 最小 80,最大 300
|
||||
return Math.max(80, Math.min(300, estimated));
|
||||
};
|
||||
|
||||
const internalPageNo = ref(props.pageNo);
|
||||
const internalPageSize = ref(props.pageSize);
|
||||
|
||||
@@ -148,7 +162,7 @@ watch(
|
||||
() => props.isAllData,
|
||||
(isAllData) => {
|
||||
if (isAllData) {
|
||||
internalPageNo.value = props.pageNo;
|
||||
internalPageNo.value = 1;
|
||||
internalPageSize.value = props.pageSize;
|
||||
}
|
||||
}
|
||||
@@ -187,7 +201,7 @@ const handlePagination = (pagination: { page: number; limit: number }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const tableRef = ref<InstanceType<typeof import('element-plus').ElTable> | null>(null);
|
||||
const tableRef = ref<any>(null);
|
||||
const tableWrapperRef = ref<HTMLDivElement | null>(null);
|
||||
const paginationWrapperRef = ref<HTMLDivElement | null>(null);
|
||||
const dynamicTableHeight = ref<number | null>(null);
|
||||
@@ -294,29 +308,27 @@ watch(
|
||||
}
|
||||
);
|
||||
|
||||
const handleRowClick = (row: Record<string, any>, column: any, event: Event) => {
|
||||
emit('row-click', row, column, event);
|
||||
const handleRowClick = ({ row, column, $event }: { row: any; column: any; $event: Event }) => {
|
||||
emit('row-click', row, column, $event);
|
||||
emit('cell-click', row, column, $event);
|
||||
};
|
||||
|
||||
const handleSelectionChange = (selection: Record<string, any>[]) => {
|
||||
emit('selection-change', selection);
|
||||
const handleSelectionChange = ({ records }: { records: Record<string, any>[] }) => {
|
||||
emit('selection-change', records);
|
||||
};
|
||||
|
||||
const handleSortChange = ({
|
||||
column,
|
||||
prop,
|
||||
order,
|
||||
}: {
|
||||
column: any;
|
||||
prop: string;
|
||||
order: string;
|
||||
}) => {
|
||||
emit('sort-change', { column, prop, order });
|
||||
const handleSelectionAll = ({ records }: { records: Record<string, any>[] }) => {
|
||||
emit('selection-change', records);
|
||||
};
|
||||
|
||||
const handleSortChange = ({ column, field, order }: { column: any; field: string; order: string }) => {
|
||||
emit('sort-change', { column, prop: field, order: order === 'asc' ? 'ascending' : order === 'desc' ? 'descending' : null });
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
tableRef,
|
||||
tableWrapperRef,
|
||||
clearSelection: () => tableRef.value?.clearCheckboxRow(),
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<template>
|
||||
<template>
|
||||
<div class="table-layout-container">
|
||||
<div class="card-content-wrapper">
|
||||
<div
|
||||
@@ -95,7 +95,7 @@
|
||||
:page-no="props.queryParams.pageNo"
|
||||
:page-size="props.queryParams.pageSize"
|
||||
@row-click="handleRowClick"
|
||||
@selection-change="handleSelectionChange"
|
||||
@checkbox-change="handleSelectionChange"
|
||||
@sort-change="handleSortChange"
|
||||
@pagination="handlePagination"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user