Files
erp-frontend/src/components/base/base-table-form/BaseTableForm.vue
c ac3756f199 fix: 0.1.1 修正问题如下:
1. 修正了部分开发页面以及开源时的展示问题。
2. 修正了成品入库单模块出货功能行选择框缓存没清除的问题。
3. 修正了角色管理级联选中问题。
4. 修正了表格行按钮的宽度不够展示不全的问题。
5. 对 Excel 解析进行了兼容。
2026-04-03 14:46:13 +08:00

289 lines
7.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref, computed, watch } from "vue";
import { useRoute } from "vue-router";
import type { TableInstance } from "element-plus";
// Props 定义
const props = defineProps({
title: {
type: String,
default: "列表选择",
},
// 表格数据
tableData: {
type: Array as () => any[],
default: () => [],
},
// 是否显示加载状态
loading: {
type: Boolean,
default: false,
},
// 用于行勾选的唯一键,默认 'id'
rowKey: {
type: String,
default: "id",
},
// 是否默认全选(可选)
defaultSelectAll: {
type: Boolean,
default: false,
},
// 新增:是否显示底部统计信息
showSummary: {
type: Boolean,
default: true,
},
// 新增:自定义统计文本前缀,默认 "共"
summaryText: {
type: String,
default: "共",
},
// 【新增】是否禁用所有行的选中功能
// true: 所有行不可选 (复选框变灰)
// false: 所有行可选 (默认)
disabledSelection: {
type: Boolean,
default: false,
},
useAuth: {
type: Boolean,
default: true,
},
selectable: {
type: Boolean,
default: true,
},
});
const visible = defineModel<boolean>("visible");
// Emits 定义
const emit = defineEmits<{
"update:visible": [value: boolean];
"selection-change": [selection: any[]];
"dialog-button-click": [eventName: string, buttonConfig: globalThis.ButtonProp, selectedRows: any[]];
"filter-refresh": []; // 当父组件在 slot 中点击查询时,可触发此事件刷新数据
}>();
const route = useRoute();
const tableRef = ref<TableInstance>();
// 获取底部对话框按钮权限
// 假设权限数据结构与参考代码类似,但来源是 dialogButtonAuth
const dialogButtonList = computed<globalThis.ButtonProp[]>(() => {
if (!props.useAuth) return [];
const list = (route.meta.dialogButtonAuth as globalThis.ButtonProp[]) || [];
// 按 rank 排序rank 越小越靠前,没有 rank 的排后面
return list.sort((a, b) => (a.rank ?? 999) - (b.rank ?? 999));
});
// 内部状态
const selectedRows = ref<any[]>([]);
// 监听弹窗打开,如果需要默认全选
watch(
() => visible,
val => {
if (val && props.defaultSelectAll && tableRef.value) {
// 下一帧执行以确保 DOM 渲染完成
setTimeout(() => {
tableRef.value?.toggleAllSelection();
}, 0);
}
if (!val) {
// 关闭时清空选中
selectedRows.value = [];
// 清空表格内部的选择状态,避免下次打开时残留
tableRef.value?.clearSelection();
}
}
);
// 处理表格勾选变化
const handleSelectionChange = (selection: any[]) => {
selectedRows.value = selection;
emit("selection-change", selection);
};
// 处理底部按钮点击
const handleDialogButtonClick = (btn: globalThis.ButtonProp) => {
emit("dialog-button-click", btn.eventName, btn, selectedRows.value);
};
// 暴露方法给父组件(例如手动刷新表格或清空选中)
const clearSelection = () => {
tableRef.value?.clearSelection();
};
const toggleRowSelection = (row: any, selected?: boolean) => {
tableRef.value?.toggleRowSelection(row, selected);
};
const checkPermission = (buttonEventName?: string) => {
if (!buttonEventName) return true;
return dialogButtonList.value.find(item => item.eventName === buttonEventName) !== undefined;
};
defineExpose({
clearSelection,
toggleRowSelection,
tableRef,
});
</script>
<template>
<el-dialog v-model="visible" :title="title" :close-on-click-modal="false" :lock-scroll="false" width="80%">
<!-- 1. 顶部筛选区域 (Slot) -->
<div class="dialog-filter-area">
<slot name="filter" :refresh="() => emit('filter-refresh')" :selected-rows="selectedRows">
<!-- 默认空内容由父组件填充 -->
</slot>
</div>
<!-- 2. 中部表格区域 -->
<div class="dialog-table-area">
<el-table
ref="tableRef"
:data="tableData"
:row-key="rowKey"
border
stripe
style="width: 100%"
:loading="loading"
max-height="50vh"
@selection-change="handleSelectionChange"
>
<!-- 勾选列 -->
<el-table-column type="selection" width="55" v-if="selectable" />
<!-- 父组件自定义列 -->
<slot name="table-columns"></slot>
<!-- 如果没有权限按钮且父组件也没传操作列可以留空或显示提示 -->
</el-table>
<!-- 提示信息当无数据时 -->
<el-empty v-if="!loading && tableData.length === 0" description="暂无数据" />
</div>
<!-- 3. 底部区域 (修改部分) -->
<template #footer>
<div class="dialog-footer-container">
<!-- 左侧统计信息 -->
<div class="footer-summary" v-if="showSummary">
<span class="total-count">{{ summaryText }} {{ tableData.length }} </span>
<span v-if="selectedRows.length > 0" class="selected-count">(已选 {{ selectedRows.length }} )</span>
</div>
<!-- 右侧操作按钮 -->
<div class="footer-actions">
<slot name="attachment" :selected-rows="selectedRows" :checkPermission="checkPermission">
<!-- 默认内容为空不影响原有布局 -->
</slot>
<el-button
v-for="btn in dialogButtonList"
:key="btn.buttonName"
:type="btn.colorType || 'primary'"
:disabled="selectedRows.length === 0 && !['cancel'].includes(btn.eventName)"
@click="handleDialogButtonClick(btn)"
>
{{ $t("_button." + btn.eventName) || btn.text }}
</el-button>
<el-button @click="visible = false">
{{ $t("_button.cancel") || "取消" }}
</el-button>
</div>
</div>
</template>
</el-dialog>
</template>
<style scoped>
.footer-actions {
display: flex;
gap: 12px;
align-items: center;
}
.dialog-footer-container {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.footer-summary {
display: flex;
gap: 8px;
align-items: center;
}
.total-count {
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary);
}
.selected-count {
font-size: 13px;
font-weight: 500;
color: var(--el-color-primary);
}
.dialog-filter-area {
padding: 12px 16px;
margin-bottom: 16px;
background-color: var(--el-fill-color-lighter);
border: 1px solid var(--el-border-color-lighter);
border-radius: 6px;
}
.dialog-table-area {
min-height: 200px;
max-height: 60vh;
overflow-y: auto;
}
.dialog-footer {
display: flex;
gap: 12px;
justify-content: flex-end;
}
:deep(.el-dialog) {
overflow: hidden;
border-radius: 8px;
}
:deep(.el-dialog__body) {
overflow-y: auto;
scrollbar-gutter: stable;
}
:deep(.el-dialog__header) {
padding: 16px 20px;
margin-right: 0;
background-color: var(--el-fill-color-light);
border-bottom: 1px solid var(--el-border-color-lighter);
}
:deep(.el-dialog__title) {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
}
:deep(.el-dialog__headerbtn) {
top: 16px;
}
:deep(.el-table) {
font-size: 13px;
}
:deep(.el-table th.el-table__cell) {
font-weight: 600;
background-color: var(--el-fill-color-light);
}
</style>