[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 append-to-body
> >
<div class="top-course-sorter" v-loading="loading"> <div class="top-course-sorter" v-loading="loading">
<div class="top-course-sorter__table" v-if="topList.length"> <el-table
<div class="sorter-header"> v-if="topList.length"
<div class="header-cell header-cell--handle"></div> ref="sortTable"
<div class="header-cell header-cell--order">排序</div> :data="topList"
<div class="header-cell header-cell--name">课程名称</div> class="top-course-sorter__table"
<div class="header-cell header-cell--teacher">授课教师</div> row-key="id"
</div> :row-class-name="getRowClassName"
<div :header-cell-style="{ background: '#f5f7fa', color: '#303133', fontWeight: '600' }"
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"> <el-table-column width="60" align="center">
<template slot-scope="scope">
<div class="drag-handle">
<i class="el-icon-s-operation"></i> <i class="el-icon-s-operation"></i>
</div> </div>
<div class="row-cell row-cell--order">{{ index + 1 }}</div> </template>
<div class="row-cell row-cell--name"> </el-table-column>
<el-tooltip :content="item.name" placement="top" :disabled="!item.name"> <el-table-column prop="sort" label="排序" width="80" align="left">
<div class="cell-text">{{ item.name }}</div> <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> </el-tooltip>
</div> </template>
<div class="row-cell row-cell--teacher"> </el-table-column>
<el-tooltip :content="item.teacherName || '-'" placement="top" :disabled="!item.teacherName"> <el-table-column prop="teacherName" label="授课教师" :min-width="teacherColumnWidth" align="center">
<div class="cell-text">{{ item.teacherName || '-' }}</div> <template slot-scope="scope">
</el-tooltip> <span class="common-cell">{{ scope.row.teacherName || '-' }}</span>
</div> </template>
</div> </el-table-column>
</div> </el-table>
<el-empty v-else-if="!loading" description="暂无置顶课程"></el-empty> <el-empty v-else-if="!loading" description="暂无置顶课程"></el-empty>
</div> </div>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
@@ -57,6 +57,12 @@ import apiCourse from '@/api/modules/course.js';
export default { export default {
name: 'TopCourseSorter', name: 'TopCourseSorter',
computed: {
// 动态计算"授课教师"列的最小宽度
teacherColumnWidth() {
return this.calcColumnWidth('授课教师', 'teacherName');
},
},
data() { data() {
return { return {
dialogVisible: false, dialogVisible: false,
@@ -66,6 +72,19 @@ export default {
draggingIndex: null, draggingIndex: null,
}; };
}, },
watch: {
topList: {
handler(newVal, oldVal) {
// 只在列表长度变化或首次加载时初始化拖拽事件
if (!oldVal || newVal.length !== oldVal.length) {
this.$nextTick(() => {
this.initDragEvents();
});
}
},
immediate: false,
},
},
methods: { methods: {
open() { open() {
this.dialogVisible = true; this.dialogVisible = true;
@@ -76,7 +95,12 @@ export default {
try { try {
const res = await apiCourse.fetchTopCourseList(); const res = await apiCourse.fetchTopCourseList();
if (res.status === 200) { if (res.status === 200) {
this.topList = Array.isArray(res.result) ? [...res.result] : []; this.topList = res.result.map(item=>{
return {
...item,
// teacherName: '对对对柳在一、李玉冰、李国鑫、张添瑞、张子峰、 柳在一、李玉冰、李国鑫、张添瑞、张子峰'
}
})
} else { } else {
this.$message.error(res.message || '获取置顶课程失败'); this.$message.error(res.message || '获取置顶课程失败');
this.topList = []; this.topList = [];
@@ -88,6 +112,37 @@ export default {
this.loading = false; 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) { handleDragStart(index, event) {
this.draggingIndex = index; this.draggingIndex = index;
if (event && event.dataTransfer) { if (event && event.dataTransfer) {
@@ -95,16 +150,57 @@ export default {
event.dataTransfer.setData('text/plain', index); 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) { if (this.draggingIndex === null || this.draggingIndex === targetIndex) {
return; return;
} }
const movingItem = this.topList.splice(this.draggingIndex, 1)[0]; const movingItem = this.topList.splice(this.draggingIndex, 1)[0];
this.topList.splice(targetIndex, 0, movingItem); this.topList.splice(targetIndex, 0, movingItem);
this.draggingIndex = targetIndex; 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() { handleDragEnd() {
this.draggingIndex = null; 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() { async handleSave() {
if (!this.topList.length) { if (!this.topList.length) {
@@ -137,6 +233,26 @@ export default {
this.loading = false; this.loading = false;
this.saving = 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> </script>
@@ -148,75 +264,56 @@ export default {
} }
.top-course-sorter__table { .top-course-sorter__table {
border: 1px solid #ebeef5; ::v-deep .el-table__row {
border-radius: 6px;
overflow: hidden;
}
.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; cursor: move;
transition: background-color 0.2s ease; transition: background-color 0.2s ease;
}
.sorter-row:last-child { &:hover {
border-bottom: none;
}
.sorter-row:hover {
background-color: #f9fbff; background-color: #f9fbff;
} }
.sorter-row.is-dragging { &.is-dragging {
opacity: 0.7; opacity: 0.7;
background-color: #ecf5ff; background-color: #ecf5ff;
} }
.header-cell, &.drag-over {
.row-cell { border-top: 2px solid #409eff;
padding: 0 16px; }
}
::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;
}
}
.drag-handle {
display: flex; display: flex;
align-items: center; align-items: center;
}
.header-cell--handle,
.row-cell--handle {
justify-content: center; justify-content: center;
cursor: move;
width: 100%;
height: 100%;
padding: 0 16px;
i {
font-size: 20px;
color: #c0c4cc;
} }
.header-cell--order, &:hover i {
.row-cell--order { color: #409eff;
justify-content: flex-start;
} }
.row-cell--name,
.row-cell--teacher {
color: #303133;
overflow: hidden;
}
.row-cell--name {
font-weight: 500;
}
.row-cell--teacher {
color: #666;
} }
.cell-text { .cell-text {
@@ -226,9 +323,10 @@ export default {
width: 100%; width: 100%;
} }
.row-cell--handle i { .common-cell {
font-size: 20px; font-weight: 400;
color: #c0c4cc; font-size: 14px;
color: #000000;
} }
.dialog-footer { .dialog-footer {

View File

@@ -189,52 +189,52 @@
</el-table-column> </el-table-column>
<el-table-column label="课程时长" prop="courseDuration" min-width="110" align="center" sortable="custom"> <el-table-column label="课程时长" prop="courseDuration" min-width="110" align="center" sortable="custom">
<template slot-scope="scope"> <template slot-scope="scope">
<span class="common-cell">{{ formatCourseDuration(scope.row) }}</span> <span class="common-cell common-cell-right">{{ formatCourseDuration(scope.row) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="学习人数" prop="studys" min-width="110" align="center" sortable="custom"> <el-table-column label="学习人数" prop="studys" min-width="110" align="center" sortable="custom">
<template slot-scope="scope"> <template slot-scope="scope">
<span class="common-cell">{{ scope.row.studys || '-' }}</span> <span class="common-cell common-cell-right">{{ scope.row.studys || '-' }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="课程评分" prop="score" min-width="110" align="center" sortable="custom"> <el-table-column label="课程评分" prop="score" min-width="110" align="center" sortable="custom">
<template slot-scope="scope"> <template slot-scope="scope">
<span class="common-cell">{{ formatScore(scope.row) }}</span> <span class="common-cell common-cell-right">{{ formatScore(scope.row) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="审核状态" prop="status" min-width="110" align="center" sortable="custom"> <el-table-column label="审核状态" prop="status" min-width="110" align="center" sortable="custom">
<template slot-scope="scope"> <template slot-scope="scope">
<span class="common-cell" v-if="scope.row.status == 1">-</span> <span class="common-cell common-cell-right" v-if="scope.row.status == 1">-</span>
<span class="common-cell" v-if="scope.row.status == 2">审核中</span> <span class="common-cell common-cell-right" v-if="scope.row.status == 2">审核中</span>
<span :class="['common-cell', 'status--pass']" v-if="scope.row.status == 5">审核通过</span> <span :class="['common-cell common-cell-right', 'status--pass']" v-if="scope.row.status == 5">审核通过</span>
<span :class="['common-cell', 'status--reject']" v-if="scope.row.status == 3">审核驳回</span> <span :class="['common-cell common-cell-right', 'status--reject']" v-if="scope.row.status == 3">审核驳回</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="发布状态" prop="published" min-width="110" align="center" sortable="custom"> <el-table-column label="发布状态" prop="published" min-width="110" align="center" sortable="custom">
<template slot-scope="scope"> <template slot-scope="scope">
<span class="common-cell">{{ scope.row.published == true ? '已发布' : '未发布' }}</span> <span class="common-cell common-cell-right">{{ scope.row.published == true ? '已发布' : '未发布' }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="启停用状态" prop="enabled" min-width="130" align="center" sortable="custom"> <el-table-column label="启停用状态" prop="enabled" min-width="130" align="center" sortable="custom">
<template slot-scope="scope"> <template slot-scope="scope">
<span class="common-cell">{{ scope.row.enabled == true ? '启用' : '停用' }}</span> <span class="common-cell common-cell-right">{{ scope.row.enabled == true ? '启用' : '停用' }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="公开课" prop="openCourse" min-width="110" align="center" sortable="custom"> <el-table-column label="公开课" prop="openCourse" min-width="110" align="center" sortable="custom">
<template slot-scope="scope"> <template slot-scope="scope">
<span class="common-cell">{{ scope.row.openCourse == 1 ? '是' : '否' }}</span> <span class="common-cell common-cell-right">{{ scope.row.openCourse == 1 ? '是' : '否' }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="资源归属" prop="orgName" min-width="220" align="center" sortable="custom"> <el-table-column label="资源归属" prop="orgName" min-width="220" align="center" sortable="custom">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tooltip :content="scope.row.orgFullName || scope.row.orgName" placement="top" effect="dark"> <el-tooltip :content="scope.row.orgFullName || scope.row.orgName" placement="top" effect="dark">
<span class="common-cell">{{ scope.row.orgName }}</span> <span class="common-cell common-cell-right">{{ scope.row.orgName }}</span>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="创建人" prop="sysCreateBy" min-width="130" align="center" sortable="custom"> <el-table-column label="创建人" prop="sysCreateBy" min-width="130" align="center" sortable="custom" show-overflow-tooltip>
<template slot-scope="scope"> <template slot-scope="scope">
<span class="common-cell">{{ scope.row.sysCreateBy }}</span> <span class="common-cell common-cell-right">{{ scope.row.sysCreateBy }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="创建来源" min-width="140" align="center"> <el-table-column label="创建来源" min-width="140" align="center">
@@ -2097,6 +2097,9 @@ export default {
font-size: 14px; font-size: 14px;
color: #000000; color: #000000;
} }
.common-cell-right {
padding-right: 20px;
}
.single-line-ellipsis { .single-line-ellipsis {
display: inline-block; display: inline-block;