java
复制代码
<template>
<div class="top-right-btn" :style="style">
<el-row>
<el-tooltip v-if="search" class="item" effect="dark" :content="showSearch ? '隐藏搜索表单' : '显示搜索表单'" placement="top">
<el-button type="text" :icon="showSearch ? 'Hide' : 'View'" class="search-btn" @click="toggleSearch()" />
</el-tooltip>
<el-tooltip v-if="showRefresh" class="item" effect="dark" content="刷新" placement="top">
<el-button type="text" icon="Refresh" @click="refresh()" />
</el-tooltip>
<el-tooltip v-if="columns" class="item" effect="dark" content="显示/隐藏列" placement="top">
<div class="show-btn">
<el-popover placement="bottom" trigger="click" width="250" @show="handlePopoverShow">
<div class="popover-content">
<div class="tree-header">显示/隐藏列</div>
<!-- 添加滚动条的树容器 -->
<div class="tree-container">
<el-tree
ref="columnRef"
:data="columns"
show-checkbox
check-on-click-node
node-key="key"
:props="{ label: 'label', children: 'children' }"
@check="columnChange"
>
<template #default="{ node, data }">
<span class="custom-tree-node" :title="data.label">
{{ node.label }}
</span>
</template>
</el-tree>
</div>
<!-- 底部按钮区域,包含全选和操作按钮 -->
<div class="popover-footer">
<div class="footer-left">
<el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" size="small" @change="handleCheckAllChange">全选</el-checkbox>
</div>
<div class="footer-right">
<el-button size="small" style="font-size: 12px; margin-right: -8px; margin-left: 5px" @click="resetToDefault">恢复默认</el-button>
<el-button size="small" style="font-size: 12px" type="primary" :loading="saving" @click="handleSave">保存配置</el-button>
</div>
</div>
</div>
<template #reference>
<el-button type="text" icon="Setting" />
</template>
</el-popover>
</div>
</el-tooltip>
</el-row>
</div>
</template>
<script setup lang="ts">
import { propTypes } from '@/utils/propTypes';
import { ElMessage } from 'element-plus';
const props = defineProps({
showSearch: propTypes.bool.def(true),
showRefresh: propTypes.bool.def(true),
columns: propTypes.fieldOption,
search: propTypes.bool.def(true),
gutter: propTypes.number.def(10),
// 表格标识,用于保存到后端时区分不同表格
tableKey: propTypes.string.def(''),
// 是否显示保存按钮
showSave: propTypes.bool.def(true),
// 树形控件最大高度
treeMaxHeight: propTypes.string.def('300px')
});
const columnRef = ref<ElTreeInstance>();
const emits = defineEmits(['update:showSearch', 'queryTable', 'update:columns', 'save']);
// 存储默认列配置
const defaultColumns = ref<any[]>([]);
const saving = ref(false);
const checkAll = ref(false);
const isIndeterminate = ref(false);
const style = computed(() => {
const ret: any = {};
if (props.gutter) {
ret.marginRight = `${props.gutter / 2}px`;
}
return ret;
});
// 更新全选状态
const updateCheckAllStatus = () => {
if (!props.columns || props.columns.length === 0) return;
const checkedCount = props.columns.filter((item) => item.visible).length;
const totalCount = props.columns.length;
if (checkedCount === 0) {
checkAll.value = false;
isIndeterminate.value = false;
} else if (checkedCount === totalCount) {
checkAll.value = true;
isIndeterminate.value = false;
} else {
checkAll.value = false;
isIndeterminate.value = true;
}
};
// 全选/取消全选
const handleCheckAllChange = (val: boolean) => {
// 更新所有列的visible状态
props.columns?.forEach((item) => {
item.visible = val;
});
// 触发更新事件
emits('update:columns', props.columns);
// 更新树形控件的选中状态
nextTick(() => {
props.columns?.forEach((item) => {
if (item.visible) {
columnRef.value?.setChecked(item.key, true, false);
} else {
columnRef.value?.setChecked(item.key, false, false);
}
});
});
isIndeterminate.value = false;
};
// 搜索
function toggleSearch() {
emits('update:showSearch', !props.showSearch);
}
// 刷新
function refresh() {
emits('queryTable');
}
// 更改数据列的显示和隐藏
function columnChange(...args: any[]) {
props.columns?.forEach((item) => {
item.visible = args[1].checkedKeys.includes(item.key);
});
// 触发更新事件
emits('update:columns', props.columns);
// 更新全选状态
updateCheckAllStatus();
}
// 弹窗显示时的处理
const handlePopoverShow = () => {
// 确保树形控件的选中状态与columns同步
nextTick(() => {
props.columns?.forEach((item) => {
if (item.visible) {
columnRef.value?.setChecked(item.key, true, false);
} else {
columnRef.value?.setChecked(item.key, false, false);
}
});
updateCheckAllStatus();
});
};
// 保存默认列配置
const saveDefaultColumns = () => {
if (props.columns) {
defaultColumns.value = props.columns.map((item: any) => ({
key: item.key,
label: item.label,
visible: item.visible
}));
}
};
// 恢复默认配置
const resetToDefault = () => {
if (!props.columns || defaultColumns.value.length === 0) return;
// 恢复默认visible状态
props.columns.forEach((item: any) => {
const defaultItem = defaultColumns.value.find((d: any) => d.key === item.key);
if (defaultItem) {
item.visible = defaultItem.visible;
}
});
// 触发更新事件
emits('update:columns', props.columns);
// 更新树形控件的选中状态
nextTick(() => {
props.columns?.forEach((item: any) => {
if (item.visible) {
columnRef.value?.setChecked(item.key, true, false);
} else {
columnRef.value?.setChecked(item.key, false, false);
}
});
updateCheckAllStatus();
});
ElMessage.success('已恢复默认配置');
};
// 处理保存
const handleSave = async () => {
if (!props.columns || props.columns.length === 0) {
ElMessage.warning('没有可保存的列配置');
return;
}
// 构建要保存的列配置数据
const columnConfig = props.columns.map((item: any) => ({
key: item.key,
label: item.label,
visible: item.visible
}));
// 触发保存事件,由父组件处理实际的保存逻辑
emits('save', {
tableKey: props.tableKey,
columns: columnConfig
});
};
// 从后端加载列配置
const loadColumnConfig = (config: any[]) => {
if (!props.columns || !config) return;
// 更新列的visible状态
props.columns.forEach((item: any) => {
const savedItem = config.find((s: any) => s.key === item.key);
if (savedItem) {
item.visible = savedItem.visible;
}
});
// 触发更新事件
emits('update:columns', props.columns);
// 更新树形控件的选中状态
nextTick(() => {
props.columns?.forEach((item: any) => {
if (item.visible) {
columnRef.value?.setChecked(item.key, true, false);
} else {
columnRef.value?.setChecked(item.key, false, false);
}
});
updateCheckAllStatus();
});
};
// 设置保存状态
const setSaving = (status: boolean) => {
saving.value = status;
};
// 初始化
onMounted(() => {
saveDefaultColumns();
// 初始化树形控件的选中状态
nextTick(() => {
props.columns?.forEach((item) => {
if (item.visible) {
columnRef.value?.setChecked(item.key, true, false);
}
});
updateCheckAllStatus();
});
});
// 监听columns变化,更新默认配置
watch(
() => props.columns,
(newVal) => {
if (newVal && defaultColumns.value.length === 0) {
saveDefaultColumns();
}
},
{ deep: true }
);
// 暴露方法给父组件调用
defineExpose({
loadColumnConfig,
setSaving
});
</script>
<style lang="scss" scoped>
:deep(.el-transfer__button) {
border-radius: 50%;
display: block;
margin-left: 0px;
}
:deep(.el-transfer__button:first-child) {
margin-bottom: 10px;
}
.my-el-transfer {
text-align: center;
}
.popover-content {
padding: 8px 0;
}
.tree-header {
width: 100%;
line-height: 24px;
text-align: center;
font-weight: bold;
padding: 0 12px 8px 12px;
border-bottom: 1px solid #eee;
}
// 树容器,添加滚动条
.tree-container {
max-height: 300px;
overflow-y: auto;
padding: 4px 0;
// 自定义树节点样式
:deep(.el-tree-node__content) {
height: 32px;
}
}
.custom-tree-node {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-right: 8px;
font-size: 13px;
}
.popover-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px 0 12px;
border-top: 1px solid #eee;
margin-top: 4px;
.footer-left {
flex-shrink: 0;
}
.footer-right {
display: flex;
gap: 8px;
}
}
.show-btn {
margin-left: 12px;
}
.el-button {
color: #333;
font-size: 20px;
margin-right: 5px;
}
</style>
<style>
/* 全局样式,确保弹窗不会影响表格布局 */
.el-popover.column-popover {
padding: 0 !important;
}
</style>
java
复制代码
<right-toolbar
v-model:showSearch="showSearch"
:columns="columns"
table-key="SQMS_MATERIALS_BASE_INFO"
@update:columns="columns = $event"
@query-table="getList"
@save="handleSaveColumnConfig"
></right-toolbar>
<el-table-column v-if="columns[0].visible" label="xxx" align="center" prop="xxx" width="120"/>
// 列显隐信息
const columns = ref<>(
[
{ key: 0, label: `xxx`, visible: true, children: [] }
]
import { saveColumnConfig, getColumnConfig } from '@/api/system/user/index';
const rightToolbarRef = ref();
// 保存列配置到后端
const handleSaveColumnConfig = async (data) => {
try {
// 这里调用您的保存接口
const saveData = {
tableName: data.tableKey,
columnConfig: JSON.stringify(data.columns)
};
await saveColumnConfig(saveData);
ElMessage.success('列配置保存成功');
} catch (error) {
console.error('保存失败:', error);
ElMessage.error('保存失败');
}
};
// 加载列配置
const loadColumnConfig = async () => {
try {
// 从后端获取配置
const res = await getColumnConfig({
tableName: 'SQMS_MATERIALS_BASE_INFO'
});
if (res.data && res.data.length > 0) {
columns.value = res.data;
const config = JSON.parse(res.data);
// 调用组件暴露的方法加载配置
rightToolbarRef.value?.loadColumnConfig(config);
}
} catch (error) {
console.error('加载配置失败:', error);
}
};
java
复制代码
@Override
public R saveColumnConfig(Map<String, Object> map) {
if (ObjectUtil.isEmpty(map.get("tableName"))) {
throw new ServiceException("列配置保存失败:表名称不可为空");
}
if (ObjectUtil.isEmpty(map.get("columnConfig"))) {
throw new ServiceException("列配置保存失败:配置项不可为空");
}
List<ColumnConfig> columnConfig = JSON.parseObject(map.get("columnConfig").toString(), new TypeReference<List<ColumnConfig>>() {
});
String key = String.format("column-config:%s_%s", LoginHelper.getUsername(), map.get("tableName"));
RedisUtils.setCacheObject(key, JSON.toJSONString(columnConfig));
return R.ok();
}
@Data
public static class ColumnConfig {
private int key;
private String label;
private Boolean visible;
}
@Override
public R getColumnConfig(Map<String, Object> map) {
if (ObjectUtil.isEmpty(map.get("tableName"))) {
throw new ServiceException("列配置查询失败:表名称不可为空");
}
String key = String.format("column-config:%s_%s", LoginHelper.getUsername(), map.get("tableName"));
Object cacheObject = RedisUtils.getCacheObject(key);
if (ObjectUtil.isEmpty(cacheObject)) {
return R.ok();
}
List<ColumnConfig> columnConfigs = JSON.parseObject(cacheObject.toString(), new TypeReference<List<ColumnConfig>>() {
});
return R.ok(columnConfigs);
}