2 Commit-ok 165c860553 ... 4a7e487d1e

Szerző SHA1 Üzenet Dátum
  dsy 4a7e487d1e Merge branch 'master' of http://60.205.254.193:3000/GCLS/eep_page 1 hete
  dsy 91f3e46d16 问题修改 1 hete

+ 1 - 1
.env

@@ -11,4 +11,4 @@ VUE_APP_BookWebSI = '/GCLSBookWebSI/ServiceInterface'
 VUE_APP_EepServer = '/EEPServer/SI'
 
 #version
-VUE_APP_VERSION = '2025.12.26'
+VUE_APP_VERSION = '2025.11.21'

+ 34 - 2
main.js

@@ -258,17 +258,49 @@ ipcMain.handle('download-file', async (evt, { url, destPath }) => {
     const fileStream = fs.createWriteStream(destPath); // 创建写入流
     const req = net.request(url);
     req.on('response', (res) => {
+      const contentLength = Number(res.headers['content-length'] || '0');
+      let bytesReceived = 0;
+
       res.on('data', (chunk) => {
         fileStream.write(chunk);
+        bytesReceived += chunk.length;
+
+        // 在主进程计算百分比并发送
+        const percentage = contentLength > 0 ? Math.round((bytesReceived / contentLength) * 100) : 0;
+        evt.sender.send('download-progress', {
+          bytesReceived, // 已接收字节数
+          contentLength, // 总字节数
+          percentage, // 百分比
+        });
       });
+
       res.on('end', () => {
-        fileStream.end();
-        resolve(destPath);
+        fileStream.end(() => {
+          // 结束时确保发送 100%(若已知总长度)
+          const finalPercentage = contentLength > 0 ? 100 : 0;
+          evt.sender.send('download-progress', {
+            url,
+            bytesReceived,
+            contentLength,
+            percentage: finalPercentage,
+          });
+          resolve(destPath);
+        });
+      });
+
+      res.on('aborted', () => {
+        reject(new Error('response aborted'));
       });
     });
+
     req.on('error', (err) => {
       reject(err);
     });
+
+    fileStream.on('error', (err) => {
+      reject(err);
+    });
+
     req.end();
   });
 });

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "eep_page",
-  "version": "2025.12.26",
+  "version": "2025.12.27",
   "private": true,
   "main": "main.js",
   "description": "智慧梧桐数字教材编辑器",

+ 11 - 0
preload.js

