mirror of
https://codeup.aliyun.com/67762337eccfc218f6110e0e/vue/fe-manage.git
synced 2025-12-20 08:16:46 +08:00
feat(course): 实现课程标签管理功能
- 新增课程标签API模块,支持标签分页查询、创建、修改状态等操作 - 开发课程标签组件,支持标签搜索、创建、删除和数量限制 - 集成标签组件到专业模式页面,替换原有标签选择器 - 优化课程创建组件,重构表单状态管理和操作流程 - 升级Element Plus组件版本,支持el-select-v2等新组件 - 添加lodash依赖用于防抖搜索功能 - 调整样式和布局,优化标签显示和交互体验
This commit is contained in:
@@ -4,9 +4,7 @@ import { ElButton, ElCheckbox, ElDialog, ElMessageBox } from "element-plus";
|
||||
import { $message } from "@/utils/useMessage";
|
||||
import dragTable from "./dragTable.vue";
|
||||
import { ref, watch } from "vue";
|
||||
defineOptions({
|
||||
name: "CreateCourse",
|
||||
});
|
||||
import { getType } from "@/hooks/useCreateCourseMaps";
|
||||
import { useCourseData } from "@/hooks/useCourseData";
|
||||
import chooseFileList from "@/components/CreatedCourse/chooseFileList.vue";
|
||||
import VideoComp from "@/components/CreatedCourse/preview/VideoComp.vue";
|
||||
@@ -18,7 +16,10 @@ import ScormComp from "@/components/CreatedCourse/preview/ScormComp.vue";
|
||||
import PaperComp from "@/components/CreatedCourse/preview/PaperComp.vue";
|
||||
import HomeWorkComp from "@/components/CreatedCourse/preview/HomeWorkComp.vue";
|
||||
import AccessComp from "@/components/CreatedCourse/preview/AccessComp.vue";
|
||||
import { getType } from "@/hooks/useCreateCourseMaps";
|
||||
defineOptions({
|
||||
name: "CreateCourse",
|
||||
});
|
||||
// 组件映射
|
||||
const mapComponents = [
|
||||
VideoComp,
|
||||
AudioComp,
|
||||
@@ -34,22 +35,16 @@ const mapComponents = [
|
||||
// 使用课程数据hook
|
||||
const { courseMetadata, courseList, courseActionButtons, addChapter } =
|
||||
useCourseData();
|
||||
const isPreview = ref(false);
|
||||
const chooseItemData = ref({});
|
||||
const showSettingDialog = ref(false);
|
||||
// 添加操作的时候 弹窗是否弹出对应类型表单
|
||||
const isNext = ref(true);
|
||||
const showTablePreview = ref(false);
|
||||
// 定义表格列
|
||||
const showDialog = ref(false);
|
||||
|
||||
const classId = ref("");
|
||||
|
||||
const copyChooseItemData = ref({});
|
||||
|
||||
// 监听课程索引变化,更新classId
|
||||
watch(
|
||||
() => courseMetadata.chooseIndex,
|
||||
(newVal) => {
|
||||
classId.value = courseList.value[newVal].id;
|
||||
if (courseList.value[newVal]) {
|
||||
courseMetadata.classId = courseList.value[newVal].id || "";
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -57,46 +52,46 @@ watch(
|
||||
const courseOperations = {
|
||||
addVideo: () => {
|
||||
courseMetadata.resType = 10;
|
||||
showDialog.value = true;
|
||||
courseMetadata.showDialog = true;
|
||||
},
|
||||
addAudio: () => {
|
||||
courseMetadata.resType = 20;
|
||||
showDialog.value = true;
|
||||
courseMetadata.showDialog = true;
|
||||
},
|
||||
addDocument: () => {
|
||||
courseMetadata.resType = 40;
|
||||
showDialog.value = true;
|
||||
isNext.value = false;
|
||||
courseMetadata.showDialog = true;
|
||||
courseMetadata.isNext = false;
|
||||
},
|
||||
addImageText: () => {
|
||||
courseMetadata.resType = 41;
|
||||
chooseItemData.value.resType = 41;
|
||||
showSettingDialog.value = true;
|
||||
courseMetadata.showSettingDialog = true;
|
||||
},
|
||||
addExternalLink: () => {
|
||||
courseMetadata.resType = 52;
|
||||
chooseItemData.value.resType = 52;
|
||||
showSettingDialog.value = true;
|
||||
courseMetadata.showSettingDialog = true;
|
||||
},
|
||||
addScorm: () => {
|
||||
courseMetadata.resType = 50;
|
||||
showDialog.value = true;
|
||||
isNext.value = false;
|
||||
courseMetadata.showDialog = true;
|
||||
courseMetadata.isNext = false;
|
||||
},
|
||||
addExam: () => {
|
||||
courseMetadata.resType = 61;
|
||||
showDialog.value = true;
|
||||
isNext.value = false;
|
||||
courseMetadata.showDialog = true;
|
||||
courseMetadata.isNext = false;
|
||||
},
|
||||
addHomework: () => {
|
||||
courseMetadata.resType = 60;
|
||||
chooseItemData.value.resType = 60;
|
||||
showSettingDialog.value = true;
|
||||
courseMetadata.showSettingDialog = true;
|
||||
},
|
||||
addAssessment: () => {
|
||||
courseMetadata.resType = 62;
|
||||
chooseItemData.value.resType = 62;
|
||||
showSettingDialog.value = true;
|
||||
courseMetadata.showSettingDialog = true;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -105,9 +100,10 @@ const executeCourseOperation = (operationName, data) => {
|
||||
courseMetadata.chooseIndex = data;
|
||||
courseMetadata.selectionIndex = null;
|
||||
copyChooseItemData.value = {};
|
||||
isPreview.value = false;
|
||||
isNext.value = true;
|
||||
courseMetadata.isPreview = false;
|
||||
courseMetadata.isNext = true;
|
||||
chooseItemData.value = {};
|
||||
|
||||
if (courseOperations[operationName]) {
|
||||
courseOperations[operationName](data);
|
||||
} else {
|
||||
@@ -115,92 +111,130 @@ const executeCourseOperation = (operationName, data) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 选择项目处理
|
||||
const chooseItem = (data) => {
|
||||
console.log(data);
|
||||
chooseItemData.value = data;
|
||||
if (!isNext.value) {
|
||||
// 如果不需要下一步,则直接保存
|
||||
if (!courseMetadata.isNext) {
|
||||
saveContent();
|
||||
return;
|
||||
}
|
||||
showSettingDialog.value = true;
|
||||
courseMetadata.showSettingDialog = true;
|
||||
};
|
||||
|
||||
// 预览项目处理
|
||||
const choosePreviewItem = (data) => {
|
||||
chooseItemData.value = data;
|
||||
showSettingDialog.value = true;
|
||||
isPreview.value = true;
|
||||
courseMetadata.showSettingDialog = true;
|
||||
courseMetadata.isPreview = true;
|
||||
};
|
||||
|
||||
// 取消保存
|
||||
const cancelSave = () => {
|
||||
showSettingDialog.value = false;
|
||||
courseMetadata.showSettingDialog = false;
|
||||
// 恢复原始数据
|
||||
chooseItemData.value = copyChooseItemData.value;
|
||||
courseList.value[courseMetadata.chooseIndex].data[
|
||||
courseMetadata.selectionIndex
|
||||
] = chooseItemData.value;
|
||||
console.log(chooseItemData);
|
||||
};
|
||||
|
||||
// 保存
|
||||
const saveContent = () => {
|
||||
console.log(chooseItemData.value);
|
||||
|
||||
if (courseMetadata.selectionIndex !== null) {
|
||||
if (
|
||||
courseMetadata.chooseIndex !== null &&
|
||||
courseMetadata.selectionIndex !== null &&
|
||||
courseList.value[courseMetadata.chooseIndex] &&
|
||||
courseList.value[courseMetadata.chooseIndex].data[
|
||||
courseMetadata.selectionIndex
|
||||
]
|
||||
) {
|
||||
courseList.value[courseMetadata.chooseIndex].data[
|
||||
courseMetadata.selectionIndex
|
||||
] = chooseItemData.value;
|
||||
} else {
|
||||
courseList.value[courseMetadata.chooseIndex].data.push({
|
||||
resType: courseMetadata.resType,
|
||||
...chooseItemData.value,
|
||||
});
|
||||
}
|
||||
showDialog.value = false;
|
||||
showSettingDialog.value = false;
|
||||
|
||||
// 可以调用保存方法 保存考试
|
||||
};
|
||||
|
||||
// 保存内容
|
||||
const saveContent = () => {
|
||||
if (courseMetadata.selectionIndex !== null) {
|
||||
// 更新已有项
|
||||
if (
|
||||
courseList.value[courseMetadata.chooseIndex] &&
|
||||
courseList.value[courseMetadata.chooseIndex].data[
|
||||
courseMetadata.selectionIndex
|
||||
]
|
||||
) {
|
||||
courseList.value[courseMetadata.chooseIndex].data[
|
||||
courseMetadata.selectionIndex
|
||||
] = {
|
||||
...courseList.value[courseMetadata.chooseIndex].data[
|
||||
courseMetadata.selectionIndex
|
||||
],
|
||||
...chooseItemData.value,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// 添加新项
|
||||
if (courseList.value[courseMetadata.chooseIndex]) {
|
||||
courseList.value[courseMetadata.chooseIndex].data.push({
|
||||
resType: courseMetadata.resType,
|
||||
...chooseItemData.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
courseMetadata.showDialog = false;
|
||||
courseMetadata.showSettingDialog = false;
|
||||
};
|
||||
|
||||
// 删除行
|
||||
const deleteRow = (data) => {
|
||||
courseMetadata.chooseIndex = data.index;
|
||||
courseMetadata.selectionIndex = data.selectionIndex;
|
||||
|
||||
ElMessageBox.confirm(`确定删除${data.record.name}吗?`, "删除确认", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "error",
|
||||
}).then(() => {
|
||||
courseList.value[courseMetadata.chooseIndex].data.splice(
|
||||
courseMetadata.selectionIndex,
|
||||
1
|
||||
);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
if (
|
||||
courseList.value[courseMetadata.chooseIndex] &&
|
||||
courseList.value[courseMetadata.chooseIndex].data
|
||||
) {
|
||||
courseList.value[courseMetadata.chooseIndex].data.splice(
|
||||
courseMetadata.selectionIndex,
|
||||
1
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 用户取消删除
|
||||
});
|
||||
};
|
||||
|
||||
// 设置行
|
||||
const settingRow = (data) => {
|
||||
courseMetadata.chooseIndex = data.index;
|
||||
courseMetadata.selectionIndex = data.selectionIndex;
|
||||
chooseItemData.value = data.record;
|
||||
chooseItemData.value = { ...data.record }; // 创建副本避免直接引用
|
||||
copyChooseItemData.value = JSON.parse(JSON.stringify(data.record));
|
||||
isPreview.value = false;
|
||||
showSettingDialog.value = true;
|
||||
courseMetadata.isPreview = false;
|
||||
courseMetadata.showSettingDialog = true;
|
||||
};
|
||||
|
||||
// 预览行
|
||||
const previewRow = (data) => {
|
||||
courseMetadata.chooseIndex = data.index;
|
||||
courseMetadata.selectionIndex = data.selectionIndex;
|
||||
chooseItemData.value = data.record;
|
||||
chooseItemData.value = { ...data.record }; // 创建副本避免直接引用
|
||||
copyChooseItemData.value = JSON.parse(JSON.stringify(data.record));
|
||||
isPreview.value = true;
|
||||
showSettingDialog.value = true;
|
||||
courseMetadata.isPreview = true;
|
||||
courseMetadata.showSettingDialog = true;
|
||||
};
|
||||
|
||||
// 自定义考试
|
||||
const chooseCusExam = (data) => {
|
||||
chooseItemData.value = data;
|
||||
showSettingDialog.value = true;
|
||||
courseMetadata.showSettingDialog = true;
|
||||
};
|
||||
|
||||
// 下一步处理
|
||||
const handleNext = () => {
|
||||
console.log($message);
|
||||
$message.success("213");
|
||||
};
|
||||
</script>
|
||||
@@ -212,7 +246,7 @@ const handleNext = () => {
|
||||
<span>创建时间:{{ courseMetadata.createTime }}</span>
|
||||
</div>
|
||||
<div class="course-content">
|
||||
<div style="padding: 10px">
|
||||
<div class="course-actions">
|
||||
<el-button @click="addChapter">添加章</el-button>
|
||||
<el-checkbox style="margin-left: 10px">顺序学习</el-checkbox>
|
||||
</div>
|
||||
@@ -220,73 +254,85 @@ const handleNext = () => {
|
||||
<div>
|
||||
<dragCollapse v-model:courseList="courseList">
|
||||
<template #title="{ course }">{{ course.title }}</template>
|
||||
<template #desc="{ course }"
|
||||
>若课程只有一个章节,将不在学员端显示该章节名称</template
|
||||
>
|
||||
<template #desc="{ course }">
|
||||
若课程只有一个章节,将不在学员端显示该章节名称
|
||||
</template>
|
||||
<template #default="{ course, index }">
|
||||
<div class="drag-course-btn-content">
|
||||
<el-button
|
||||
v-for="btn in courseActionButtons"
|
||||
:key="btn.fun"
|
||||
type="primary"
|
||||
class="btn-item"
|
||||
plain
|
||||
@click="executeCourseOperation(btn.fun, index)"
|
||||
>{{ btn.label }}</el-button
|
||||
>
|
||||
{{ btn.label }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<!-- 修改:添加 groupId 和 tableId 属性以支持跨表格拖拽 -->
|
||||
<!-- 添加 groupId 和 tableId 属性以支持跨表格拖拽 -->
|
||||
<dragTable
|
||||
:data="course.data"
|
||||
:group-id="'course-chapters'"
|
||||
group-id="course-chapters"
|
||||
:table-id="'chapter-' + index"
|
||||
:index="index"
|
||||
@delete="deleteRow"
|
||||
@setting="settingRow"
|
||||
@preview="previewRow"
|
||||
></dragTable>
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</dragCollapse>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 选择文件列表-->
|
||||
<el-dialog v-model="showDialog" :title="getType(courseMetadata.resType)">
|
||||
<!-- 选择文件列表 -->
|
||||
<el-dialog
|
||||
v-model="courseMetadata.showDialog"
|
||||
:title="getType(courseMetadata.resType)"
|
||||
>
|
||||
<chooseFileList
|
||||
v-if="showDialog"
|
||||
v-if="courseMetadata.showDialog"
|
||||
@chooseItem="chooseItem"
|
||||
@choosePreviewItem="choosePreviewItem"
|
||||
:resType="courseMetadata.resType"
|
||||
:showTablePreview="showTablePreview"
|
||||
:showTablePreview="courseMetadata.showTablePreview"
|
||||
@chooseCusExam="chooseCusExam"
|
||||
></chooseFileList>
|
||||
/>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 设置预览弹窗 -->
|
||||
<!-- 设置预览弹窗 -->
|
||||
<el-dialog
|
||||
v-model="showSettingDialog"
|
||||
:title="isPreview ? '预览' : getType(chooseItemData.resType)"
|
||||
v-model="courseMetadata.showSettingDialog"
|
||||
:title="
|
||||
courseMetadata.isPreview ? '预览' : getType(chooseItemData.resType)
|
||||
"
|
||||
:fullscreen="chooseItemData.resType === 41"
|
||||
>
|
||||
<div style="max-height: 600px; overflow: auto">
|
||||
<template v-for="item in mapComponents">
|
||||
<div class="component-preview">
|
||||
<template v-for="item in mapComponents" :key="item.name">
|
||||
<component
|
||||
v-if="
|
||||
Number(chooseItemData.resType) === item.resType &&
|
||||
showSettingDialog
|
||||
courseMetadata.showSettingDialog
|
||||
"
|
||||
:is="item"
|
||||
v-model:dialogVideoForm="chooseItemData"
|
||||
:isPreview="isPreview"
|
||||
:classId="classId"
|
||||
></component>
|
||||
:isPreview="courseMetadata.isPreview"
|
||||
:classId="courseMetadata.classId"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancelSave()">取消</el-button>
|
||||
<el-button type="primary" @click="saveContent()" v-if="!isPreview">
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="saveContent()"
|
||||
v-if="!courseMetadata.isPreview"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -305,20 +351,32 @@ const handleNext = () => {
|
||||
.create-course {
|
||||
width: 100%;
|
||||
padding: 10px 20px;
|
||||
|
||||
.course-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.course-actions {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.drag-course-btn-content {
|
||||
padding: 0 10px;
|
||||
|
||||
.btn-item + .btn-item {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.component-preview {
|
||||
max-height: 600px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user