🦄 refactor: 封装统一表格组件,便于批量扩展表格能力

This commit is contained in:
砂糖
2025-08-27 16:47:33 +08:00
parent 278b0c8258
commit 7ea0de6a67
133 changed files with 465 additions and 432 deletions

View File

@@ -39,7 +39,7 @@
<script>
export default {
name: "klp-list",
name: "KLPList",
components: {},
props: {
/** 列表数据源(必传) */
@@ -76,11 +76,11 @@ export default {
default: false
},
/** 标题最大宽度(像素),控制文字溢出 */
titleMaxWidth: {
/** 标题最大宽度占容器的百分比0-100),控制文字溢出 */
titleMaxPercent: {
type: Number,
required: false,
default: 200
default: 80 // 默认占容器80%宽度
}
},
data() {
@@ -88,7 +88,9 @@ export default {
// 内部管理选中状态:存储当前选中项的唯一键值(单选中)
selectedKey: null,
// 文字溢出检测临时元素(避免重复创建)
overflowCheckElements: {}
overflowCheckElements: {},
// 容器宽度缓存
containerWidth: 0
};
},
methods: {
@@ -107,9 +109,10 @@ export default {
* @param {Object} item - 点击的列表项数据
*/
handleItemClick(item) {
const itemKey = item[this.listKey];
const itemKey = item[this.listKey];
// 单选中逻辑:点击已选中项取消选中,点击未选中项切换选中(取消其他项)
this.selectedKey = this.selectedKey === itemKey ? null : itemKey;
// this.selectedKey = this.selectedKey === itemKey ? null : itemKey;
this.selectedKey = itemKey;
// 向父组件触发事件传递当前选中项null表示无选中
const selectedItem = this.selectedKey ? item : null;
@@ -154,6 +157,15 @@ export default {
// 内容为空时不显示Tooltip
if (!content) return false;
// 获取容器宽度并增加存在性检查
const container = this.$el.querySelector('.klp-list-container');
if (!container) return false; // 容器不存在时直接返回
this.containerWidth = container.clientWidth;
// 计算标题内容最大可用宽度(减去标签和边距)
const labelWidth = this.$el.querySelector('.title-label')?.offsetWidth || 60;
const availableWidth = (this.containerWidth * this.titleMaxPercent / 100) - labelWidth - 20;
// 创建临时元素测量文字实际宽度(复用元素避免性能问题)
if (!this.overflowCheckElements[itemKey]) {
const tempEl = document.createElement("span");
@@ -171,9 +183,17 @@ export default {
this.overflowCheckElements[itemKey] = tempEl;
}
// 比较文字实际宽度与设定的最大宽度
// 比较文字实际宽度与可用宽度
const tempEl = this.overflowCheckElements[itemKey];
return tempEl.offsetWidth > this.titleMaxWidth;
return tempEl.offsetWidth > availableWidth;
},
/**
* 监听容器宽度变化
*/
handleResize() {
// 宽度变化时重新计算溢出状态
this.$forceUpdate();
}
},
watch: {
@@ -190,16 +210,29 @@ export default {
}
},
/** 标题最大宽度变化时,强制重绘以重新计算溢出 */
titleMaxWidth() {
/** 标题最大百分比变化时,重新计算溢出 */
titleMaxPercent() {
this.$forceUpdate();
}
},
/** 组件销毁时清理临时元素,避免内存泄漏 */
mounted() {
this.$nextTick(() => {
const container = this.$el.querySelector('.klp-list-container');
// 增加存在性检查
this.containerWidth = container ? container.clientWidth : 0;
});
// 监听窗口大小变化
window.addEventListener('resize', this.handleResize);
},
beforeDestroy() {
// 清理临时元素
Object.values(this.overflowCheckElements).forEach(el => {
document.body.removeChild(el);
});
// 移除事件监听
window.removeEventListener('resize', this.handleResize);
}
};
</script>
@@ -211,6 +244,8 @@ export default {
overflow-y: auto;
padding-right: 8px;
margin-top: 10px;
width: 100%; /* 确保容器宽度正确计算 */
box-sizing: border-box;
}
/* 加载状态容器(避免加载时容器塌陷) */
@@ -229,6 +264,7 @@ export default {
justify-content: space-between;
align-items: center;
box-sizing: border-box;
width: 100%; /* 确保列表项占满容器宽度 */
}
/* 列表项选中状态(左侧高亮边框+背景色) */
@@ -242,6 +278,8 @@ export default {
display: flex;
align-items: center;
flex: 1;
min-width: 0; /* 关键允许flex项缩小到内容尺寸以下 */
overflow: hidden; /* 确保内容不会超出容器 */
}
/* 标题前置标签样式 */
@@ -250,6 +288,7 @@ export default {
margin-right: 6px;
font-size: 13px;
white-space: nowrap; /* 标签不换行 */
flex-shrink: 0; /* 标签不缩小 */
}
/* 标题内容样式(溢出省略) */
@@ -260,7 +299,8 @@ export default {
white-space: nowrap; /* 禁止换行 */
overflow: hidden; /* 超出部分隐藏 */
text-overflow: ellipsis; /* 超出部分显示省略号 */
max-width: v-bind(titleMaxWidth + "px"); /* 绑定父组件传入的最大宽度 */
flex: 1; /* 占据剩余空间 */
min-width: 0; /* 关键:允许内容区域缩小 */
}
/* 操作按钮组(按钮间距控制) */
@@ -268,6 +308,8 @@ export default {
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0; /* 操作区不缩小 */
margin-left: 8px;
}
/* 空状态样式(居中显示) */
@@ -275,4 +317,5 @@ export default {
padding: 40px 0;
text-align: center;
}
</style>
</style>