@@ -83,6 +83,17 @@ contextBridge.exposeInMainWorld('fileAPI', {
   },
 
   /**
+   * 监听下载进度
+   * @param {Function} cb 回调,接收 { bytesReceived, contentLength, percentage } 作为参数
+   * @returns {Function} 用于移除监听器的函数
+   */
+  onDownloadProgress: (cb) => {
+    const handler = (event, data) => cb(data);
+    ipcRenderer.on('download-progress', handler);
+    return () => ipcRenderer.removeListener('download-progress', handler);
+  },
+
+  /**
    * 打开文件对话框
    * @param {Object} opts 对话框选项
    * @returns {Promise} 文件路径

+ 46 - 25
src/App.vue

@@ -2,11 +2,18 @@
   <div id="app">
     <RouterView />
     <GlobalProgress />
+    <CommonProgress
+      :visible.sync="visible"
+      title="下载更新包"
+      :percentage="percentage"
+      text="正在下载更新包,请稍候..."
+    />
   </div>
 </template>
 
 <script>
 import GlobalProgress from '@/components/GlobalProgress.vue';
+import CommonProgress from '@/components/CommonProgress.vue';
 
 import { GetSysVersionInfo } from '@/api/app';
 
@@ -14,35 +21,45 @@ export default {
   name: 'App',
   components: {
     GlobalProgress,
+    CommonProgress,
   },
   data() {
     return {
       savePath: '',
       client_download_url: '',
       version: '',
+      visible: false,
+      percentage: 0,
     };
   },
   mounted() {
-    GetSysVersionInfo().then(({ version, client_download_url }) => {
-      let curVersion = process.env.VUE_APP_VERSION.replace(/\./g, '');
-
-      if (Number(curVersion) < version) {
-        this.$confirm('新版本可用。是否下载最新版本?', '更新提示', {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
-          type: 'primary',
-        })
-          .then(() => {
-            this.version = version;
-            this.client_download_url = client_download_url;
-            this.selectDirectory();
-          })
-          .catch(() => {});
-      }
-    });
+    const isDev = process.env.NODE_ENV === 'development';
+    if (isDev) return;
+    this.getSysVersionInfo();
   },
   methods: {
     /**
+     * 获取系统版本信息,并检查是否有新版本可用
+     */
+    getSysVersionInfo() {
+      GetSysVersionInfo().then(({ version, client_download_url }) => {
+        let curVersion = Number(process.env.VUE_APP_VERSION.replace(/\./g, ''));
+        if (curVersion < version) {
+          this.$confirm('新版本可用。是否下载最新版本?', '更新提示', {
+            confirmButtonText: '确定',
+            cancelButtonText: '取消',
+            type: 'primary',
+          })
+            .then(() => {
+              this.version = version;
+              this.client_download_url = client_download_url;
+              this.selectDirectory();
+            })
+            .catch(() => {});
+        }
+      });
+    },
+    /**
      * 选择保存更新包的目录
      */
     async selectDirectory() {
@@ -56,16 +73,20 @@ export default {
       }
       this.savePath = result.filePaths[0];
       const downloadDir = `${this.savePath}\\智慧梧桐数字教材编辑器_${this.version}.exe`;
-      this.loadingInstance = this.$loading({
-        lock: true,
-        text: '正在下载更新包,请稍候...',
-        spinner: 'el-icon-loading',
-        background: 'rgba(255, 255, 255, 0.7)',
+      this.visible = true;
+      const unsubscribe = window.fileAPI.onDownloadProgress(({ percentage }) => {
+        this.percentage = percentage;
       });
-      await window.fileAPI.downloadFile(this.client_download_url, downloadDir);
-      this.loadingInstance.text = '下载完成,正在安装更新...';
+      try {
+        await window.fileAPI.downloadFile(this.client_download_url, downloadDir);
+      } catch (err) {
+        this.$message.error(`下载失败:${err && err.message}`);
+      } finally {
+        unsubscribe();
+        this.visible = false;
+      }
       await window.updateAPI.installUpdate(downloadDir); // 调用安装更新的API
-      this.loadingInstance.close();
+      this.percentage = 0;
     },
   },
 };

+ 2 - 2
src/common/data.js

@@ -1,6 +1,6 @@
 export const unified_attrib = {
-  topic_color: '#FFBBCC', // 主题色
-  assist_color: '#FFBBEE', // 辅助色
+  topic_color: '#F47921', // 主题色
+  assist_color: '#FEE7D4', // 辅助色
   font: '宋体,微软雅黑', // 字体
   font_size: '12pt', // 字号
   pinyin_size: '12pt', // 拼音字号

+ 53 - 0
src/components/CommonProgress.vue

@@ -0,0 +1,53 @@
+<template>
+  <el-dialog
+    :visible="visible"
+    :title="title"
+    :close-on-click-modal="false"
+    :before-close="dialogClose"
+    width="330px"
+    custom-class="common-progress-dialog"
+  >
+    <h3>{{ text }}</h3>
+    <el-progress :text-inside="true" :stroke-width="26" :percentage="percentage" text-color="#fff" />
+  </el-dialog>
+</template>
+
+<script>
+export default {
+  name: 'CommonProgress',
+  props: {
+    visible: {
+      type: Boolean,
+      required: true,
+    },
+    title: {
+      type: String,
+      default: '进度',
+    },
+    percentage: {
+      type: Number,
+      default: 0,
+    },
+    text: {
+      type: String,
+      default: '正在进行中,请稍候...',
+    },
+  },
+  data() {
+    return {};
+  },
+  methods: {
+    dialogClose() {
+      this.$emit('update:visible', false);
+    },
+  },
+};
+</script>
+
+<style lang="scss">
+.el-dialog.common-progress-dialog {
+  .el-dialog__body {
+    padding-top: 0;
+  }
+}
+</style>

+ 3 - 3
src/components/RichText.vue

@@ -702,7 +702,7 @@ export default {
       return str.replace(/<span\b[^>]*>(.*?)<\/span>/gi, '$1');
     },
     createParsedTextInfoPinyin(content) {
-      var styles = this.getFirstCharStyles();
+      let styles = this.getFirstCharStyles();
       let text = content.replace(/<[^>]+>/g, '');
       this.$emit('createParsedTextInfoPinyin', text, styles);
     },
@@ -817,7 +817,7 @@ export default {
         if (eleMathArs.length === 0) return;
         await this.$nextTick();
         window.MathJax.typesetPromise(eleMathArs).catch((err) =>
-          /* eslint-disable */ console.error(...oo_tx(`483836707_818_65_818_101_11`, 'MathJax error:', err))
+          /* eslint-disable */ console.error(...oo_tx(`483836707_818_65_818_101_11`, 'MathJax error:', err)),
         );
         this.mathEleIsInit = true;
       }
@@ -968,7 +968,7 @@ export default {
         {
           acceptNode: (node) => (node.textContent.trim() ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT),
         },
-        false
+        false,
       );
       return walker.nextNode();
     },

+ 5 - 4
src/views/book/courseware/create/components/base/rich_text/RichText.vue

@@ -124,7 +124,8 @@ export default {
         return;
       }
       data.text = text.replace(/<[^>]+>/g, '');
-      data.is_first_sentence_first_hz_pinyin_first_char_upper_case = this.data.property.is_first_sentence_first_hz_pinyin_first_char_upper_case;
+      data.is_first_sentence_first_hz_pinyin_first_char_upper_case =
+        this.data.property.is_first_sentence_first_hz_pinyin_first_char_upper_case;
       CrateParsedTextInfo_Pinyin(data).then(({ parsed_text }) => {
         if (parsed_text) {
           const mergedData = parsed_text.paragraph_list.map((outerArr, i) =>
@@ -137,7 +138,7 @@ export default {
                   originalItem.hash === this.hashText(newItem.text) &&
                   originalItem.id === `p${i}-l${j}-i${k}`;
                 if (!newItem.pinyin) newItem.pinyin = newItem.text;
-                var tmp = {
+                let tmp = {
                   ...newItem,
                   id: `p${i}-l${j}-i${k}`,
                   hash: this.hashText(newItem.text),
@@ -148,8 +149,8 @@ export default {
                   Object.assign(tmp.activeTextStyle, styles);
                 }
                 return tmp;
-              })
-            )
+              }),
+            ),
           );
           this.data.paragraph_list = mergedData; // 取出合并后的数组
         }

