1. 修正了部分开发页面以及开源时的展示问题。 2. 修正了成品入库单模块出货功能行选择框缓存没清除的问题。 3. 修正了角色管理级联选中问题。 4. 修正了表格行按钮的宽度不够展示不全的问题。 5. 对 Excel 解析进行了兼容。
289 lines
7.2 KiB
Vue
289 lines
7.2 KiB
Vue
<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>
|