mirror of
https://codeup.aliyun.com/67762337eccfc218f6110e0e/vue/learning-system-portal.git
synced 2025-12-13 21:06:42 +08:00
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user