更新vxetable框架并升级前端组件框架

This commit is contained in:
2026-06-03 11:19:52 +08:00
parent 5b6b23331d
commit 5a2050a736
385 changed files with 19691 additions and 21188 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"
>