|
|
@@ -33,104 +33,109 @@
|
|
|
<span class="title-cell">操作</span>
|
|
|
</div>
|
|
|
<div class="chapters-container">
|
|
|
- <div
|
|
|
- v-for="(
|
|
|
- {
|
|
|
- id,
|
|
|
- name,
|
|
|
- deep,
|
|
|
- producer_list,
|
|
|
- is_leaf_chapter,
|
|
|
- is_root,
|
|
|
- is_inherited_producer,
|
|
|
- is_inherited_auditor,
|
|
|
- auditor_desc,
|
|
|
- status_name,
|
|
|
- last_editor_name,
|
|
|
- last_edit_time,
|
|
|
- edit_end_date,
|
|
|
- is_show_gc_button,
|
|
|
- status,
|
|
|
- },
|
|
|
- i
|
|
|
- ) in node_list"
|
|
|
- :key="id"
|
|
|
- :class="['catalogue', { active: curSelectId === id }]"
|
|
|
- @click="selectActiveChapter(id, is_leaf_chapter === 'true')"
|
|
|
- >
|
|
|
- <div :class="['chapter-title', { courseware: isEnable(is_leaf_chapter) }]" :style="computedNameStyle(deep)">
|
|
|
- <span class="nowrap-ellipsis" :title="name">{{ name }}</span>
|
|
|
- <el-dropdown v-if="!isEnable(is_leaf_chapter)" trigger="click">
|
|
|
- <span class="el-dropdown-link" style="cursor: pointer"><i class="el-icon-plus"></i> </span>
|
|
|
- <el-dropdown-menu slot="dropdown">
|
|
|
- <el-dropdown-item @click.native="addCoursewareDialog(id)">
|
|
|
- <span>添加教材内容</span>
|
|
|
- </el-dropdown-item>
|
|
|
- <el-dropdown-item @click.native="addChapterDialog(id)">
|
|
|
- <span>添加章节</span>
|
|
|
- </el-dropdown-item>
|
|
|
- </el-dropdown-menu>
|
|
|
- </el-dropdown>
|
|
|
-
|
|
|
- <div v-if="isEnable(is_leaf_chapter) && isEnable(is_show_gc_button)">
|
|
|
- <span v-if="status === 2" class="link" @click="setCoursewareCanGC(id, 'true')">开始改错</span>
|
|
|
- <span v-if="status === 4" class="link" @click="setCoursewareCanGC(id, 'false')">取消改错</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div
|
|
|
- class="producer nowrap-ellipsis"
|
|
|
- :style="{ color: !isEnable(is_root) && isEnable(is_inherited_producer) ? '#ff4757' : '' }"
|
|
|
- :title="producer_list.map((producer) => producer.name).join(';')"
|
|
|
- >
|
|
|
- <span>{{ producer_list.map((producer) => producer.name).join(';') }}</span>
|
|
|
- </div>
|
|
|
- <div
|
|
|
- class="edit-end-date"
|
|
|
- :style="{ color: !isEnable(is_root) && isEnable(is_inherited_producer) ? '#ff4757' : '' }"
|
|
|
- >
|
|
|
- {{ edit_end_date }}
|
|
|
- </div>
|
|
|
+ <VueDraggable v-model="node_list" :animation="150" :move="canMove" @start="onStart" @end="onEnd">
|
|
|
<div
|
|
|
- class="audit nowrap-ellipsis"
|
|
|
- :style="{ color: !isEnable(is_root) && isEnable(is_inherited_auditor) ? '#ff4757' : '' }"
|
|
|
- :title="auditor_desc"
|
|
|
+ v-for="(
|
|
|
+ {
|
|
|
+ id,
|
|
|
+ name: nodeName,
|
|
|
+ deep,
|
|
|
+ producer_list,
|
|
|
+ is_leaf_chapter,
|
|
|
+ is_root,
|
|
|
+ is_inherited_producer,
|
|
|
+ is_inherited_auditor,
|
|
|
+ auditor_desc,
|
|
|
+ status_name,
|
|
|
+ last_editor_name,
|
|
|
+ last_edit_time,
|
|
|
+ edit_end_date,
|
|
|
+ is_show_gc_button,
|
|
|
+ status,
|
|
|
+ },
|
|
|
+ i
|
|
|
+ ) in node_list"
|
|
|
+ :key="id"
|
|
|
+ :class="[
|
|
|
+ 'catalogue',
|
|
|
+ { active: curSelectId === id, 'drag-target': dragCtx.dragging && dragCtx.targetId === id },
|
|
|
+ ]"
|
|
|
+ @click="selectActiveChapter(id, is_leaf_chapter === 'true')"
|
|
|
>
|
|
|
- {{ auditor_desc }}
|
|
|
- </div>
|
|
|
- <div class="status">{{ status_name }}</div>
|
|
|
- <div class="last-editor">{{ isEnable(is_leaf_chapter) ? last_editor_name : '' }}</div>
|
|
|
- <div class="last-edit-time">{{ isEnable(is_leaf_chapter) ? last_edit_time : '' }}</div>
|
|
|
- <div class="operator">
|
|
|
- <template v-if="isEnable(is_leaf_chapter)">
|
|
|
- <span v-if="i > 1 && computedIsRootFirst(i)" class="link" @click="moveChapterTreeNode(i, 0)">上移</span>
|
|
|
- <span v-if="i < node_list.length - 1" class="link" @click="moveChapterTreeNode(i, 1)">下移</span>
|
|
|
- </template>
|
|
|
-
|
|
|
- <template v-else>
|
|
|
- <span v-if="i > 1" class="link" @click="moveChapterNode(i, deep, 0)">上移</span>
|
|
|
- <span v-if="i > 0 && computedIsLast(id, deep)" class="link" @click="moveChapterNode(i, deep, 1)">
|
|
|
- 下移
|
|
|
- </span>
|
|
|
- </template>
|
|
|
-
|
|
|
- <span
|
|
|
- v-if="is_root !== 'true'"
|
|
|
- class="link"
|
|
|
- @click="openUpdateNameDialog(id, name, is_leaf_chapter === 'true')"
|
|
|
+ <div :class="['chapter-title', { courseware: isEnable(is_leaf_chapter) }]" :style="computedNameStyle(deep)">
|
|
|
+ <span class="nowrap-ellipsis" :title="nodeName">{{ nodeName }}</span>
|
|
|
+ <el-dropdown v-if="!isEnable(is_leaf_chapter)" trigger="click">
|
|
|
+ <span class="el-dropdown-link" style="cursor: pointer"><i class="el-icon-plus"></i> </span>
|
|
|
+ <el-dropdown-menu slot="dropdown">
|
|
|
+ <el-dropdown-item @click.native="addCoursewareDialog(id)">
|
|
|
+ <span>添加教材内容</span>
|
|
|
+ </el-dropdown-item>
|
|
|
+ <el-dropdown-item @click.native="addChapterDialog(id)">
|
|
|
+ <span>添加章节</span>
|
|
|
+ </el-dropdown-item>
|
|
|
+ </el-dropdown-menu>
|
|
|
+ </el-dropdown>
|
|
|
+
|
|
|
+ <div v-if="isEnable(is_leaf_chapter) && isEnable(is_show_gc_button)">
|
|
|
+ <span v-if="status === 2" class="link" @click="setCoursewareCanGC(id, 'true')">开始改错</span>
|
|
|
+ <span v-if="status === 4" class="link" @click="setCoursewareCanGC(id, 'false')">取消改错</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="producer nowrap-ellipsis"
|
|
|
+ :style="{ color: !isEnable(is_root) && isEnable(is_inherited_producer) ? '#ff4757' : '' }"
|
|
|
+ :title="producer_list.map((producer) => producer.name).join(';')"
|
|
|
>
|
|
|
- 修改
|
|
|
- </span>
|
|
|
- <span class="link" @click="openSetProducer(id, producer_list)">设置制作人</span>
|
|
|
- <span class="link" @click="openSetAuditor(id)">设置审核人</span>
|
|
|
- <span
|
|
|
- v-if="is_root !== 'true'"
|
|
|
- class="link danger"
|
|
|
- @click="is_leaf_chapter === 'true' ? deleteCourseware(id) : deleteChapter(id)"
|
|
|
+ <span>{{ producer_list.map((producer) => producer.name).join(';') }}</span>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="edit-end-date"
|
|
|
+ :style="{ color: !isEnable(is_root) && isEnable(is_inherited_producer) ? '#ff4757' : '' }"
|
|
|
+ >
|
|
|
+ {{ edit_end_date }}
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="audit nowrap-ellipsis"
|
|
|
+ :style="{ color: !isEnable(is_root) && isEnable(is_inherited_auditor) ? '#ff4757' : '' }"
|
|
|
+ :title="auditor_desc"
|
|
|
>
|
|
|
- 删除
|
|
|
- </span>
|
|
|
+ {{ auditor_desc }}
|
|
|
+ </div>
|
|
|
+ <div class="status">{{ status_name }}</div>
|
|
|
+ <div class="last-editor">{{ isEnable(is_leaf_chapter) ? last_editor_name : '' }}</div>
|
|
|
+ <div class="last-edit-time">{{ isEnable(is_leaf_chapter) ? last_edit_time : '' }}</div>
|
|
|
+ <div class="operator">
|
|
|
+ <template v-if="isEnable(is_leaf_chapter)">
|
|
|
+ <span v-if="i > 1 && computedIsRootFirst(i)" class="link" @click="moveChapterTreeNode(i, 0)">上移</span>
|
|
|
+ <span v-if="i < node_list.length - 1" class="link" @click="moveChapterTreeNode(i, 1)">下移</span>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template v-else>
|
|
|
+ <span v-if="i > 1" class="link" @click="moveChapterNode(i, deep, 0)">上移</span>
|
|
|
+ <span v-if="i > 0 && computedIsLast(id, deep)" class="link" @click="moveChapterNode(i, deep, 1)">
|
|
|
+ 下移
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <span
|
|
|
+ v-if="is_root !== 'true'"
|
|
|
+ class="link"
|
|
|
+ @click="openUpdateNameDialog(id, nodeName, is_leaf_chapter === 'true')"
|
|
|
+ >
|
|
|
+ 修改
|
|
|
+ </span>
|
|
|
+ <span class="link" @click="openSetProducer(id, producer_list)">设置制作人</span>
|
|
|
+ <span class="link" @click="openSetAuditor(id)">设置审核人</span>
|
|
|
+ <span
|
|
|
+ v-if="is_root !== 'true'"
|
|
|
+ class="link danger"
|
|
|
+ @click="is_leaf_chapter === 'true' ? deleteCourseware(id) : deleteChapter(id)"
|
|
|
+ >
|
|
|
+ 删除
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ </VueDraggable>
|
|
|
</div>
|
|
|
</main>
|
|
|
|
|
|
@@ -178,6 +183,7 @@ import SetAuditor from './components/SetAuditor.vue';
|
|
|
import UpdateName from './components/UpdateName.vue';
|
|
|
import BookUnifiedAttr from './components/BookUnifiedAttr.vue';
|
|
|
import BookUnifiedTitle from './components/BookUnifiedTitle.vue';
|
|
|
+import VueDraggable from 'vuedraggable';
|
|
|
|
|
|
import { GetProjectBaseInfo } from '@/api/project';
|
|
|
import {
|
|
|
@@ -206,6 +212,7 @@ export default {
|
|
|
UpdateName,
|
|
|
BookUnifiedAttr,
|
|
|
BookUnifiedTitle,
|
|
|
+ VueDraggable,
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
@@ -237,6 +244,13 @@ export default {
|
|
|
},
|
|
|
visibleAttr: false, // 教材属性设置弹窗
|
|
|
visibleTitle: false, // 教材标题设置弹窗
|
|
|
+ // 拖拽相关数据
|
|
|
+ dragCtx: {
|
|
|
+ movedId: '',
|
|
|
+ oldIndex: -1,
|
|
|
+ targetId: '',
|
|
|
+ dragging: false,
|
|
|
+ },
|
|
|
};
|
|
|
},
|
|
|
created() {
|
|
|
@@ -457,7 +471,7 @@ export default {
|
|
|
},
|
|
|
/**
|
|
|
* 设置制作人
|
|
|
- * @param {Object} data - 章节制作人数据
|
|
|
+ * @param {object} data - 章节制作人数据
|
|
|
* @param {string} data.node_id - 章节ID
|
|
|
* @param {string} data.producer_id_list - 制作人ID列表
|
|
|
* @param {string} data.edit_end_date - 交稿日期
|
|
|
@@ -567,6 +581,78 @@ export default {
|
|
|
// 忽略用户取消确认的情况
|
|
|
});
|
|
|
},
|
|
|
+ /**
|
|
|
+ * 拖拽过程中判断是否可以移动
|
|
|
+ * @param {object} evt - 拖拽事件对象
|
|
|
+ * @returns {boolean} - 是否可以移动
|
|
|
+ */
|
|
|
+ canMove(evt) {
|
|
|
+ // 当前被拖拽的数据项
|
|
|
+ const dragged = evt.draggedContext && evt.draggedContext.element;
|
|
|
+ const related = evt.relatedContext && evt.relatedContext.element;
|
|
|
+ this.dragCtx.targetId = related ? related.id : '';
|
|
|
+ if (dragged && dragged.is_root === 'true') {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 拖拽开始时记录被移动节点的信息
|
|
|
+ * @param {object} evt - 拖拽事件对象
|
|
|
+ * @param {number} evt.oldIndex - 原始索引
|
|
|
+ */
|
|
|
+ onStart(evt) {
|
|
|
+ const { oldIndex } = evt;
|
|
|
+ this.dragCtx.dragging = true;
|
|
|
+ this.dragCtx.oldIndex = oldIndex;
|
|
|
+ this.dragCtx.movedId = this.node_list[oldIndex]?.id || '';
|
|
|
+ this.dragCtx.targetId = '';
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 拖拽结束后处理章节移动
|
|
|
+ * @param {object} evt - 拖拽事件对象
|
|
|
+ * @param {number} evt.oldIndex - 原始索引
|
|
|
+ * @param {number} evt.newIndex - 新索引
|
|
|
+ */
|
|
|
+ onEnd(evt) {
|
|
|
+ const { oldIndex, newIndex } = evt;
|
|
|
+
|
|
|
+ if (oldIndex === newIndex) {
|
|
|
+ this.dragCtx.dragging = false;
|
|
|
+ this.dragCtx.targetId = '';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 被移动节点 id
|
|
|
+ const id = this.dragCtx.movedId || this.node_list[newIndex]?.id;
|
|
|
+
|
|
|
+ // 按拖拽方向计算目标位置:上移放到目标前,下移放到目标后
|
|
|
+ const isMoveDown = newIndex > oldIndex;
|
|
|
+ const dest_position = isMoveDown ? 1 : 0;
|
|
|
+ const targetIndex = isMoveDown ? newIndex - 1 : newIndex + 1;
|
|
|
+ const dest_id = this.node_list[targetIndex]?.id || '';
|
|
|
+
|
|
|
+ if (!id || !dest_id) {
|
|
|
+ this.dragCtx.dragging = false;
|
|
|
+ this.dragCtx.targetId = '';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ ChapterMoveTreeNode({ id, dest_id, dest_position })
|
|
|
+ .then(() => {
|
|
|
+ this.getBookChapterStructExpandList();
|
|
|
+ this.$message.success('章节移动成功');
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ // 请求失败时,恢复原始位置
|
|
|
+ const movedNode = this.node_list.splice(newIndex, 1)[0];
|
|
|
+ this.node_list.splice(oldIndex, 0, movedNode);
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ this.dragCtx.dragging = false;
|
|
|
+ this.dragCtx.targetId = '';
|
|
|
+ });
|
|
|
+ },
|
|
|
},
|
|
|
};
|
|
|
</script>
|
|
|
@@ -683,6 +769,10 @@ export default {
|
|
|
background-color: $main-active-color;
|
|
|
}
|
|
|
|
|
|
+ &.drag-target {
|
|
|
+ background-color: rgba($main-hover-color, 0.15);
|
|
|
+ }
|
|
|
+
|
|
|
> div {
|
|
|
height: 40px;
|
|
|
padding: 8px;
|