refactor(course): 重构课程创建组件以支持文件选择和预览功能

- 将 AddVideo.vue 重命名为 chooseFileList.vue 并优化其内部逻辑
- 引入 watch API 并调整组件结构以提升响应性
- 更新事件发射器 saveContent 为 chooseItem 以匹配新流程
- 移除视频对话框相关代码并将其功能迁移至独立组件
- 在 createCourse.vue 中新增设置和预览弹窗逻辑
- 调整 dragTable.vue 的编辑和删除方法以传递完整的记录对象
- 统一使用响应式数据处理代替部分 refs 用法以简化状态管理
- 清理无关注释及调试语句提高代码可读性和维护性
This commit is contained in:
陈昱达
2025-11-21 15:12:59 +08:00
parent 8ebca12470
commit 6528491334
7 changed files with 762 additions and 394 deletions

View File

@@ -1,5 +1,5 @@
<script setup> <script setup>
import { reactive, onMounted, ref, h } from "vue"; import { reactive, onMounted, ref, h, watch } from "vue";
import { import {
ElButton, ElButton,
ElInput, ElInput,
@@ -12,6 +12,9 @@ import {
} from "element-plus"; } from "element-plus";
import BasicTable from "@/components/BasicElTable/BasicTable.vue"; import BasicTable from "@/components/BasicElTable/BasicTable.vue";
import { getPageListByType } from "@/hooks/useCreateCourseMaps"; import { getPageListByType } from "@/hooks/useCreateCourseMaps";
const props = defineProps({});
const tableData = ref([]); const tableData = ref([]);
const form = reactive({ const form = reactive({
name: "", name: "",
@@ -22,8 +25,7 @@ let pagination = reactive({
current: 1, current: 1,
total: 0, total: 0,
}); });
const dialogVideoForm = reactive({
const dialogVideoForm = ref({
name: "", name: "",
isDrag: false, isDrag: false,
completeSetup: 0, completeSetup: 0,
@@ -31,7 +33,7 @@ const dialogVideoForm = ref({
}); });
const loading = ref(false); const loading = ref(false);
const showDialog = ref(false); const showDialog = ref(false);
const emit = defineEmits(["saveContent"]); const emit = defineEmits(["chooseItem"]);
const columns = [ const columns = [
{ {
title: "序号", title: "序号",
@@ -67,12 +69,13 @@ const columns = [
type: "primary", type: "primary",
size: "small", size: "small",
onClick: () => { onClick: () => {
console.log(params); emit("chooseItem", {
showDialog.value = true;
dialogVideoForm.value = {
...dialogVideoForm.value,
...params.row, ...params.row,
}; isDrag: false,
completeSetup: 0,
setupTage: "",
resType: 10,
});
}, },
}, },
"选择" "选择"
@@ -101,9 +104,9 @@ const getVideoList = () => {
}); });
}; };
const saveContent = (type) => { const chooseItem = (type) => {
showDialog.value = false; showDialog.value = false;
emit("saveContent", { emit("chooseItem", {
...dialogVideoForm, ...dialogVideoForm,
type: 10, type: 10,
}); });
@@ -132,7 +135,7 @@ onMounted(() => {
</script> </script>
<template> <template>
<div class="add-video"> <div class="add-video" v-if="!isPreview && !isSetting">
<div class="add-vide-header"> <div class="add-vide-header">
<div> <div>
<el-button>上传新视频</el-button> <el-button>上传新视频</el-button>
@@ -158,57 +161,6 @@ onMounted(() => {
></BasicTable> ></BasicTable>
</div> </div>
</div> </div>
<el-dialog v-model="showDialog" title="视频">
<el-form>
<el-form-item label="视频名称">
<el-input v-model="dialogVideoForm.name"></el-input>
</el-form-item>
<video
controls
style="width: 100%; max-height: 400px"
class="mb10"
v-if="showDialog"
>
<source
:src="'http://home.hzer.xyz:9960/upload/' + dialogVideoForm.filePath"
type="video/mp4"
/>
您的浏览器不支持video
</video>
<el-form-item label="是否允许拖拽">
<el-radio-group v-model="dialogVideoForm.isDrag" size="small">
<el-radio :label="true" border></el-radio>
<el-radio :label="false" border></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="完成规则设置">
<el-radio-group v-model="dialogVideoForm.completeSetup">
<el-radio :label="0">默认(系统自动控制)</el-radio>
<el-radio :label="1"
>按进度
<el-input-number
:disabled="dialogVideoForm.completeSetup === 0"
v-model="dialogVideoForm.setupTage"
size="mini"
:min="0"
:max="100"
label="描述文字"
controls-position="right"
></el-input-number>
%</el-radio
>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveContent(1)"> 保存 </el-button>
</div>
</template>
</el-dialog>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -0,0 +1,109 @@
<script setup>
import {
ElForm,
ElFormItem,
ElInput,
ElInputNumber,
ElRadio,
ElRadioGroup,
} from "element-plus";
const props = defineProps({
dialogVideoForm: {
type: Object,
default: () => ({
name: "",
filePath: "",
isDrag: true,
completeSetup: 0,
setupTage: 0,
}),
},
isPreview: {
type: Boolean,
default: false,
},
});
import { ref, watch } from "vue";
// Create a reactive copy of the prop for local modifications
const localDialogVideoForm = ref({ ...props.dialogVideoForm });
// Watch for changes in the prop and update the local copy
watch(
() => props.dialogVideoForm,
(newVal) => {
Object.assign(localDialogVideoForm.value, newVal);
},
{ deep: true }
);
// Emit updates to parent component
const emit = defineEmits(["update:dialogVideoForm"]);
// Update form values and emit changes
const updateFormValue = (field, value) => {
localDialogVideoForm.value[field] = value;
emit("update:dialogVideoForm", { ...localDialogVideoForm.value });
};
</script>
<template>
<el-form>
<el-form-item label="视频名称" v-if="!isPreview">
<el-input
v-model="localDialogVideoForm.name"
@update:modelValue="(val) => updateFormValue('name', val)"
></el-input>
</el-form-item>
<!-- Added video type prop -->
<video
controls
style="width: 100%; max-height: 400px"
class="mb10"
:key="localDialogVideoForm.filePath"
>
<source
:src="
'http://home.hzer.xyz:9960/upload/' + localDialogVideoForm.filePath
"
type="video/mp4"
/>
您的浏览器不支持video
</video>
<el-form-item label="是否允许拖拽" v-if="!isPreview">
<el-radio-group
:model-value="localDialogVideoForm.isDrag"
@update:modelValue="(val) => updateFormValue('isDrag', val)"
size="small"
>
<el-radio :label="true" border></el-radio>
<el-radio :label="false" border></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="完成规则设置" v-if="!isPreview">
<el-radio-group
:model-value="localDialogVideoForm.completeSetup"
@update:modelValue="(val) => updateFormValue('completeSetup', val)"
>
<el-radio :label="0">默认(系统自动控制)</el-radio>
<el-radio :label="1">
按进度
<el-input-number
:disabled="localDialogVideoForm.completeSetup === 0"
:model-value="localDialogVideoForm.setupTage"
@update:modelValue="(val) => updateFormValue('setupTage', val)"
size="mini"
:min="0"
:max="100"
label="描述文字"
controls-position="right"
></el-input-number>
%
</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</template>
<style scoped lang="scss"></style>

File diff suppressed because it is too large Load Diff

View File

@@ -111,7 +111,6 @@ const initDictTree = (key) => {
}, },
}).then( }).then(
(res) => { (res) => {
console.log(res.data.result, "课程分类接口");
store.commit("SET_DICT", { key, data: res.data.result }); store.commit("SET_DICT", { key, data: res.data.result });
//转化为map放到状态中 //转化为map放到状态中
let map = new Map(); let map = new Map();

View File

@@ -6,49 +6,48 @@
* @FilePath: /fe-manage/src/router/index.js * @FilePath: /fe-manage/src/router/index.js
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/ */
import { createRouter, createWebHistory } from 'vue-router'; import { createRouter, createWebHistory } from "vue-router";
import routesConfig from './config'; import routesConfig from "./config";
import store from "@/store"; import store from "@/store";
const routes = [ const routes = [
{ {
path: '/', path: "/",
name: '首页', name: "首页",
redirect: '/learningpath' redirect: "/learningpath",
}, },
...routesConfig ...routesConfig,
] ];
const router = createRouter({ const router = createRouter({
history: createWebHistory(process.env.VUE_APP_BASE), history: createWebHistory(process.env.VUE_APP_BASE),
routes, routes,
});
})
let count = 0; let count = 0;
const timers = setInterval(() => { const timers = setInterval(() => {
count ++ count++;
let pathArr = store.state.menus let pathArr = store.state.menus;
let currentPath = router.options.history.location let currentPath = router.options.history.location;
console.log(pathArr, currentPath, pathArr.indexOf(currentPath)) // console.log(pathArr, currentPath, pathArr.indexOf(currentPath))
if(pathArr.length!==0){ if (pathArr.length !== 0) {
// 刷新界面是否重新执行 // 刷新界面是否重新执行
if(pathArr.indexOf(currentPath)==-1){ if (pathArr.indexOf(currentPath) == -1) {
if(localStorage.getItem("refreshPage") == "jumpverify"){ if (localStorage.getItem("refreshPage") == "jumpverify") {
clearInterval(timers) clearInterval(timers);
return return;
} }
clearInterval(timers) clearInterval(timers);
router.push({path: pathArr[0]}) router.push({ path: pathArr[0] });
localStorage.setItem("refreshPage", "jumpverify") localStorage.setItem("refreshPage", "jumpverify");
}else{ } else {
localStorage.setItem("refreshPage", "jumpverify") localStorage.setItem("refreshPage", "jumpverify");
clearInterval(timers) clearInterval(timers);
} }
} }
// 30s超时结束 // 30s超时结束
if(count==60&&pathArr.length==0){ if (count == 60 && pathArr.length == 0) {
clearInterval(timers) clearInterval(timers);
} }
}, 500); }, 500);
export default router export default router;

View File

@@ -2,16 +2,24 @@
import dragCollapse from "./dragCollapse.vue"; import dragCollapse from "./dragCollapse.vue";
import { ElButton, ElCheckbox, ElDialog } from "element-plus"; import { ElButton, ElCheckbox, ElDialog } from "element-plus";
import dragTable from "./dragTable.vue"; import dragTable from "./dragTable.vue";
import { ref } from "vue"; import { ref, reactive } from "vue";
defineOptions({ defineOptions({
name: "CreateCourse", name: "CreateCourse",
}); });
import { useCourseData } from "@/hooks/useCourseData"; import { useCourseData } from "@/hooks/useCourseData";
import AddVideoComp from "@/components/CreatedCourse/AddVideo.vue"; import chooseFileList from "@/components/CreatedCourse/chooseFileList.vue";
import VideoComp from "@/components/CreatedCourse/preview/VideoComp.vue";
const mapComponents = {
VideoComp,
};
// 使用课程数据hook // 使用课程数据hook
const { courseMetadata, courseList, courseActionButtons } = useCourseData(); const { courseMetadata, courseList, courseActionButtons } = useCourseData();
const isSetting = ref(false);
const isPreview = ref(false);
const chooseItemData = ref({});
const showSettingDialog = ref(false);
// 定义表格列 // 定义表格列
// 添加章 // 添加章
@@ -59,6 +67,7 @@ const courseOperations = {
const executeCourseOperation = (operationName, data) => { const executeCourseOperation = (operationName, data) => {
courseMetadata.chooseIndex = data; courseMetadata.chooseIndex = data;
courseMetadata.selectionIndex = null; courseMetadata.selectionIndex = null;
isPreview.value = false;
if (courseOperations[operationName]) { if (courseOperations[operationName]) {
courseOperations[operationName](data); courseOperations[operationName](data);
} else { } else {
@@ -66,28 +75,45 @@ const executeCourseOperation = (operationName, data) => {
} }
}; };
const isSetting = ref(false); const chooseItem = (data) => {
console.log(data);
chooseItemData.value = data;
const saveVideo = (data) => { console.log(chooseItemData.value);
showDialog.value = false; showSettingDialog.value = true;
if (isSetting.value) {
} else {
courseList.value[courseMetadata.chooseIndex].data.push(data);
}
}; };
const saveContent = () => {
console.log(chooseItemData.value);
if (courseMetadata.selectionIndex !== null) {
} else {
console.log(courseList.value, courseMetadata);
courseList.value[courseMetadata.chooseIndex].data.push(
chooseItemData.value
);
}
showDialog.value = false;
showSettingDialog.value = false;
};
const deleteRow = (data) => { const deleteRow = (data) => {
console.log(data);
courseMetadata.chooseIndex = data.index; courseMetadata.chooseIndex = data.index;
courseMetadata.selectionIndex = data.selectionIndex; courseMetadata.selectionIndex = data.selectionIndex;
chooseItemData.value = data.record;
}; };
const settingRow = (data) => { const settingRow = (data) => {
courseMetadata.chooseIndex = data.index; courseMetadata.chooseIndex = data.index;
courseMetadata.selectionIndex = data.selectionIndex; courseMetadata.selectionIndex = data.selectionIndex;
chooseItemData.value = data.record;
isPreview.value = false;
showSettingDialog.value = true;
}; };
const previewRow = (data) => { const previewRow = (data) => {
courseMetadata.chooseIndex = data.index; courseMetadata.chooseIndex = data.index;
courseMetadata.selectionIndex = data.selectionIndex; courseMetadata.selectionIndex = data.selectionIndex;
chooseItemData.value = data.record;
isPreview.value = true;
showSettingDialog.value = true;
}; };
</script> </script>
@@ -137,9 +163,27 @@ const previewRow = (data) => {
</div> </div>
</div> </div>
<!-- 课程按钮弹窗--> <!-- 选择文件列表-->
<el-dialog v-model="showDialog" title="请选择操作"> <el-dialog v-model="showDialog" title="请选择操作">
<AddVideoComp @saveContent="saveVideo"></AddVideoComp> <chooseFileList @chooseItem="chooseItem"></chooseFileList>
</el-dialog>
<!-- 设置预览弹窗 -->
<el-dialog v-model="showSettingDialog" title="请选择操作">
<component
v-for="item in mapComponents"
:is="item"
v-model:dialogVideoForm="chooseItemData"
:isPreview="isPreview"
></component>
<template #footer>
<div class="dialog-footer">
<el-button @click="showSettingDialog = false">取消</el-button>
<el-button type="primary" @click="saveContent()" v-if="!isPreview">
保存
</el-button>
</div>
</template>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>

View File

@@ -101,26 +101,26 @@ const editValue = ref("");
// 开始编辑 // 开始编辑
const startEdit = (record) => { const startEdit = (record) => {
record._value.copyName = record._value.name; record.copyName = record.name;
record._value.isEdit = true; record.isEdit = true;
}; };
// 保存编辑 // 保存编辑
const saveEdit = (record) => { const saveEdit = (record) => {
record._value.name = record._value.copyName; record.name = record.copyName;
record._value.copyName = null; record.copyName = null;
record._value.isEdit = false; record.isEdit = false;
}; };
// 删除处理函数 // 删除处理函数
const handleDelete = (index) => { const handleDelete = (index, record) => {
emit("delete", { index: props.index, selectionIndex: index }); emit("delete", { index: props.index, selectionIndex: index, record });
}; };
const handleSetting = (index) => { const handleSetting = (index, record) => {
emit("setting", { index: props.index, selectionIndex: index }); emit("setting", { index: props.index, selectionIndex: index, record });
}; };
const handlePreview = (index) => { const handlePreview = (index, record) => {
emit("preview", { index: props.index, selectionIndex: index }); emit("preview", { index: props.index, selectionIndex: index, record });
}; };
// 渲染序号列 // 渲染序号列
@@ -149,16 +149,17 @@ const renderIndexColumn = () => {
// 渲染名称列 // 渲染名称列
const renderNameColumn = () => { const renderNameColumn = () => {
return ({ record }) => { return ({ record }) => {
console.log(record);
// 如果处于编辑状态,显示输入框和确认按钮 // 如果处于编辑状态,显示输入框和确认按钮
if (record._value.isEdit) { if (record.isEdit) {
return h( return h(
"span", "span",
{ style: { display: "flex", alignItems: "center", gap: "8px" } }, { style: { display: "flex", alignItems: "center", gap: "8px" } },
[ [
h("input", { h("input", {
value: record._value.copyName, value: record.copyName,
onInput: (e) => { onInput: (e) => {
record._value.copyName = e.target.value; record.copyName = e.target.value;
}, },
style: { style: {
border: "1px solid #d9d9d9", border: "1px solid #d9d9d9",
@@ -183,13 +184,13 @@ const renderNameColumn = () => {
} }
// 否则显示正常文本和编辑图标 // 否则显示正常文本和编辑图标
const Icon = getIconComponent(getType(record._value.resType)); const Icon = getIconComponent(getType(record.resType));
return h( return h(
"span", "span",
{ style: { display: "flex", alignItems: "center", gap: "8px" } }, { style: { display: "flex", alignItems: "center", gap: "8px" } },
[ [
createVNode(Icon, { style: { color: "#1890ff" } }), createVNode(Icon, { style: { color: "#1890ff" } }),
h("span", {}, record._value.name), h("span", {}, record.name),
h( h(
"a", "a",
{ {
@@ -217,7 +218,7 @@ const renderResTypeColumn = () => {
textAlign: "center", textAlign: "center",
}, },
}, },
getType(record._value.resType) getType(record.resType)
); );
}; };
}; };
@@ -234,7 +235,10 @@ const renderActionColumn = () => {
// 设置 // 设置
h( h(
"a", "a",
{ href: "javascript:void(0)", onClick: () => handleSetting(index) }, {
href: "javascript:void(0)",
onClick: () => handleSetting(index, record),
},
[ [
createVNode(SettingOutlined, { createVNode(SettingOutlined, {
style: { style: {
@@ -253,7 +257,10 @@ const renderActionColumn = () => {
// 预览 // 预览
h( h(
"a", "a",
{ href: "javascript:void(0)", onClick: () => handlePreview(index) }, {
href: "javascript:void(0)",
onClick: () => handlePreview(index, record),
},
[ [
createVNode(EyeOutlined, { createVNode(EyeOutlined, {
style: { fontSize: "14px", color: "#1890ff" }, style: { fontSize: "14px", color: "#1890ff" },
@@ -270,7 +277,7 @@ const renderActionColumn = () => {
"a", "a",
{ {
href: "javascript:void(0)", href: "javascript:void(0)",
onClick: () => handleDelete(index), onClick: () => handleDelete(index, record),
}, },
[ [
createVNode(DeleteOutlined, { createVNode(DeleteOutlined, {
@@ -336,9 +343,10 @@ const processedColumns = computed(() => {
}, },
{ {
title: "类型", title: "类型",
key: "type", key: "resType",
dataIndex: "type", dataIndex: "resType",
align: "center", align: "center",
customRender: renderResTypeColumn(),
}, },
{ {
title: "操作", title: "操作",