Files
learning-system-portal/src/components/Course/TopCourseSorter.vue
huweihang f4c73c927b [FIX]置顶修改、表格居中修改
(cherry picked from commit ffeb5bf0b6)
2025-12-13 09:34:42 +08:00

347 lines
9.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<el-dialog
title="置顶排序"
:visible.sync="dialogVisible"
custom-class="g-dialog top-course-sorter-dialog"
width="820px"
:close-on-click-modal="false"
@closed="handleClosed"
append-to-body
>
<div class="top-course-sorter" v-loading="loading">
<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>
</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">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :disabled="!topList.length" :loading="saving" @click="handleSave">确定</el-button>
</span>
</el-dialog>
</template>
<script>
import apiCourse from '@/api/modules/course.js';
export default {
name: 'TopCourseSorter',
computed: {
// 动态计算"授课教师"列的最小宽度
teacherColumnWidth() {
return this.calcColumnWidth('授课教师', 'teacherName');
},
},
data() {
return {
dialogVisible: false,
loading: false,
saving: false,
topList: [],
draggingIndex: null,
};
},
watch: {
topList: {
handler(newVal, oldVal) {
// 只在列表长度变化或首次加载时初始化拖拽事件
if (!oldVal || newVal.length !== oldVal.length) {
this.$nextTick(() => {
this.initDragEvents();
});
}
},
immediate: false,
},
},
methods: {
open() {
this.dialogVisible = true;
this.fetchTopList();
},
async fetchTopList() {
this.loading = true;
try {
const res = await apiCourse.fetchTopCourseList();
if (res.status === 200) {
this.topList = res.result.map(item=>{
return {
...item,
// teacherName: '对对对柳在一、李玉冰、李国鑫、张添瑞、张子峰、 柳在一、李玉冰、李国鑫、张添瑞、张子峰'
}
})
} else {
this.$message.error(res.message || '获取置顶课程失败');
this.topList = [];
}
} catch (error) {
this.$message.error(error.message || '获取置顶课程失败');
this.topList = [];
} finally {
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) {
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', index);
}
},
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) {
this.$message.warning('暂无需要保存的排序');
return;
}
const payload = this.topList.map((item, index) => ({
id: item.id,
sortWeight: index,
}));
this.saving = true;
try {
const res = await apiCourse.updateTopCourseSort(payload);
if (res.status === 200) {
this.$message.success('排序更新成功');
this.$emit('sorted');
this.dialogVisible = false;
} else {
throw new Error(res.message || '排序更新失败');
}
} catch (error) {
this.$message.error(error.message || '排序更新失败');
} finally {
this.saving = false;
}
},
handleClosed() {
this.topList = [];
this.draggingIndex = null;
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>
<style lang="scss" scoped>
.top-course-sorter {
min-height: 200px;
padding-top: 8px;
}
.top-course-sorter__table {
::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;
}
}
.drag-handle {
display: flex;
align-items: center;
justify-content: center;
cursor: move;
width: 100%;
height: 100%;
padding: 0 16px;
i {
font-size: 20px;
color: #c0c4cc;
}
&:hover i {
color: #409eff;
}
}
.cell-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}
.common-cell {
font-weight: 400;
font-size: 14px;
color: #000000;
}
.dialog-footer {
text-align: right;
}
</style>