|
|
@@ -51,8 +51,9 @@
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="creator_name" label="创建人" width="120" align="center" />
|
|
|
<el-table-column prop="create_time" label="创建时间" width="160" align="center" />
|
|
|
- <el-table-column label="操作" fixed="right" width="150" align="center">
|
|
|
+ <el-table-column label="操作" fixed="right" width="220" align="center">
|
|
|
<template slot-scope="{ row }">
|
|
|
+ <span class="link" @click="selectDirectory(row.id)">导出离线包</span>
|
|
|
<span class="link" @click="handleDisable(row.id)">废弃</span>
|
|
|
<el-divider direction="vertical" />
|
|
|
<span class="link" @click="handleDel(row.id)">删除</span>
|
|
|
@@ -74,15 +75,15 @@
|
|
|
</el-input>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="有效次数" prop="effective_count">
|
|
|
- <el-input v-model.number="editForm.effective_count" placeholder="请输入有效次数"></el-input>
|
|
|
+ <el-input v-model.number="editForm.effective_count" placeholder="请输入有效次数" />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="有效截止日期" prop="effective_end_date">
|
|
|
<el-date-picker
|
|
|
+ v-model="editForm.effective_end_date"
|
|
|
type="date"
|
|
|
placeholder="选择日期"
|
|
|
- v-model="editForm.effective_end_date"
|
|
|
value-format="yyyy-MM-dd"
|
|
|
- ></el-date-picker>
|
|
|
+ />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="备注" prop="memo">
|
|
|
<el-input v-model="editForm.memo" type="textarea" />
|
|
|
@@ -127,6 +128,8 @@ import {
|
|
|
DisableBookOfflinePackAuth,
|
|
|
BookOfflinePackActivate,
|
|
|
PageQueryBookOfflinePackAuthList,
|
|
|
+ CreateBookOfflinePackChapterStructFileList,
|
|
|
+ CreateOfflinePackCoursewareFileList,
|
|
|
} from '@/api/offline';
|
|
|
import { isTrue } from '@/utils/validate';
|
|
|
|
|
|
@@ -169,9 +172,22 @@ export default {
|
|
|
],
|
|
|
effective_end_date: [{ required: true, message: '请选择有效截止日期', trigger: 'blur' }],
|
|
|
},
|
|
|
+ loadingInstance: null, // 加载中实例
|
|
|
+ tempDir: '', // 临时目录,用于存放下载的文件
|
|
|
+ savePath: '', // 保存离线包的路径
|
|
|
+ packageName: 'EEP 离线包', // 离线包名称
|
|
|
+ file_info_list: [], // 章节结构文件列表
|
|
|
+ downloadTotal: 0, // 下载总数
|
|
|
+ downloadCompleted: 0, // 已下载数量
|
|
|
+ downloadWatcher: null, // 下载进度监听器
|
|
|
};
|
|
|
},
|
|
|
- computed: {},
|
|
|
+ computed: {
|
|
|
+ downloadProgress() {
|
|
|
+ if (this.downloadTotal === 0) return 0;
|
|
|
+ return (this.downloadCompleted / this.downloadTotal) * 100;
|
|
|
+ },
|
|
|
+ },
|
|
|
watch: {
|
|
|
dialogFormEdit: {
|
|
|
handler(val) {
|
|
|
@@ -182,6 +198,25 @@ export default {
|
|
|
deep: true,
|
|
|
},
|
|
|
},
|
|
|
+ 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: {
|
|
|
queryList() {
|
|
|
this.pageList(this.queryForm);
|
|
|
@@ -194,7 +229,7 @@ export default {
|
|
|
this.list = offline_pack_auth_list;
|
|
|
this.total = total_count;
|
|
|
this.cur_page_begin_index = cur_page_begin_index;
|
|
|
- }
|
|
|
+ },
|
|
|
);
|
|
|
},
|
|
|
pageBookList(data) {
|
|
|
@@ -223,7 +258,7 @@ export default {
|
|
|
let data = this.editForm;
|
|
|
this.dialogSearchBook = false;
|
|
|
AddBookOfflinePackAuth(data).then((res) => {
|
|
|
- if (res && res.status == 1) {
|
|
|
+ if (res && res.status === 1) {
|
|
|
this.dialogFormEdit = false;
|
|
|
this.queryList();
|
|
|
}
|
|
|
@@ -241,7 +276,7 @@ export default {
|
|
|
type: 'warning',
|
|
|
})
|
|
|
.then(() => {
|
|
|
- DisableBookOfflinePackAuth({ id: id }).then(() => {
|
|
|
+ DisableBookOfflinePackAuth({ id }).then(() => {
|
|
|
this.queryList();
|
|
|
});
|
|
|
})
|
|
|
@@ -254,17 +289,170 @@ export default {
|
|
|
type: 'warning',
|
|
|
})
|
|
|
.then(() => {
|
|
|
- DeleteBookOfflinePackAuth({ id: id }).then(() => {
|
|
|
+ DeleteBookOfflinePackAuth({ id }).then(() => {
|
|
|
this.queryList();
|
|
|
});
|
|
|
})
|
|
|
.catch(() => {});
|
|
|
},
|
|
|
handleActive(id) {
|
|
|
- BookOfflinePackActivate({ id: id }).then(() => {
|
|
|
+ BookOfflinePackActivate({ id }).then(() => {
|
|
|
this.queryList();
|
|
|
});
|
|
|
},
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 选择保存离线包的目录
|
|
|
+ * @param {String} book_id 教材ID
|
|
|
+ */
|
|
|
+ async selectDirectory(book_id) {
|
|
|
+ const result = await window.fileAPI.openFileDialog({
|
|
|
+ title: '选择保存离线包的文件夹',
|
|
|
+ properties: ['openDirectory', 'createDirectory'],
|
|
|
+ });
|
|
|
+ if (result.canceled) {
|
|
|
+ this.$message.warning('未选择文件夹,操作已取消');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.savePath = result.filePaths[0];
|
|
|
+ this.downloadOfflinePackage(book_id);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 下载离线包
|
|
|
+ * @param {String} offline_pack_auth_id 离线包授权ID
|
|
|
+ */
|
|
|
+ async downloadOfflinePackage(offline_pack_auth_id) {
|
|
|
+ const { file_info_list, courseware_id_list } = await CreateBookOfflinePackChapterStructFileList({
|
|
|
+ offline_pack_auth_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.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.CreateOfflinePackCoursewareFileList(courseware_id)));
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 下载课件文件列表
|
|
|
+ * @param {Number} courseware_id 课件ID
|
|
|
+ * @return {Promise} Promise对象
|
|
|
+ */
|
|
|
+ async CreateOfflinePackCoursewareFileList(courseware_id) {
|
|
|
+ const { file_info_list } = await CreateOfflinePackCoursewareFileList({ 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() {
|
|
|
+ if (this.downloadWatcher) {
|
|
|
+ this.downloadWatcher = null;
|
|
|
+ }
|
|
|
+ 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;
|
|
|
+ this.tempDir = '';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.loadingInstance.text = `正在下载离线包,进度:100%`;
|
|
|
+
|
|
|
+ // 删除临时目录及其内容
|
|
|
+ try {
|
|
|
+ await window.fileAPI.deleteTempDir(this.tempDir);
|
|
|
+ this.tempDir = '';
|
|
|
+ } catch (e) {
|
|
|
+ console.error('删除临时目录失败:', e);
|
|
|
+ this.tempDir = '';
|
|
|
+ }
|
|
|
+
|
|
|
+ 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>
|