| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477 |
- <template>
- <div class="book org">
- <ProjectMenu cur-key="org/offlinepackageauth" />
- <el-row>
- <el-button type="primary" @click="dialogFormEdit = true">创建授权</el-button>
- </el-row>
- <el-divider />
- <div id="query-form">
- <el-form inline>
- <el-form-item prop="book_name" label="教材">
- <el-input v-model="queryForm.book_name" clearable />
- </el-form-item>
- <el-form-item prop="auth_code" label="授权码">
- <el-input v-model="queryForm.auth_code" clearable />
- </el-form-item>
- <el-form-item prop="memo" label="备注">
- <el-input v-model="queryForm.memo" clearable />
- </el-form-item>
- <el-form-item class="search-box">
- <el-button class="search-btn" type="primary" @click="queryList">查询</el-button>
- </el-form-item>
- </el-form>
- </div>
- <div class="book-list">
- <el-table :data="list">
- <el-table-column label="序号" width="60" align="center" header-align="center" class-name="index-column">
- <template slot-scope="{ $index }">
- {{ cur_page_begin_index + $index }}
- </template>
- </el-table-column>
- <el-table-column prop="book_name" min-width="200" label="教材" />
- <el-table-column prop="auth_code" label="授权码" width="180" align="center" />
- <el-table-column prop="effective_count" label="有效次数" width="100" align="right" />
- <el-table-column label="激活状态" width="100" align="center">
- <template slot-scope="{ row }">
- {{ isTrue(row.is_activated) ? '已激活' : '未激活' }}
- </template>
- </el-table-column>
- <el-table-column label="激活时间" width="160" align="center">
- <template slot-scope="{ row }">
- <span v-if="isTrue(row.is_activated)">{{ row.activate_time }}</span>
- <span v-else>--</span>
- </template>
- </el-table-column>
- <el-table-column label="有效截止日期" width="120" align="center">
- <template slot-scope="{ row }">
- <span v-if="isTrue(row.is_disabled)" style="color: red">已废弃</span>
- <span v-else>{{ row.effective_end_date }}</span>
- </template>
- </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="180" align="center">
- <template slot-scope="{ row }">
- <span class="link" @click="selectDirectory(row.id)">导出离线包</span>
- <el-divider direction="vertical" />
- <span class="link" @click="handleDisable(row.id)">废弃</span>
- <el-divider direction="vertical" />
- <span class="link" @click="handleDel(row.id)">删除</span>
- </template>
- </el-table-column>
- <el-table-column prop="memo" label="备注" />
- </el-table>
- <PaginationPage :total="total" @getList="pageList" />
- </div>
- <el-dialog title="创建授权" width="500px" :visible.sync="dialogFormEdit" :close-on-click-modal="false">
- <el-form ref="editForm" :model="editForm" :rules="rules" label-width="120px">
- <el-form-item label="教材" prop="book_name">
- <el-input v-model="editForm.book_name" disabled class="input-with-select" placeholder="请选择教材">
- <el-button slot="append" icon="el-icon-search" @click="handleSearchBook('')" />
- </el-input>
- </el-form-item>
- <el-form-item label="有效次数" prop="effective_count">
- <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="选择日期"
- value-format="yyyy-MM-dd"
- />
- </el-form-item>
- <el-form-item label="备注" prop="memo">
- <el-input v-model="editForm.memo" type="textarea" />
- </el-form-item>
- </el-form>
- <div slot="footer" class="dialog-footer">
- <el-button @click="dialogFormEdit = false">取 消</el-button>
- <el-button type="primary" @click="handleSave('editForm')">确 定</el-button>
- </div>
- </el-dialog>
- <el-dialog title="选择教材" width="950px" :visible.sync="dialogSearchBook" :close-on-click-modal="false">
- <el-table :data="bookList">
- <el-table-column label="序号" width="60" align="center" header-align="center" class-name="index-column">
- <template slot-scope="{ $index }">
- {{ cur_page_begin_index + $index }}
- </template>
- </el-table-column>
- <el-table-column prop="sn" label="编号" width="120" header-align="center" />
- <el-table-column prop="name" label="名称" min-width="240" header-align="center" />
- <el-table-column prop="project_sn" label="项目编号" width="120" header-align="center" />
- <el-table-column prop="project_name" label="项目名称" width="240" header-align="center" />
- <el-table-column label="操作" width="80">
- <template #default="{ row }">
- <el-button type="primary" @click="selectBook(row)">选择</el-button>
- </template>
- </el-table-column>
- </el-table>
- <PaginationPage :total="bookTotal" @getList="pageBookList" />
- </el-dialog>
- <CommonProgress
- :percentage="percentage"
- :visible.sync="visible"
- title="导出离线包"
- :text="`正在导出离线包,请稍候...`"
- />
- </div>
- </template>
- <script>
- import ProjectMenu from '@/views/project_manage/common/ProjectMenu.vue';
- import PaginationPage from '@/components/PaginationPage.vue';
- import CommonProgress from '@/components/CommonProgress.vue';
- import { PageQueryYSJBookList_OrgManager } from '@/api/list';
- import {
- AddBookOfflinePackAuth,
- DeleteBookOfflinePackAuth,
- DisableBookOfflinePackAuth,
- PageQueryBookOfflinePackAuthList,
- CreateBookOfflinePackChapterStructFileList,
- CreateOfflinePackCoursewareFileList,
- } from '@/api/offline';
- import { isTrue } from '@/utils/validate';
- export default {
- name: 'OrgProjectManageOfflinePackAuth',
- components: {
- ProjectMenu,
- PaginationPage,
- CommonProgress,
- },
- data() {
- return {
- list: [],
- total: 0,
- cur_page_begin_index: 0,
- bookList: [],
- bookTotal: 0,
- bookPageIndex: 0,
- isTrue,
- dialogFormEdit: false,
- dialogSearchBook: false,
- queryForm: {
- book_name: '',
- auth_code: '',
- memo: '',
- page_capacity: 10,
- cur_page: 1,
- },
- editForm: {
- book_id: '',
- book_name: '',
- effective_count: null,
- effective_end_date: '',
- memo: '',
- },
- rules: {
- book_name: [{ required: true, message: '请选择教材', trigger: 'blur' }],
- effective_count: [
- { required: true, message: '请填写有效次数', trigger: 'blur' },
- { type: 'number', message: '有效次数必须为数字值' },
- ],
- effective_end_date: [{ required: true, message: '请选择有效截止日期', trigger: 'blur' }],
- },
- tempDir: '', // 临时目录,用于存放下载的文件
- savePath: '', // 保存离线包的路径
- packageName: 'EEP 离线包', // 离线包名称
- file_info_list: [], // 章节结构文件列表
- downloadTotal: 0, // 下载总数
- downloadCompleted: 0, // 已下载数量
- downloadWatcher: null, // 下载进度监听器
- visible: false,
- percentage: 0,
- };
- },
- computed: {
- downloadProgress() {
- if (this.downloadTotal === 0) return 0;
- return (this.downloadCompleted / this.downloadTotal) * 100;
- },
- },
- watch: {
- dialogFormEdit: {
- handler(val) {
- if (!val) {
- this.$refs.editForm.resetFields();
- }
- },
- 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;
- }
- },
- methods: {
- queryList() {
- this.pageList(this.queryForm);
- },
- pageList(data) {
- Object.assign(this.queryForm, data);
- PageQueryBookOfflinePackAuthList(this.queryForm).then(
- ({ total_count, offline_pack_auth_list, cur_page_begin_index }) => {
- this.list = offline_pack_auth_list;
- this.total = total_count;
- this.cur_page_begin_index = cur_page_begin_index;
- },
- );
- },
- pageBookList(data) {
- this.handleSearchBook(data);
- },
- handleSearchBook(data) {
- let page = data || {
- cur_page: 1,
- page_capacity: 10,
- };
- PageQueryYSJBookList_OrgManager(page).then(({ total_count, book_list, cur_page_begin_index }) => {
- this.bookList = book_list;
- this.bookTotal = total_count;
- this.bookPageIndex = cur_page_begin_index;
- this.dialogSearchBook = true;
- });
- },
- selectBook(book) {
- this.editForm.book_name = book.name;
- this.editForm.book_id = book.id;
- this.dialogSearchBook = false;
- },
- handleSave(formName) {
- this.$refs[formName].validate((valid) => {
- if (valid) {
- let data = this.editForm;
- this.dialogSearchBook = false;
- AddBookOfflinePackAuth(data).then((res) => {
- if (res && res.status === 1) {
- this.dialogFormEdit = false;
- this.queryList();
- }
- });
- } else {
- return false;
- }
- });
- },
- async handleDisable(id) {
- this.$confirm('确定要废弃此条数据吗?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- })
- .then(() => {
- DisableBookOfflinePackAuth({ id }).then(() => {
- this.queryList();
- });
- })
- .catch(() => {});
- },
- async handleDel(id) {
- this.$confirm('确定要删除此条数据吗?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- })
- .then(() => {
- DeleteBookOfflinePackAuth({ id }).then(() => {
- this.queryList();
- });
- })
- .catch(() => {});
- },
- /**
- * 选择保存离线包的目录
- * @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.visible = true;
- 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) {
- Promise.all(
- courseware_id_list.map((courseware_id) => this.CreateOfflinePackCoursewareFileList(courseware_id)),
- ).then(() => {
- this.downloadWatcher = this.$watch(
- 'downloadProgress',
- (newVal) => {
- if (newVal >= 100) {
- this.downloadWatcher();
- this.startCompress();
- } else {
- this.percentage = (
- (this.downloadCompleted / (this.downloadTotal + this.downloadTotal * 0.1)) *
- 100
- ).toFixed(2);
- }
- },
- { immediate: true },
- );
- });
- }
- },
- /**
- * 下载课件文件列表
- * @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;
- // 如果有子目录,则判断是否有,没有就创建子目录
- if (dir_name.split('/').length > 1) {
- if (!window.fileAPI.existsSync(`${this.tempDir}\\${dir_name}`)) {
- window.fileAPI.mkdirSync(`${this.tempDir}\\${dir_name}`);
- }
- }
- 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.tempDir = '';
- this.visible = false;
- return;
- } finally {
- this.percentage = 100;
- this.visible = false;
- }
- // 删除临时目录及其内容
- try {
- await window.fileAPI.deleteTempDir(this.tempDir);
- this.tempDir = '';
- } catch (e) {
- console.error('删除临时目录失败:', e);
- this.tempDir = '';
- } finally {
- this.percentage = 0;
- }
- 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>
- <style lang="scss" scoped>
- @use '@/styles/mixin.scss' as *;
- .book {
- @include page-base;
- @include table-list;
- }
- .el-divider--horizontal {
- margin: 10px 0 !important;
- }
- .el-form-item__content > * {
- width: 100%;
- }
- </style>
|