[FIX]置顶修改、表格居中修改

(cherry picked from commit ffeb5bf0b6)
This commit is contained in:
huweihang
2025-12-12 23:32:06 +08:00
parent 5a1d03b02d
commit 07506d0fff
2 changed files with 214 additions and 113 deletions

View File

@@ -9,40 +9,40 @@
append-to-body
>
<div class="top-course-sorter" v-loading="loading">
<div class="top-course-sorter__table" v-if="topList.length">
<div class="sorter-header">
<div class="header-cell header-cell--handle"></div>
<div class="header-cell header-cell--order">排序</div>
<div class="header-cell header-cell--name">课程名称</div>
<div class="header-cell header-cell--teacher">授课教师</div>
</div>
<div
class="sorter-row"
v-for="(item, index) in topList"
:key="item.id"
draggable="true"
@dragstart="handleDragStart(index, $event)"
@dragover.prevent
@drop="handleDrop(index)"
@dragend="handleDragEnd"
:class="{ 'is-dragging': draggingIndex === index }"
>
<div class="row-cell row-cell--handle">
<i class="el-icon-s-operation"></i>
</div>
<div class="row-cell row-cell--order">{{ index + 1 }}</div>
<div class="row-cell row-cell--name">
<el-tooltip :content="item.name" placement="top" :disabled="!item.name">
<div class="cell-text">{{ item.name }}</div>
<el-table
v-if="topList.length"
ref="sortTable"
:data="topList"
class="top-course-sorter__table"
row-key="id"
:row-class-name="getRowClassName"
:header-cell-style="{ background: '#f5f7fa', color: '#303133', fontWeight: '600' }"
>
<el-table-column width="60" align="center">
<template slot-scope="scope">
<div class="drag-handle">
<i class="el-icon-s-operation"></i>
</div>
</template>
</el-table-column>
<el-table-column prop="sort" label="排序" width="80" align="left">
<template slot-scope="scope">
<span class="common-cell">{{ scope.$index + 1 }}</span>
</template>
</el-table-column>
<el-table-column prop="name" label="课程名称" min-width="200">
<template slot-scope="scope">
<el-tooltip :content="scope.row.name" placement="top" :disabled="!scope.row.name">
<div class="cell-text common-cell">{{ scope.row.name }}</div>
</el-tooltip>
</div>
<div class="row-cell row-cell--teacher">
<el-tooltip :content="item.teacherName || '-'" placement="top" :disabled="!item.teacherName">
<div class="cell-text">{{ item.teacherName || '-' }}</div>
</el-tooltip>
</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="teacherName" label="授课教师" :min-width="teacherColumnWidth" align="center">
<template slot-scope="scope">
<span class="common-cell">{{ scope.row.teacherName || '-' }}</span>
</template>
</el-table-column>
</el-table>
<el-empty v-else-if="!loading" description="暂无置顶课程"></el-empty>
</div>
<span slot="footer" class="dialog-footer">
@@ -57,6 +57,12 @@ import apiCourse from '@/api/modules/course.js';
export default {
name: 'TopCourseSorter',
computed: {
// 动态计算"授课教师"列的最小宽度
teacherColumnWidth() {
return this.calcColumnWidth('授课教师', 'teacherName');
},
},
data() {
return {
dialogVisible: false,
@@ -66,6 +72,19 @@ export default {
draggingIndex: null,
};
},
watch: {
topList: {
handler(newVal, oldVal) {
// 只在列表长度变化或首次加载时初始化拖拽事件
if (!oldVal || newVal.length !== oldVal.length) {
this.$nextTick(() => {
this.initDragEvents();
});
}
},
immediate: false,
},
},
methods: {
open() {
this.dialogVisible = true;
@@ -76,7 +95,12 @@ export default {
try {
const res = await apiCourse.fetchTopCourseList();
if (res.status === 200) {
this.topList = Array.isArray(res.result) ? [...res.result] : [];
this.topList = res.result.map(item=>{
return {
...item,
// teacherName: '对对对柳在一、李玉冰、李国鑫、张添瑞、张子峰、 柳在一、李玉冰、李国鑫、张添瑞、张子峰'
}
})
} else {
this.$message.error(res.message || '获取置顶课程失败');
this.topList = [];
@@ -88,6 +112,37 @@ export default {
this.loading = false;
}
},
getRowClassName({ row, rowIndex }) {
if (this.draggingIndex === rowIndex) {
return 'is-dragging';
}
return '';
},
initDragEvents() {
if (!this.$refs.sortTable) return;
const tableBody = this.$refs.sortTable.$el.querySelector('.el-table__body-wrapper tbody');
if (!tableBody) return;
const rows = tableBody.querySelectorAll('tr');
rows.forEach((row, index) => {
// 移除旧的事件监听器
row.removeEventListener('dragstart', this.handleDragStart);
row.removeEventListener('dragover', this.handleDragOver);
row.removeEventListener('drop', this.handleDrop);
row.removeEventListener('dragend', this.handleDragEnd);
// 设置可拖拽
row.draggable = true;
row.setAttribute('data-index', index);
// 添加新的事件监听器
row.addEventListener('dragstart', (e) => this.handleDragStart(index, e));
row.addEventListener('dragover', (e) => this.handleDragOver(index, e));
row.addEventListener('drop', (e) => this.handleDrop(index, e));
row.addEventListener('dragend', () => this.handleDragEnd());
});
},
handleDragStart(index, event) {
this.draggingIndex = index;
if (event && event.dataTransfer) {
@@ -95,16 +150,57 @@ export default {
event.dataTransfer.setData('text/plain', index);
}
},
handleDrop(targetIndex) {
handleDragOver(index, event) {
event.preventDefault();
if (this.draggingIndex !== null && this.draggingIndex !== index) {
const tableBody = this.$refs.sortTable.$el.querySelector('.el-table__body-wrapper tbody');
if (tableBody) {
const rows = tableBody.querySelectorAll('tr');
rows.forEach((row, idx) => {
if (idx === index) {
row.classList.add('drag-over');
} else {
row.classList.remove('drag-over');
}
});
}
}
},
handleDrop(targetIndex, event) {
event.preventDefault();
if (this.draggingIndex === null || this.draggingIndex === targetIndex) {
return;
}
const movingItem = this.topList.splice(this.draggingIndex, 1)[0];
this.topList.splice(targetIndex, 0, movingItem);
this.draggingIndex = targetIndex;
// 清除拖拽样式
const tableBody = this.$refs.sortTable.$el.querySelector('.el-table__body-wrapper tbody');
if (tableBody) {
const rows = tableBody.querySelectorAll('tr');
rows.forEach((row) => {
row.classList.remove('drag-over');
});
}
// 重新初始化拖拽事件,因为 DOM 顺序已改变
this.$nextTick(() => {
this.initDragEvents();
});
},
handleDragEnd() {
this.draggingIndex = null;
// 清除所有拖拽样式
if (this.$refs.sortTable) {
const tableBody = this.$refs.sortTable.$el.querySelector('.el-table__body-wrapper tbody');
if (tableBody) {
const rows = tableBody.querySelectorAll('tr');
rows.forEach((row) => {
row.classList.remove('drag-over');
});
}
}
},
async handleSave() {
if (!this.topList.length) {
@@ -137,6 +233,26 @@ export default {
this.loading = false;
this.saving = false;
},
// 计算文本宽度(通过隐藏 span 获取实际宽度)
getTextWidth(text = '') {
if (typeof document === 'undefined') return 0;
const span = document.createElement('span');
span.innerText = text;
span.style.cssText = 'position:absolute;visibility:hidden;font-size:14px;font-family:inherit;white-space:nowrap;';
document.body.appendChild(span);
const { width } = span.getBoundingClientRect();
document.body.removeChild(span);
return Math.ceil(width);
},
// 计算列宽(头+内容取最大值,加 padding并控制最小值
calcColumnWidth(label, prop, padding = 24, min = 130, max = Infinity) {
const contents = (this.topList || []).map(row => this.getTextWidth((row && row[prop]) || '-'));
const maxContentWidth = contents.length ? Math.max(...contents) : 0;
const labelWidth = this.getTextWidth(label || '');
const baseWidth = Math.max(maxContentWidth, labelWidth) + padding;
const clamped = Math.max(min, Math.min(baseWidth, max));
return `${clamped}px`;
},
},
};
</script>
@@ -148,75 +264,56 @@ export default {
}
.top-course-sorter__table {
border: 1px solid #ebeef5;
border-radius: 6px;
overflow: hidden;
::v-deep .el-table__row {
cursor: move;
transition: background-color 0.2s ease;
&:hover {
background-color: #f9fbff;
}
&.is-dragging {
opacity: 0.7;
background-color: #ecf5ff;
}
&.drag-over {
border-top: 2px solid #409eff;
}
}
::v-deep .el-table__body-wrapper {
.el-table__row {
&:last-child {
.el-table__cell {
border-bottom: none;
}
}
}
}
::v-deep .el-table__cell {
padding: 12px 0;
}
}
.sorter-header,
.sorter-row {
display: grid;
grid-template-columns: 60px 80px 1fr 160px;
align-items: center;
}
.sorter-header {
background-color: #f5f7fa;
height: 48px;
font-weight: 600;
color: #303133;
border-bottom: 1px solid #ebeef5;
}
.sorter-row {
min-height: 56px;
border-bottom: 1px solid #f2f6fc;
cursor: move;
transition: background-color 0.2s ease;
}
.sorter-row:last-child {
border-bottom: none;
}
.sorter-row:hover {
background-color: #f9fbff;
}
.sorter-row.is-dragging {
opacity: 0.7;
background-color: #ecf5ff;
}
.header-cell,
.row-cell {
padding: 0 16px;
.drag-handle {
display: flex;
align-items: center;
}
.header-cell--handle,
.row-cell--handle {
justify-content: center;
}
cursor: move;
width: 100%;
height: 100%;
padding: 0 16px;
.header-cell--order,
.row-cell--order {
justify-content: flex-start;
}
i {
font-size: 20px;
color: #c0c4cc;
}
.row-cell--name,
.row-cell--teacher {
color: #303133;
overflow: hidden;
}
.row-cell--name {
font-weight: 500;
}
.row-cell--teacher {
color: #666;
&:hover i {
color: #409eff;
}
}
.cell-text {
@@ -226,9 +323,10 @@ export default {
width: 100%;
}
.row-cell--handle i {
font-size: 20px;
color: #c0c4cc;
.common-cell {
font-weight: 400;
font-size: 14px;
color: #000000;
}
.dialog-footer {