[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 }"
<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' }"
>
<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>
</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>
</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;
}
.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;
::v-deep .el-table__row {
cursor: move;
transition: background-color 0.2s ease;
}
.sorter-row:last-child {
border-bottom: none;
}
.sorter-row:hover {
&:hover {
background-color: #f9fbff;
}
.sorter-row.is-dragging {
&.is-dragging {
opacity: 0.7;
background-color: #ecf5ff;
}
.header-cell,
.row-cell {
padding: 0 16px;
&.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;
}
}
.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;
i {
font-size: 20px;
color: #c0c4cc;
}
.header-cell--order,
.row-cell--order {
justify-content: flex-start;
&:hover i {
color: #409eff;
}
.row-cell--name,
.row-cell--teacher {
color: #303133;
overflow: hidden;
}
.row-cell--name {
font-weight: 500;
}
.row-cell--teacher {
color: #666;
}
.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 {

View File

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