|
|
@@ -4,6 +4,7 @@
|
|
|
|
|
|
<CommonPreview :id="id" ref="preview" :project-id="project_id">
|
|
|
<template #operator="{ courseware }">
|
|
|
+ <span class="link" @click="selectDirectory">下载离线包</span>
|
|
|
<span class="link" @click="saveCoursewareAsTemplate">保存为个人模板</span>
|
|
|
<span v-if="isTrue(courseware.is_can_start_edit)" class="link" @click="editTask">开始编辑</span>
|
|
|
<span v-if="isTrue(courseware.is_can_submit_audit)" class="link" @click="submitCoursewareToAuditFlow">
|
|
|
@@ -22,6 +23,7 @@ import CommonPreview from '@/components/CommonPreview.vue';
|
|
|
import { SubmitBookCoursewareToAuditFlow } from '@/api/project';
|
|
|
import { isTrue } from '@/utils/validate';
|
|
|
import { SaveCoursewareAsTemplatePersonal } from '@/api/template';
|
|
|
+import { CreateBookChapterStructFileList, CreateCoursewareFileList } from '@/api/offline';
|
|
|
|
|
|
export default {
|
|
|
name: 'TaskPreviewPage',
|
|
|
@@ -34,8 +36,41 @@ export default {
|
|
|
id: this.$route.params.id,
|
|
|
project_id: this.$route.query.project_id,
|
|
|
isTrue,
|
|
|
+ loadingInstance: null, // 加载中实例
|
|
|
+ tempDir: '', // 临时目录,用于存放下载的文件
|
|
|
+ savePath: '', // 保存离线包的路径
|
|
|
+ packageName: 'EEP 离线包', // 离线包名称
|
|
|
+ file_info_list: [], // 章节结构文件列表
|
|
|
+ downloadTotal: 0, // 下载总数
|
|
|
+ downloadCompleted: 0, // 已下载数量
|
|
|
+ downloadWatcher: null, // 下载进度监听器
|
|
|
};
|
|
|
},
|
|
|
+ computed: {
|
|
|
+ downloadProgress() {
|
|
|
+ if (this.downloadTotal === 0) return 0;
|
|
|
+ return (this.downloadCompleted / this.downloadTotal) * 100;
|
|
|
+ },
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ // 删除临时目录及其内容
|
|
|
+ if (this.tempDir && this.tempDir.length > 0) {
|
|
|
+ window.fileAPI.deleteTempDir(this.tempDir);
|
|
|
+ this.tempDir = '';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除 watch 监听器
|
|
|
+ if (this.downloadWatcher) {
|
|
|
+ this.downloadWatcher();
|
|
|
+ this.downloadWatcher = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 关闭加载中实例
|
|
|
+ if (this.loadingInstance) {
|
|
|
+ this.loadingInstance.close();
|
|
|
+ this.loadingInstance = null;
|
|
|
+ }
|
|
|
+ },
|
|
|
methods: {
|
|
|
goBackBookList() {
|
|
|
this.$router.push({ path: '/personal_workbench/edit_task' });
|
|
|
@@ -71,6 +106,152 @@ export default {
|
|
|
})
|
|
|
.catch(() => {});
|
|
|
},
|
|
|
+ /**
|
|
|
+ * 选择保存离线包的目录
|
|
|
+ */
|
|
|
+ async selectDirectory() {
|
|
|
+ const result = await window.fileAPI.openFileDialog({
|
|
|
+ title: '选择保存离线包的文件夹',
|
|
|
+ properties: ['openDirectory', 'createDirectory'],
|
|
|
+ });
|
|
|
+ if (result.canceled) {
|
|
|
+ this.$message.warning('未选择文件夹,操作已取消');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.savePath = result.filePaths[0];
|
|
|
+ this.downloadOfflinePackage();
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 下载离线包
|
|
|
+ */
|
|
|
+ async downloadOfflinePackage() {
|
|
|
+ const { file_info_list, courseware_id_list } = await CreateBookChapterStructFileList({
|
|
|
+ book_id: this.project_id,
|
|
|
+ });
|
|
|
+
|
|
|
+ this.file_info_list = file_info_list;
|
|
|
+ if (file_info_list.length === 0) {
|
|
|
+ this.$message.error('章节结构文件生成失败,无法下载离线包');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.tempDir.length === 0) {
|
|
|
+ this.tempDir = window.fileAPI.createTempDir(); // 创建临时保存目录
|
|
|
+ }
|
|
|
+
|
|
|
+ this.loadingInstance = this.$loading({
|
|
|
+ lock: true,
|
|
|
+ text: '正在下载离线包,请稍候...',
|
|
|
+ spinner: 'el-icon-loading',
|
|
|
+ background: 'rgba(255, 255, 255, 0.7)',
|
|
|
+ });
|
|
|
+
|
|
|
+ for (const { dir_name, file_name, file_url } of this.file_info_list) {
|
|
|
+ const dirPath = dir_name.length > 0 ? `${this.tempDir}\\${dir_name}` : this.tempDir;
|
|
|
+ await window.fileAPI.downloadFile(file_url, `${dirPath}\\${file_name}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ const struct = await this.readFileContent('struct.json'); // 读取章节结构文件内容
|
|
|
+
|
|
|
+ if (struct.node_list && struct.node_list.length > 0) {
|
|
|
+ this.packageName = struct.node_list[0].name; // 设置离线包名称为课件名称
|
|
|
+ }
|
|
|
+
|
|
|
+ if (courseware_id_list && courseware_id_list.length > 0) {
|
|
|
+ this.downloadWatcher = this.$watch(
|
|
|
+ 'downloadProgress',
|
|
|
+ (newVal) => {
|
|
|
+ if (newVal >= 100) {
|
|
|
+ this.downloadWatcher();
|
|
|
+ this.downloadWatcher = null;
|
|
|
+ this.startCompress();
|
|
|
+ } else {
|
|
|
+ // 显示下载进度,预留 10% 的压缩进度
|
|
|
+ this.loadingInstance.text = `正在下载离线包,进度:${((this.downloadCompleted / (this.downloadTotal + this.downloadTotal * 0.1)) * 100).toFixed(2)}%`;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: true },
|
|
|
+ );
|
|
|
+
|
|
|
+ Promise.all(courseware_id_list.map((courseware_id) => this.createCoursewareFileList(courseware_id)));
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 下载课件文件列表
|
|
|
+ * @param {Number} courseware_id 课件ID
|
|
|
+ * @return {Promise} Promise对象
|
|
|
+ */
|
|
|
+ async createCoursewareFileList(courseware_id) {
|
|
|
+ const { file_info_list } = await CreateCoursewareFileList({ courseware_id });
|
|
|
+ this.downloadTotal += file_info_list.length;
|
|
|
+
|
|
|
+ file_info_list.forEach((fileInfo) => {
|
|
|
+ const { file_name, file_url, dir_name } = fileInfo;
|
|
|
+ window.fileAPI.downloadFile(file_url, `${this.tempDir}\\${dir_name}\\${file_name}`).then(() => {
|
|
|
+ this.downloadCompleted += 1;
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 开始压缩
|
|
|
+ */
|
|
|
+ async startCompress() {
|
|
|
+ const sources = [`${this.tempDir}\\resource`, `${this.tempDir}\\courseware`]; // 要压缩的文件或文件夹路径数组
|
|
|
+ this.file_info_list.forEach(({ dir_name, file_name }) => {
|
|
|
+ const dirPath = dir_name.length > 0 ? `${this.tempDir}\\${dir_name}` : this.tempDir;
|
|
|
+ sources.push(`${dirPath}\\${file_name}`);
|
|
|
+ });
|
|
|
+ const dest = `${this.savePath}\\${this.packageName}.eep`; // 压缩包保存路径
|
|
|
+ try {
|
|
|
+ const offErr = window.fileAPI.onStderr((text) => console.warn('7z stderr:', text));
|
|
|
+
|
|
|
+ await window.fileAPI.compress({
|
|
|
+ sources,
|
|
|
+ dest,
|
|
|
+ format: 'zip',
|
|
|
+ level: 5,
|
|
|
+ password: '1234567a',
|
|
|
+ recurse: true,
|
|
|
+ });
|
|
|
+ offErr();
|
|
|
+ } catch (e) {
|
|
|
+ console.error('压缩失败:', e);
|
|
|
+ this.$message.error('离线包下载失败,请重试!');
|
|
|
+ this.loadingInstance.close();
|
|
|
+ this.loadingInstance = null;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.loadingInstance.text = `正在下载离线包,进度:100%`;
|
|
|
+
|
|
|
+ // 删除临时目录及其内容
|
|
|
+ try {
|
|
|
+ await window.fileAPI.deleteTempDir(this.tempDir);
|
|
|
+ this.tempDir = '';
|
|
|
+ } catch (e) {
|
|
|
+ console.error('删除临时目录失败:', e);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.loadingInstance.close();
|
|
|
+ this.loadingInstance = null;
|
|
|
+
|
|
|
+ this.$message.success('离线包下载并保存成功!');
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 读取文件内容
|
|
|
+ * @param entryName 文件条目名称
|
|
|
+ * @param subdirectory 子目录
|
|
|
+ */
|
|
|
+ async readFileContent(entryName, subdirectory = '') {
|
|
|
+ const content = await window.fileAPI.readZipFileSync(`${this.tempDir}/${subdirectory}`, entryName);
|
|
|
+ const text = new TextDecoder().decode(content);
|
|
|
+ const obj = JSON.parse(text);
|
|
|
+ return obj;
|
|
|
+ },
|
|
|
},
|
|
|
};
|
|
|
</script>
|