+ 8 - 6
src/views/book/courseware/create/components/question/judge/Judge.vue

@@ -3,7 +3,7 @@
     <template #content>
       <ul class="option-list">
         <li v-for="(item, i) in data.option_list" :key="i" class="option-item">
-          <span class="serial-number">{{ convertNumberToLetter(i) }}.</span>
+          <span class="serial-number">{{ computedOptionNumber(i) }}.</span>
           <div class="option-content">
             <RichText
               ref="richText"
@@ -55,11 +55,12 @@
 </template>
 
 <script>
-import { getJudgeData, getOption, option_type_list, isEnable } from '@/views/book/courseware/data/judge';
-
 import ModuleMixin from '../../common/ModuleMixin';
 import PinyinText from '@/components/PinyinText.vue';
 
+import { getJudgeData, getOption, option_type_list, isEnable } from '@/views/book/courseware/data/judge';
+import { serialNumberTypeList, computeOptionMethods } from '@/views/book/courseware/data/common';
+
 export default {
   name: 'JudgePage',
   components: {
@@ -109,9 +110,10 @@ export default {
     },
   },
   methods: {
-    // 将数字转换为小写字母
-    convertNumberToLetter(number) {
-      return String.fromCharCode(97 + number);
+    computedOptionNumber(number) {
+      let type = serialNumberTypeList.find((item) => item.value === this.data.property.option_serial_type)?.value;
+      if (!type) return number + 1;
+      return computeOptionMethods[type](number);
     },
     /**
      * 选择选项答案

+ 42 - 1
src/views/book/courseware/create/components/question/judge/JudgeSetting.vue

@@ -2,6 +2,12 @@
   <div>
     <el-form :model="property" label-width="72px" label-position="left">
       <SerailNumber :property="property" />
+
+      <el-form-item label="选项序号" class="serial-number">
+        <el-input v-model="optionSerialType" disabled />
+        <SvgIcon icon-class="switch" height="16" width="16" @click="switchSNType" />
+      </el-form-item>
+
       <el-form-item label="不确定">
         <el-radio-group v-model="property.is_view_incertitude">
           <el-radio v-for="{ value, label } in switchOption" :key="value" :label="value" :value="value">
@@ -41,6 +47,7 @@
 import SettingMixin from '@/views/book/courseware/create/components/common/SettingMixin';
 
 import { getJudgeProperty, switchOption } from '@/views/book/courseware/data/judge';
+import { serialNumberTypeList } from '@/views/book/courseware/data/common';
 
 export default {
   name: 'JudgeSetting',
@@ -51,7 +58,41 @@ export default {
       switchOption,
     };
   },
-  methods: {},
+  computed: {
+    optionSerialType() {
+      let type = serialNumberTypeList.find((item) => item.value === this.property.option_serial_type)?.value;
+
+      let val = '1';
+      switch (type) {
+        case serialNumberTypeList[0].value:
+          val = '1';
+          break;
+        case serialNumberTypeList[1].value:
+          val = '(1)';
+          break;
+        case serialNumberTypeList[2].value:
+          val = 'a';
+          break;
+        case serialNumberTypeList[3].value:
+          val = 'A';
+          break;
+        default:
+          val = '1';
+          break;
+      }
+      return val;
+    },
+  },
+  methods: {
+    /**
+     * @description 切换选项序号类型
+     */
+    switchSNType() {
+      const currentIndex = serialNumberTypeList.findIndex((item) => item.value === this.property.option_serial_type);
+      const nextIndex = (currentIndex + 1) % serialNumberTypeList.length;
+      this.updateProperty('option_serial_type', serialNumberTypeList[nextIndex].value);
+    },
+  },
 };
 </script>
 

+ 1 - 0
src/views/book/courseware/data/judge.js

@@ -25,6 +25,7 @@ export function getJudgeProperty() {
     sn_display_mode: displayList[1].value,
     option_type_list: [option_type_list[0].value, option_type_list[1].value, option_type_list[2].value],
     is_view_incertitude: switchOption[1].value,
+    option_serial_type: serialNumberTypeList[2].value,
     view_pinyin: 'false', // 显示拼音
     pinyin_position: pinyinPositionList[0].value,
     is_first_sentence_first_hz_pinyin_first_char_upper_case: displayList[0].value, // 句首大写

+ 8 - 5
src/views/book/courseware/preview/components/judge/JudgePreview.vue

@@ -14,7 +14,7 @@
             :style="{ borderColor: data.unified_attrib?.topic_color }"
             :class="['option-content', computedIsJudgeRight(mark)]"
           >
-            <span class="serial-number">{{ convertNumberToLetter(i) }}.</span>
+            <span class="serial-number">{{ computedOptionNumber(i) }}.</span>
             <PinyinText
               v-if="isEnable(data.property.view_pinyin)"
               class="content"
@@ -60,9 +60,11 @@
 </template>
 
 <script>
-import { getJudgeData, option_type_list, isEnable } from '@/views/book/courseware/data/judge';
 import PreviewMixin from '../common/PreviewMixin';
 
+import { getJudgeData, option_type_list, isEnable } from '@/views/book/courseware/data/judge';
+import { serialNumberTypeList, computeOptionMethods } from '@/views/book/courseware/data/common';
+
 export default {
   name: 'JudgePreview',
   mixins: [PreviewMixin],
@@ -104,9 +106,10 @@ export default {
     },
   },
   methods: {
-    // 将数字转换为小写字母
-    convertNumberToLetter(number) {
-      return String.fromCharCode(97 + number);
+    computedOptionNumber(number) {
+      let type = serialNumberTypeList.find((item) => item.value === this.data.property.option_serial_type)?.value;
+      if (!type) return number + 1;
+      return computeOptionMethods[type](number);
     },
 
     isAnswer(mark, option_type) {

+ 1 - 1
src/views/personal_workbench/edit_task/edit/UseTemplate.vue

@@ -115,7 +115,7 @@ export default {
         },
         // 组件列表
         row_list: [],
-        // 教材设置
+        // 教材样式设置
         unified_attrib,
       },
       data_content_group_row_list: [],

+ 2 - 2
src/views/personal_workbench/edit_task/preview/index.vue

@@ -50,9 +50,9 @@ export default {
      * 提交课件到审校流程
      */
     submitCoursewareToAuditFlow() {
-      SubmitBookCoursewareToAuditFlow({ id: this.id }).then(() => {
+      SubmitBookCoursewareToAuditFlow({ id: this.$refs.preview.select_node }).then(() => {
         this.$message.success('课件已提交审校');
-        this.$refs.preview.getBookCoursewareInfo(this.id);
+        this.$refs.preview.getBookCoursewareInfo(this.$refs.preview.select_node);
       });
     },
     /**

+ 1 - 1
src/views/personal_workbench/project/ProductionEditorialManage.vue

@@ -14,7 +14,7 @@
         </div>
         <span class="link" @click="visibleAuditSteps = true">设置审校步骤</span>
         <div class="operator flex">
-          <span class="link" @click="openBookUnifiedAttrib">教材设置</span>
+          <span class="link" @click="openBookUnifiedAttrib">教材样式设置</span>
           <span class="link" @click="addChapterDialog">添加章节节点</span>
           <span class="link" @click="addCoursewareDialog">添加教材内容节点</span>
           <span class="link" @click="$router.push({ path: `/personal_workbench/project` })">返回项目列表</span>

+ 1 - 1
src/views/personal_workbench/project/components/BookUnifiedAttr.vue

@@ -1,6 +1,6 @@
 <template>
   <el-dialog
-    title="教材设置"
+    title="教材样式设置"
     :visible="visible"
     width="600px"
     :close-on-click-modal="false"

+ 14 - 1
src/views/personal_workbench/project/index.vue

@@ -20,7 +20,7 @@
             <span class="link" @click="projectInfoManage(row.id, true)">项目信息管理</span>
             <span class="link" @click="productionResourceManage(row.id)">项目资源管理</span>
             <span class="link" @click="productionEditorialManage(row.id)">制作与审校管理</span>
-            <span class="link danger">删除</span>
+            <span class="link danger" @click="deleteProject(row.id)">删除</span>
           </template>
         </el-table-column>
       </el-table>
@@ -79,6 +79,19 @@ export default {
     productionResourceManage(id) {
       this.$router.push({ path: `/personal_workbench/production_resource/${id}` });
     },
+    /**
+     * 删除我的项目
+     * @param {string} id - 项目ID
+     */
+    deleteProject(id) {
+      this.$confirm('确定删除该项目吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+        .then(() => {})
+        .catch(() => {});
+    },
   },
 };
 </script>

+ 22 - 22
src/views/project_manage/org/offlinepackauth/index.vue

@@ -113,12 +113,20 @@
       </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 {
@@ -136,6 +144,7 @@ export default {
   components: {
     ProjectMenu,
     PaginationPage,
+    CommonProgress,
   },
   data() {
     return {
@@ -170,7 +179,6 @@ export default {
         ],
         effective_end_date: [{ required: true, message: '请选择有效截止日期', trigger: 'blur' }],
       },
-      loadingInstance: null, // 加载中实例
       tempDir: '', // 临时目录,用于存放下载的文件
       savePath: '', // 保存离线包的路径
       packageName: 'EEP 离线包', // 离线包名称
@@ -178,6 +186,8 @@ export default {
       downloadTotal: 0, // 下载总数
       downloadCompleted: 0, // 已下载数量
       downloadWatcher: null, // 下载进度监听器
+      visible: false,
+      percentage: 0,
     };
   },
   computed: {
@@ -208,12 +218,6 @@ export default {
       this.downloadWatcher();
       this.downloadWatcher = null;
     }
-
-    // 关闭加载中实例
-    if (this.loadingInstance) {
-      this.loadingInstance.close();
-      this.loadingInstance = null;
-    }
   },
   methods: {
     queryList() {
@@ -330,12 +334,7 @@ export default {
         this.tempDir = window.fileAPI.createTempDir(); // 创建临时保存目录
       }
 
-      this.loadingInstance = this.$loading({
-        lock: true,
-        text: '正在下载离线包,请稍候...',
-        spinner: 'el-icon-loading',
-        background: 'rgba(255, 255, 255, 0.7)',
-      });
+      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;
@@ -359,8 +358,10 @@ export default {
                 this.downloadWatcher();
                 this.startCompress();
               } else {
-                // 显示下载进度,预留 10% 的压缩进度
-                this.loadingInstance.text = `正在下载离线包,进度:${((this.downloadCompleted / (this.downloadTotal + this.downloadTotal * 0.1)) * 100).toFixed(2)}%`;
+                this.percentage = (
+                  (this.downloadCompleted / (this.downloadTotal + this.downloadTotal * 0.1)) *
+                  100
+                ).toFixed(2);
               }
             },
             { immediate: true },
@@ -421,14 +422,14 @@ export default {
       } catch (e) {
         console.error('压缩失败:', e);
         this.$message.error('离线包下载失败,请重试!');
-        this.loadingInstance.close();
-        this.loadingInstance = null;
         this.tempDir = '';
+        this.visible = false;
         return;
+      } finally {
+        this.percentage = 100;
+        this.visible = false;
       }
 
-      this.loadingInstance.text = `正在下载离线包,进度:100%`;
-
       // 删除临时目录及其内容
       try {
         await window.fileAPI.deleteTempDir(this.tempDir);
@@ -436,11 +437,10 @@ export default {
       } catch (e) {
         console.error('删除临时目录失败:', e);
         this.tempDir = '';
+      } finally {
+        this.percentage = 0;
       }
 
-      this.loadingInstance.close();
-      this.loadingInstance = null;
-
       this.$message.success('离线包下载并保存成功!');
     },