mirror of
https://codeup.aliyun.com/67762337eccfc218f6110e0e/vue/fe-manage.git
synced 2025-12-06 09:26:44 +08:00
feat(course): 添加文档类型资源支持
- 新增文档组件 DocComp.vue,支持 PDF 预览功能 - 在 createCourse.vue 中集成文档类型处理逻辑 - 更新 chooseFileList.vue 支持文档类型上传 - 重构音视频组件,提取公共逻辑至 useMediaComponent hook - 统一文件路径前缀为 fileBaseUrl,替换硬编码地址 - 调整拖拽表格操作按钮样式及显示逻辑 - 添加 pdf-vue3 依赖用于 PDF 渲染支持 - TODO - 上传文件目前缺少id 无法进行预览 这是一个问题!
This commit is contained in:
5109
package-lock.json
generated
5109
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -28,6 +28,7 @@
|
||||
"json-bigint": "^1.0.0",
|
||||
"mitt": "^3.0.0",
|
||||
"moment": "^2.29.4",
|
||||
"pdf-vue3": "^1.0.12",
|
||||
"qrcode.vue": "^3.3.3",
|
||||
"qs": "^6.11.0",
|
||||
"quill": "^2.0.3",
|
||||
|
||||
@@ -148,7 +148,7 @@ onMounted(() => {
|
||||
:on-success="uploadSuccess"
|
||||
:file-list="fileList"
|
||||
>
|
||||
<el-button v-if="[10, 20].includes(props.resType)" type="primary"
|
||||
<el-button v-if="[10, 20, 40].includes(props.resType)" type="primary"
|
||||
>上传新{{ getType(props.resType) }}</el-button
|
||||
>
|
||||
</el-upload>
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
ElRadio,
|
||||
ElRadioGroup,
|
||||
} from "element-plus";
|
||||
|
||||
defineOptions({
|
||||
resType: 20,
|
||||
});
|
||||
@@ -27,27 +28,13 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
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 }
|
||||
);
|
||||
|
||||
import { useMediaComponent } from "@/hooks/useMediaComponent";
|
||||
// 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 });
|
||||
};
|
||||
// 使用hook处理公共逻辑
|
||||
const { localDialogVideoForm, updateFormValue, fileBaseUrl } =
|
||||
useMediaComponent(props, emit);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -64,7 +51,7 @@ const updateFormValue = (field, value) => {
|
||||
style="width: 100%; max-height: 400px"
|
||||
class="mb10"
|
||||
:key="localDialogVideoForm.filePath"
|
||||
:src="'http://home.hzer.xyz:9960/upload/' + localDialogVideoForm.filePath"
|
||||
:src="fileBaseUrl + localDialogVideoForm.filePath"
|
||||
>
|
||||
您的浏览器不支持video
|
||||
</audio>
|
||||
|
||||
114
src/components/CreatedCourse/preview/DocComp.vue
Normal file
114
src/components/CreatedCourse/preview/DocComp.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<script setup>
|
||||
import apiCourseFile from "@/api/modules/courseFile";
|
||||
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||
import PDF from "pdf-vue3";
|
||||
defineOptions({
|
||||
resType: 40,
|
||||
});
|
||||
const props = defineProps({
|
||||
dialogVideoForm: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
name: "",
|
||||
filePath: "",
|
||||
isDrag: true,
|
||||
completeSetup: 0,
|
||||
setupTage: 0,
|
||||
}),
|
||||
},
|
||||
isPreview: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
scrollToID: {
|
||||
type: String,
|
||||
default: "pdf-perView",
|
||||
},
|
||||
autoScroll: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
import { useMediaComponent } from "@/hooks/useMediaComponent";
|
||||
// Emit updates to parent component
|
||||
const emit = defineEmits(["update:dialogVideoForm"]);
|
||||
|
||||
// 使用hook处理公共逻辑
|
||||
const { localDialogVideoForm, updateFormValue, fileBaseUrl } =
|
||||
useMediaComponent(props, emit);
|
||||
|
||||
// 添加响应式pdf路径变量
|
||||
const pdfPath = ref("");
|
||||
|
||||
const loadPdfFile = () => {
|
||||
console.log(localDialogVideoForm.value);
|
||||
|
||||
//检查当前是否是pdf,如果是pdf那么就不需要再查询一次了
|
||||
let fname = localDialogVideoForm.value.filePath;
|
||||
if (fname && fname.indexOf(".pdf") > -1) {
|
||||
localDialogVideoForm.value.pdfPath = localDialogVideoForm.value.filePath;
|
||||
pdfPath.value = localDialogVideoForm.value.filePath;
|
||||
console.log(123123);
|
||||
} else {
|
||||
apiCourseFile.detail(localDialogVideoForm.value.id).then((rs) => {
|
||||
if (rs.status == 200) {
|
||||
if (
|
||||
rs.result.previewFilePath == "" &&
|
||||
rs.result.filePath.indexOf("pdf") > -1
|
||||
) {
|
||||
pdfPath.value = rs.result.filePath;
|
||||
} else {
|
||||
pdfPath.value = rs.result.previewFilePath;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 定义响应式数据
|
||||
const currentPage = ref(0);
|
||||
// 加载进度
|
||||
const loadedRatio = ref(0);
|
||||
const showProgress = ref(true);
|
||||
|
||||
// 定义 refs
|
||||
const pdfRef = ref(null);
|
||||
|
||||
// PDF 加载处理
|
||||
const loadPdfHandler = (e) => {
|
||||
currentPage.value = 1; // 加载的时候先加载第一页
|
||||
};
|
||||
|
||||
// 监听 loadedRatio 变化
|
||||
watch(loadedRatio, (newVal) => {
|
||||
// 直接使用loadedRatio控制进度条没有加载效果
|
||||
if (newVal == 1) {
|
||||
setTimeout(() => {
|
||||
showProgress.value = false;
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
loadPdfFile();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pdf-perView" id="pdf-perView">
|
||||
<div class="pdf-box" style="height: 505px; overflow: hidden">
|
||||
<PDF
|
||||
style="max-height: 500px; overflow: scroll"
|
||||
ref="pdfRef"
|
||||
:src="fileBaseUrl + pdfPath"
|
||||
v-if="pdfPath"
|
||||
@onComplete="loadPdfHandler"
|
||||
></PDF>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -75,26 +75,12 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
// 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 }
|
||||
);
|
||||
|
||||
import { useMediaComponent } from "@/hooks/useMediaComponent";
|
||||
// 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 });
|
||||
};
|
||||
// 使用hook处理公共逻辑
|
||||
const { localDialogVideoForm, updateFormValue } = useMediaComponent(props, emit);
|
||||
|
||||
const editor = ref(null);
|
||||
|
||||
@@ -157,4 +143,4 @@ watch(
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -28,27 +28,13 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
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 }
|
||||
);
|
||||
|
||||
import { useMediaComponent } from "@/hooks/useMediaComponent";
|
||||
// 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 });
|
||||
};
|
||||
// 使用hook处理公共逻辑
|
||||
const { localDialogVideoForm, updateFormValue, fileBaseUrl } =
|
||||
useMediaComponent(props, emit);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -67,9 +53,7 @@ const updateFormValue = (field, value) => {
|
||||
:key="localDialogVideoForm.filePath"
|
||||
>
|
||||
<source
|
||||
:src="
|
||||
'http://home.hzer.xyz:9960/upload/' + localDialogVideoForm.filePath
|
||||
"
|
||||
:src="fileBaseUrl + localDialogVideoForm.filePath"
|
||||
type="video/mp4"
|
||||
/>
|
||||
您的浏览器不支持video
|
||||
|
||||
@@ -11,8 +11,9 @@ import chooseFileList from "@/components/CreatedCourse/chooseFileList.vue";
|
||||
import VideoComp from "@/components/CreatedCourse/preview/VideoComp.vue";
|
||||
import AudioComp from "@/components/CreatedCourse/preview/AudioComp.vue";
|
||||
import EditorComp from "@/components/CreatedCourse/preview/EditorComp.vue";
|
||||
import DocComp from "@/components/CreatedCourse/preview/DocComp.vue";
|
||||
import { getType } from "@/hooks/useCreateCourseMaps";
|
||||
const mapComponents = [VideoComp, AudioComp, EditorComp];
|
||||
const mapComponents = [VideoComp, AudioComp, EditorComp, DocComp];
|
||||
|
||||
// 使用课程数据hook
|
||||
const { courseMetadata, courseList, courseActionButtons, addChapter } =
|
||||
@@ -20,6 +21,8 @@ const { courseMetadata, courseList, courseActionButtons, addChapter } =
|
||||
const isPreview = ref(false);
|
||||
const chooseItemData = ref({});
|
||||
const showSettingDialog = ref(false);
|
||||
const isNext = ref(true);
|
||||
const showTablePreview = ref(false);
|
||||
// 定义表格列
|
||||
const showDialog = ref(false);
|
||||
// 课程操作映射
|
||||
@@ -32,7 +35,11 @@ const courseOperations = {
|
||||
courseMetadata.resType = 20;
|
||||
showDialog.value = true;
|
||||
},
|
||||
addDocument: () => {},
|
||||
addDocument: () => {
|
||||
courseMetadata.resType = 40;
|
||||
showDialog.value = true;
|
||||
isNext.value = false;
|
||||
},
|
||||
addImageText: () => {
|
||||
courseMetadata.resType = 41;
|
||||
chooseItemData.value.resType = 41;
|
||||
@@ -67,6 +74,10 @@ const executeCourseOperation = (operationName, data) => {
|
||||
};
|
||||
const chooseItem = (data) => {
|
||||
chooseItemData.value = data;
|
||||
if (!isNext.value) {
|
||||
saveContent();
|
||||
return;
|
||||
}
|
||||
showSettingDialog.value = true;
|
||||
};
|
||||
// 保存
|
||||
@@ -166,6 +177,7 @@ const previewRow = (data) => {
|
||||
v-if="showDialog"
|
||||
@chooseItem="chooseItem"
|
||||
:resType="courseMetadata.resType"
|
||||
:showTablePreview="showTablePreview"
|
||||
></chooseFileList>
|
||||
</el-dialog>
|
||||
|
||||
@@ -175,9 +187,10 @@ const previewRow = (data) => {
|
||||
:title="isPreview ? '预览' : getType(chooseItemData.resType)"
|
||||
>
|
||||
<div v-for="item in mapComponents">
|
||||
{{ chooseItemData.resType }}, {{ item }}
|
||||
<component
|
||||
v-if="Number(chooseItemData.resType) === item.resType"
|
||||
v-if="
|
||||
Number(chooseItemData.resType) === item.resType && showSettingDialog
|
||||
"
|
||||
:is="item"
|
||||
v-model:dialogVideoForm="chooseItemData"
|
||||
:isPreview="isPreview"
|
||||
|
||||
@@ -230,7 +230,7 @@ const renderActionColumn = () => {
|
||||
return h(
|
||||
"span",
|
||||
{
|
||||
style: { display: "flex", justifyContent: "center", gap: "12px" },
|
||||
style: { display: "flex", justifyContent: "flex-end", gap: "12px" },
|
||||
},
|
||||
[
|
||||
// 设置
|
||||
@@ -239,6 +239,9 @@ const renderActionColumn = () => {
|
||||
{
|
||||
href: "javascript:void(0)",
|
||||
onClick: () => handleSetting(index, record),
|
||||
style: {
|
||||
display: [40].includes(record.resType) ? "none" : "",
|
||||
},
|
||||
},
|
||||
[
|
||||
createVNode(SettingOutlined, {
|
||||
|
||||
Reference in New Issue
Block a user