Explorar el Código

离线包激活处理

dsy hace 9 meses
padre
commit
5192d0b9cc
Se han modificado 9 ficheros con 777 adiciones y 40 borrados
  1. 229 22
      main.js
  2. 408 11
      package-lock.json
  3. 2 1
      package.json
  4. 23 0
      preload.js
  5. 15 0
      src/api/app.js
  6. 0 1
      src/api/list.js
  7. 97 2
      src/components/CommonPreview.vue
  8. 2 2
      src/utils/http.js
  9. 1 1
      src/views/eep_view/index.vue

+ 229 - 22
main.js

@@ -7,6 +7,27 @@ const sevenBin = require('7zip-bin');
 const AdmZip = require('adm-zip');
 const os = require('os');
 
+let store = null;
+let storeReady = (async () => {
+  try {
+    const mod = await import('electron-store');
+    const StoreLib = mod && (mod.default || mod);
+    if (!StoreLib) throw new Error('Failed to load electron-store');
+    store = new StoreLib();
+    return store;
+  } catch (err) {
+    console.error('Failed to dynamically import electron-store:', err);
+    return null;
+  }
+})();
+
+let iconv = null; // 用来在 Windows 上正确解码 7za 使用系统编码输出的中文信息
+try {
+  iconv = require('iconv-lite');
+} catch (e) {
+  iconv = null;
+}
+
 let win = null;
 let pendingEep = null; // 如果在窗口未创建前收到文件,先缓存
 
@@ -25,28 +46,6 @@ const createWindow = () => {
     },
   });
 
-  // 全局拦截会话级别的网络请求,禁止所有外部网络访问(离线包专用)
-  try {
-    const ses = win.webContents.session;
-    // 拦截所有协议的请求,允许本地资源协议通过
-    ses.webRequest.onBeforeRequest({ urls: ['*://*/*'] }, (details, callback) => {
-      const u = details && details.url ? String(details.url) : '';
-      // 允许本地文件和 data/ about 等内部资源
-      if (
-        u.startsWith('file:') ||
-        u.startsWith('data:') ||
-        u.startsWith('about:') ||
-        u.startsWith('chrome-devtools://')
-      ) {
-        return callback({ cancel: false });
-      }
-      // 其它所有网络请求全部取消 (http/https/ftp/...)。
-      return callback({ cancel: true });
-    });
-  } catch (e) {
-    console.error('Failed to set webRequest blocker:', e);
-  }
-
   win.loadURL(
     url.format({
       pathname: path.join(__dirname, './dist', 'index.html'),
@@ -104,6 +103,93 @@ app.whenReady().then(() => {
 });
 
 /**
+ * 获取应用数据
+ * @param {string} key 键
+ * @returns {any} 值
+ */
+ipcMain.handle('store:get', async (event, key) => {
+  await storeReady;
+  if (!store) throw new Error('electron-store not initialized');
+  return store.get(key);
+});
+
+/**
+ * 设置应用数据
+ * @param {string} key 键
+ * @param {any} value 值
+ */
+ipcMain.handle('store:set', async (event, key, value) => {
+  await storeReady;
+  if (!store) throw new Error('electron-store not initialized');
+  store.set(key, value);
+});
+
+/**
+ * 删除应用数据
+ * @param {string} key 键
+ */
+ipcMain.handle('store:delete', async (event, key) => {
+  await storeReady;
+  if (!store) throw new Error('electron-store not initialized');
+  store.delete(key);
+});
+
+/**
+ * 得到 7za 可执行路径,优先使用 seven-bin 提供的路径;若被打包进 app.asar,则尝试 app.asar.unpacked 路径
+ */
+function get7zaPath() {
+  let sevenPath = sevenBin.path7za;
+  // 如果 sevenPath 在 app.asar 内,映射到 app.asar.unpacked 的对应相对路径
+  const asarToken = `${path.sep}app.asar${path.sep}`;
+  if (typeof sevenPath === 'string' && sevenPath.indexOf(asarToken) !== -1) {
+    const rel = sevenPath.split(asarToken)[1]; // 例如: node_modules/7zip-bin/win/x64/7za.exe
+    const unpackedCandidate = path.join(process.resourcesPath, 'app.asar.unpacked', rel);
+    if (fs.existsSync(unpackedCandidate)) {
+      sevenPath = unpackedCandidate;
+    }
+  }
+
+  // 若上面没有找到,尝试常见的 unpacked 路径(兼容不同包结构与架构)
+  if (!fs.existsSync(sevenPath)) {
+    const candidates = [
+      path.join(
+        process.resourcesPath,
+        'app.asar.unpacked',
+        'node_modules',
+        '7zip-bin',
+        'win',
+        'x64',
+        path.basename(sevenPath),
+      ),
+      path.join(
+        process.resourcesPath,
+        'app.asar.unpacked',
+        'node_modules',
+        '7zip-bin',
+        'win',
+        'x86',
+        path.basename(sevenPath),
+      ),
+      path.join(
+        process.resourcesPath,
+        'app.asar.unpacked',
+        'node_modules',
+        '7zip-bin',
+        'bin',
+        path.basename(sevenPath),
+      ),
+    ];
+    for (const c of candidates) {
+      if (fs.existsSync(c)) {
+        sevenPath = c;
+        break;
+      }
+    }
+  }
+  return sevenPath;
+}
+
+/**
  * 使用 7z 解压(支持 -p 密码),返回临时目录与条目列表
  * @param {string} filePath EEP 文件路径
  * @param {string} password 密码
@@ -151,6 +237,127 @@ ipcMain.handle('extract-zip', async (event, { filePath, password }) => {
 });
 
 /**
+ * 使用 7za 更新 zip 内的单个条目(替换或添加)
+ * opts: { archivePath, entryPath, content, encoding?, password? }
+ * - archivePath: 本地 zip 路径
+ * - entryPath: zip 内相对路径,例如 'dir/file.txt'(使用 / 分隔)
+ * - content: Buffer 或 string(若为 string 则使用 encoding 解码,默认 utf8)
+ * - password: 可选,提供则传给 7z 的 -p 参数
+ */
+ipcMain.handle('zip:update-entry-with-7z', async (evt, opts) => {
+  const sender = evt.sender;
+  if (!opts || !opts.archivePath || !opts.entryPath || !opts.content) {
+    throw new Error('missing required opts: archivePath, entryPath, content');
+  }
+
+  const archivePath = path.resolve(opts.archivePath);
+  const entryPath = opts.entryPath.replace(/\\/g, '/'); // 统一使用 / 作为 zip 内路径分隔符
+
+  if (!fs.existsSync(archivePath)) {
+    throw new Error(`archive not found: ${archivePath}`);
+  }
+
+  // 准备内容 Buffer
+  let contentBuf = null;
+  if (Buffer.isBuffer(opts.content)) {
+    contentBuf = opts.content;
+  } else if (typeof opts.content === 'string') {
+    contentBuf = Buffer.from(opts.content, opts.encoding || 'utf8');
+  } else if (opts.content && opts.content.data) {
+    // 处理类似 Uint8Array 之类的对象
+    contentBuf = Buffer.from(opts.content);
+  } else {
+    throw new Error('unsupported content type');
+  }
+
+  let sevenPath = sevenBin.path7za;
+  try {
+    sevenPath = get7zaPath();
+  } catch (err) {
+    console.error('Error resolving 7za path:', err);
+  }
+
+  if (!fs.existsSync(sevenPath)) throw new Error(`7za executable not found: ${sevenPath}`);
+
+  // 准备临时文件夹,将内容写入对应路径以供 7z 使用
+  const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), 'zipupdate-'));
+  const entryParts = entryPath.split('/').filter(Boolean);
+  const targetLocal = path.join(tmpBase, ...entryParts);
+  try {
+    fs.mkdirSync(path.dirname(targetLocal), { recursive: true });
+    fs.writeFileSync(targetLocal, contentBuf);
+  } catch (e) {
+    // 清理临时目录
+    try {
+      fs.rmSync(tmpBase, { recursive: true, force: true });
+    } catch (e2) {}
+    throw e;
+  }
+
+  // 准备 7z 命令参数
+  const args = ['u', archivePath, entryPath, '-y'];
+  if (opts.password) args.splice(2, 0, `-p${opts.password}`);
+
+  return await new Promise((resolve, reject) => {
+    let stdoutAll = '';
+    let stderrAll = '';
+    const child = spawn(sevenPath, args, { cwd: tmpBase, windowsHide: true });
+
+    child.stdout.on('data', (chunk) => {
+      const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
+      let s = '';
+      if (process.platform === 'win32' && iconv) {
+        try {
+          s = iconv.decode(buf, 'cp936');
+        } catch (e) {
+          s = buf.toString('utf8');
+        }
+      } else s = buf.toString('utf8');
+      stdoutAll += s;
+      sender.send('zip-update-stdout', s);
+    });
+    child.stderr.on('data', (chunk) => {
+      const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
+      let s = '';
+      if (process.platform === 'win32' && iconv) {
+        try {
+          s = iconv.decode(buf, 'cp936');
+        } catch (e) {
+          s = buf.toString('utf8');
+        }
+      } else s = buf.toString('utf8');
+      stderrAll += s;
+      sender.send('zip-update-stderr', s);
+    });
+
+    child.on('error', (err) => {
+      try {
+        fs.rmSync(tmpBase, { recursive: true, force: true });
+      } catch (e) {}
+      reject(err);
+    });
+
+    child.on('close', (code) => {
+      // 清理临时目录
+      try {
+        fs.rmSync(tmpBase, { recursive: true, force: true });
+      } catch (e) {}
+      if (code === 0) {
+        resolve({ success: true, archivePath, entryPath, stdout: stdoutAll, stderr: stderrAll });
+      } else {
+        const errMsg = `7z exited with code ${code} \ncommand: ${JSON.stringify({ sevenPath, args })}\nstdout:\n${stdoutAll}\nstderr:\n${stderrAll}`;
+        const e = new Error(errMsg);
+        e.code = code;
+        e.stdout = stdoutAll;
+        e.stderr = stderrAll;
+        e.cmd = { sevenPath, args };
+        reject(e);
+      }
+    });
+  });
+});
+
+/**
  * 打开文件对话框
  * @param {Object} options 对话框选项
  * @param {string} [options.title] 对话框标题

+ 408 - 11
package-lock.json

@@ -1,12 +1,12 @@
 {
-  "name": "eep_page",
-  "version": "2025.11.17",
+  "name": "eep_page_reader",
+  "version": "2025.11.28",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
-      "name": "eep_page",
-      "version": "2025.11.17",
+      "name": "eep_page_reader",
+      "version": "2025.11.28",
       "hasInstallScript": true,
       "dependencies": {
         "@tinymce/tinymce-vue": "^3.2.8",
@@ -16,6 +16,7 @@
         "cnchar": "^3.2.6",
         "core-js": "^3.37.1",
         "dompurify": "^3.1.5",
+        "electron-store": "^11.0.2",
         "element-ui": "^2.15.14",
         "hanzi-writer": "^3.7.0",
         "jquery": "^3.7.1",
@@ -5417,6 +5418,16 @@
         "node": ">= 4.5.0"
       }
     },
+    "node_modules/atomically": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.0.tgz",
+      "integrity": "sha512-+gDffFXRW6sl/HCwbta7zK4uNqbPjv4YJEAdz7Vu+FLQHe77eZ4bvbJGi4hE0QPeJlMYMA3piXEr1UL3dAwx7Q==",
+      "license": "MIT",
+      "dependencies": {
+        "stubborn-fs": "^2.0.0",
+        "when-exit": "^2.1.4"
+      }
+    },
     "node_modules/autoprefixer": {
       "version": "10.4.19",
       "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.19.tgz",
@@ -6831,6 +6842,92 @@
       "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
       "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
     },
+    "node_modules/conf": {
+      "version": "15.0.2",
+      "resolved": "https://registry.npmjs.org/conf/-/conf-15.0.2.tgz",
+      "integrity": "sha512-JBSrutapCafTrddF9dH3lc7+T2tBycGF4uPkI4Js+g4vLLEhG6RZcFi3aJd5zntdf5tQxAejJt8dihkoQ/eSJw==",
+      "license": "MIT",
+      "dependencies": {
+        "ajv": "^8.17.1",
+        "ajv-formats": "^3.0.1",
+        "atomically": "^2.0.3",
+        "debounce-fn": "^6.0.0",
+        "dot-prop": "^10.0.0",
+        "env-paths": "^3.0.0",
+        "json-schema-typed": "^8.0.1",
+        "semver": "^7.7.2",
+        "uint8array-extras": "^1.5.0"
+      },
+      "engines": {
+        "node": ">=20"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/conf/node_modules/ajv": {
+      "version": "8.17.1",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
+      "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
+      "license": "MIT",
+      "dependencies": {
+        "fast-deep-equal": "^3.1.3",
+        "fast-uri": "^3.0.1",
+        "json-schema-traverse": "^1.0.0",
+        "require-from-string": "^2.0.2"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/epoberezkin"
+      }
+    },
+    "node_modules/conf/node_modules/ajv-formats": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
+      "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ajv": "^8.0.0"
+      },
+      "peerDependencies": {
+        "ajv": "^8.0.0"
+      },
+      "peerDependenciesMeta": {
+        "ajv": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/conf/node_modules/env-paths": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
+      "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
+      "license": "MIT",
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/conf/node_modules/json-schema-traverse": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+      "license": "MIT"
+    },
+    "node_modules/conf/node_modules/semver": {
+      "version": "7.7.3",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+      "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/config-file-ts": {
       "version": "0.2.6",
       "resolved": "https://registry.npmmirror.com/config-file-ts/-/config-file-ts-0.2.6.tgz",
@@ -7499,6 +7596,21 @@
       "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==",
       "dev": true
     },
+    "node_modules/debounce-fn": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-6.0.0.tgz",
+      "integrity": "sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==",
+      "license": "MIT",
+      "dependencies": {
+        "mimic-function": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/debug": {
       "version": "4.3.4",
       "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz",
@@ -8100,6 +8212,36 @@
         "tslib": "^2.0.3"
       }
     },
+    "node_modules/dot-prop": {
+      "version": "10.1.0",
+      "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-10.1.0.tgz",
+      "integrity": "sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q==",
+      "license": "MIT",
+      "dependencies": {
+        "type-fest": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=20"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/dot-prop/node_modules/type-fest": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.3.1.tgz",
+      "integrity": "sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==",
+      "license": "(MIT OR CC0-1.0)",
+      "dependencies": {
+        "tagged-tag": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=20"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/dotenv": {
       "version": "10.0.0",
       "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-10.0.0.tgz",
@@ -8464,6 +8606,37 @@
         "node": ">=8"
       }
     },
+    "node_modules/electron-store": {
+      "version": "11.0.2",
+      "resolved": "https://registry.npmjs.org/electron-store/-/electron-store-11.0.2.tgz",
+      "integrity": "sha512-4VkNRdN+BImL2KcCi41WvAYbh6zLX5AUTi4so68yPqiItjbgTjqpEnGAqasgnG+lB6GuAyUltKwVopp6Uv+gwQ==",
+      "license": "MIT",
+      "dependencies": {
+        "conf": "^15.0.2",
+        "type-fest": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=20"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/electron-store/node_modules/type-fest": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.3.1.tgz",
+      "integrity": "sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==",
+      "license": "(MIT OR CC0-1.0)",
+      "dependencies": {
+        "tagged-tag": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=20"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/electron-to-chromium": {
       "version": "1.4.736",
       "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.736.tgz",
@@ -9649,8 +9822,7 @@
     "node_modules/fast-deep-equal": {
       "version": "3.1.3",
       "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
-      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
-      "dev": true
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
     },
     "node_modules/fast-diff": {
       "version": "1.3.0",
@@ -9685,6 +9857,22 @@
       "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
       "dev": true
     },
+    "node_modules/fast-uri": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
+      "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/fastify"
+        },
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/fastify"
+        }
+      ],
+      "license": "BSD-3-Clause"
+    },
     "node_modules/fastest-levenshtein": {
       "version": "1.0.16",
       "resolved": "https://registry.npmmirror.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz",
@@ -11972,6 +12160,12 @@
       "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
       "dev": true
     },
+    "node_modules/json-schema-typed": {
+      "version": "8.0.2",
+      "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
+      "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
+      "license": "BSD-2-Clause"
+    },
     "node_modules/json-stable-stringify": {
       "version": "1.1.1",
       "resolved": "https://registry.npmmirror.com/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz",
@@ -13511,6 +13705,18 @@
         "node": ">=6"
       }
     },
+    "node_modules/mimic-function": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
+      "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/mimic-response": {
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-1.0.1.tgz",
@@ -16561,7 +16767,6 @@
       "version": "2.0.2",
       "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz",
       "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
-      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -17949,6 +18154,21 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/stubborn-fs": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz",
+      "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==",
+      "license": "MIT",
+      "dependencies": {
+        "stubborn-utils": "^1.0.1"
+      }
+    },
+    "node_modules/stubborn-utils": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz",
+      "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==",
+      "license": "MIT"
+    },
     "node_modules/style-search": {
       "version": "0.1.0",
       "resolved": "https://registry.npmmirror.com/style-search/-/style-search-0.1.0.tgz",
@@ -19085,6 +19305,18 @@
         "url": "https://github.com/chalk/slice-ansi?sponsor=1"
       }
     },
+    "node_modules/tagged-tag": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz",
+      "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=20"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/tapable": {
       "version": "2.2.1",
       "resolved": "https://registry.npmmirror.com/tapable/-/tapable-2.2.1.tgz",
@@ -19736,6 +19968,18 @@
         "node": ">=14.17"
       }
     },
+    "node_modules/uint8array-extras": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz",
+      "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/unbox-primitive": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
@@ -20806,6 +21050,12 @@
         "webidl-conversions": "^3.0.0"
       }
     },
+    "node_modules/when-exit": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz",
+      "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==",
+      "license": "MIT"
+    },
     "node_modules/which": {
       "version": "1.3.1",
       "resolved": "https://registry.npmmirror.com/which/-/which-1.3.1.tgz",
@@ -25249,6 +25499,15 @@
       "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
       "dev": true
     },
+    "atomically": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.0.tgz",
+      "integrity": "sha512-+gDffFXRW6sl/HCwbta7zK4uNqbPjv4YJEAdz7Vu+FLQHe77eZ4bvbJGi4hE0QPeJlMYMA3piXEr1UL3dAwx7Q==",
+      "requires": {
+        "stubborn-fs": "^2.0.0",
+        "when-exit": "^2.1.4"
+      }
+    },
     "autoprefixer": {
       "version": "10.4.19",
       "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.19.tgz",
@@ -26299,6 +26558,58 @@
       "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
       "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
     },
+    "conf": {
+      "version": "15.0.2",
+      "resolved": "https://registry.npmjs.org/conf/-/conf-15.0.2.tgz",
+      "integrity": "sha512-JBSrutapCafTrddF9dH3lc7+T2tBycGF4uPkI4Js+g4vLLEhG6RZcFi3aJd5zntdf5tQxAejJt8dihkoQ/eSJw==",
+      "requires": {
+        "ajv": "^8.17.1",
+        "ajv-formats": "^3.0.1",
+        "atomically": "^2.0.3",
+        "debounce-fn": "^6.0.0",
+        "dot-prop": "^10.0.0",
+        "env-paths": "^3.0.0",
+        "json-schema-typed": "^8.0.1",
+        "semver": "^7.7.2",
+        "uint8array-extras": "^1.5.0"
+      },
+      "dependencies": {
+        "ajv": {
+          "version": "8.17.1",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
+          "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
+          "requires": {
+            "fast-deep-equal": "^3.1.3",
+            "fast-uri": "^3.0.1",
+            "json-schema-traverse": "^1.0.0",
+            "require-from-string": "^2.0.2"
+          }
+        },
+        "ajv-formats": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
+          "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+          "requires": {
+            "ajv": "^8.0.0"
+          }
+        },
+        "env-paths": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
+          "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="
+        },
+        "json-schema-traverse": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+          "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
+        },
+        "semver": {
+          "version": "7.7.3",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+          "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="
+        }
+      }
+    },
     "config-file-ts": {
       "version": "0.2.6",
       "resolved": "https://registry.npmmirror.com/config-file-ts/-/config-file-ts-0.2.6.tgz",
@@ -26763,6 +27074,14 @@
       "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==",
       "dev": true
     },
+    "debounce-fn": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-6.0.0.tgz",
+      "integrity": "sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==",
+      "requires": {
+        "mimic-function": "^5.0.0"
+      }
+    },
     "debug": {
       "version": "4.3.4",
       "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz",
@@ -27196,6 +27515,24 @@
         "tslib": "^2.0.3"
       }
     },
+    "dot-prop": {
+      "version": "10.1.0",
+      "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-10.1.0.tgz",
+      "integrity": "sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q==",
+      "requires": {
+        "type-fest": "^5.0.0"
+      },
+      "dependencies": {
+        "type-fest": {
+          "version": "5.3.1",
+          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.3.1.tgz",
+          "integrity": "sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==",
+          "requires": {
+            "tagged-tag": "^1.0.0"
+          }
+        }
+      }
+    },
     "dotenv": {
       "version": "10.0.0",
       "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-10.0.0.tgz",
@@ -27463,6 +27800,25 @@
         }
       }
     },
+    "electron-store": {
+      "version": "11.0.2",
+      "resolved": "https://registry.npmjs.org/electron-store/-/electron-store-11.0.2.tgz",
+      "integrity": "sha512-4VkNRdN+BImL2KcCi41WvAYbh6zLX5AUTi4so68yPqiItjbgTjqpEnGAqasgnG+lB6GuAyUltKwVopp6Uv+gwQ==",
+      "requires": {
+        "conf": "^15.0.2",
+        "type-fest": "^5.0.1"
+      },
+      "dependencies": {
+        "type-fest": {
+          "version": "5.3.1",
+          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.3.1.tgz",
+          "integrity": "sha512-VCn+LMHbd4t6sF3wfU/+HKT63C9OoyrSIf4b+vtWHpt2U7/4InZG467YDNMFMR70DdHjAdpPWmw2lzRdg0Xqqg==",
+          "requires": {
+            "tagged-tag": "^1.0.0"
+          }
+        }
+      }
+    },
     "electron-to-chromium": {
       "version": "1.4.736",
       "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.736.tgz",
@@ -28336,8 +28692,7 @@
     "fast-deep-equal": {
       "version": "3.1.3",
       "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
-      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
-      "dev": true
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
     },
     "fast-diff": {
       "version": "1.3.0",
@@ -28369,6 +28724,11 @@
       "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
       "dev": true
     },
+    "fast-uri": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
+      "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="
+    },
     "fastest-levenshtein": {
       "version": "1.0.16",
       "resolved": "https://registry.npmmirror.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz",
@@ -30018,6 +30378,11 @@
       "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
       "dev": true
     },
+    "json-schema-typed": {
+      "version": "8.0.2",
+      "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
+      "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="
+    },
     "json-stable-stringify": {
       "version": "1.1.1",
       "resolved": "https://registry.npmmirror.com/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz",
@@ -31099,6 +31464,11 @@
       "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
       "dev": true
     },
+    "mimic-function": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
+      "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="
+    },
     "mimic-response": {
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-1.0.1.tgz",
@@ -33321,8 +33691,7 @@
     "require-from-string": {
       "version": "2.0.2",
       "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz",
-      "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
-      "dev": true
+      "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
     },
     "requires-port": {
       "version": "1.0.0",
@@ -34383,6 +34752,19 @@
       "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
       "dev": true
     },
+    "stubborn-fs": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz",
+      "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==",
+      "requires": {
+        "stubborn-utils": "^1.0.1"
+      }
+    },
+    "stubborn-utils": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz",
+      "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg=="
+    },
     "style-search": {
       "version": "0.1.0",
       "resolved": "https://registry.npmmirror.com/style-search/-/style-search-0.1.0.tgz",
@@ -35221,6 +35603,11 @@
         }
       }
     },
+    "tagged-tag": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz",
+      "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="
+    },
     "tapable": {
       "version": "2.2.1",
       "resolved": "https://registry.npmmirror.com/tapable/-/tapable-2.2.1.tgz",
@@ -35696,6 +36083,11 @@
       "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
       "dev": true
     },
+    "uint8array-extras": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz",
+      "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="
+    },
     "unbox-primitive": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
@@ -36496,6 +36888,11 @@
         "webidl-conversions": "^3.0.0"
       }
     },
+    "when-exit": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz",
+      "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg=="
+    },
     "which": {
       "version": "1.3.1",
       "resolved": "https://registry.npmmirror.com/which/-/which-1.3.1.tgz",

+ 2 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "eep_page_reader",
-  "version": "2025.11.28",
+  "version": "2025.12.18",
   "private": true,
   "main": "main.js",
   "description": "智慧梧桐数字教材阅读器",
@@ -27,6 +27,7 @@
     "cnchar": "^3.2.6",
     "core-js": "^3.37.1",
     "dompurify": "^3.1.5",
+    "electron-store": "^11.0.2",
     "element-ui": "^2.15.14",
     "hanzi-writer": "^3.7.0",
     "jquery": "^3.7.1",

+ 23 - 0
preload.js

@@ -56,9 +56,32 @@ contextBridge.exposeInMainWorld('fileAPI', {
   },
 
   /**
+   * 修改 .eep 压缩文件中的某个文件内容
+   * @param {string} archivePath EEP 文件路径
+   * @param {string} entryPath 压缩包内文件路径
+   * @param {Buffer} content 新文件内容
+   */
+  modifyEepFileSync: async (archivePath, entryPath, content, encoding = 'utf8', password = '') => {
+    return await ipcRenderer.invoke('zip:update-entry-with-7z', {
+      archivePath,
+      entryPath,
+      content,
+      encoding,
+      password,
+    });
+  },
+
+  /**
    * 打开文件对话框
    * @param {Object} opts 对话框选项
    * @returns {Promise} 文件路径
    */
   openFileDialog: (opts) => ipcRenderer.invoke('dialog:openFiles', opts),
 });
+
+// 简单的键值存储 API
+contextBridge.exposeInMainWorld('electronStore', {
+  get: (key) => ipcRenderer.invoke('store:get', key),
+  set: (key, value) => ipcRenderer.invoke('store:set', key, value),
+  delete: (key) => ipcRenderer.invoke('store:delete', key),
+});

+ 15 - 0
src/api/app.js

@@ -104,3 +104,18 @@ export function LearnWebSI(MethodName, data) {
 export function CreateBookPreviewURL(data) {
   return http.post(`${process.env.VUE_APP_EepServer}?MethodName=book_preview_manager-CreateBookPreviewURL`, data);
 }
+
+/**
+ * 教材离线包激活
+ * @param {object} data 请求数据
+ * @param {string} data.auth_code 离线包授权码
+ * @param {string} data.host_address 主机地址
+ */
+export function BookOfflinePackActivate({ auth_code, host_address }) {
+  return http.post(
+    `${host_address}${process.env.VUE_APP_EepServer}?MethodName=offline_pack_manager-BookOfflinePackActivate`,
+    {
+      auth_code,
+    },
+  );
+}

+ 0 - 1
src/api/list.js

@@ -73,4 +73,3 @@ export function PageQueryYSJBookList_OrgManager(data) {
 export function PageQueryProjectResourceList(data) {
   return http.post(`${process.env.VUE_APP_EepServer}?MethodName=page_query-PageQueryProjectResourceList`, data);
 }
-

+ 97 - 2
src/components/CommonPreview.vue

@@ -226,6 +226,8 @@ import * as OpenCC from 'opencc-js';
 
 import { GetProjectBaseInfo } from '@/api/project';
 import { MangerGetBookMindMap, PageQueryBookResourceList } from '@/api/book';
+import { setBlockAllRequests } from '@/utils/http';
+import { BookOfflinePackActivate } from '@/api/app';
 
 export default {
   name: 'CommonPreview',
@@ -254,6 +256,10 @@ export default {
       type: String,
       required: true,
     },
+    filePath: {
+      type: String,
+      required: true,
+    },
     projectId: {
       type: String,
       required: true,
@@ -287,6 +293,7 @@ export default {
     }
 
     return {
+      storeName: 'courseActivateEffectiveData', // 存储离线包激活信息的store名称
       select_node: this.id,
       courseware_info: {
         book_name: '',
@@ -388,6 +395,10 @@ export default {
       async handler(val) {
         if (!val || !val.length) return;
 
+        const auth = await this.readFileContent('authorization.json'); // 读取授权文件
+        const activated = await this.ensureOfflineActivated(auth);
+        if (!activated) return;
+
         this.coursewareFileList = val
           .filter(({ entryName, isDirectory }) => {
             if (isDirectory) return false;
@@ -440,8 +451,92 @@ export default {
     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;
+      try {
+        const obj = JSON.parse(text);
+        return obj;
+      } catch (e) {
+        return text;
+      }
+    },
+
+    /**
+     * 处理离线包激活逻辑
+     * @param {Object} auth - 授权对象
+     * @param {string} auth.auth_code - 授权码
+     * @param {string} auth.host_address - 主机地址
+     * @param {string} auth.is_activated - 是否已激活
+     * @returns {Promise<boolean>} - 激活成功或已可用返回 true,否则返回 false
+     */
+    async ensureOfflineActivated(auth = {}) {
+      const { auth_code, host_address, is_activated } = auth || {};
+      // 离线包已激活
+      if (isTrue(is_activated)) {
+        let storeData = await window.electronStore.get(this.storeName);
+        let info = storeData && storeData[auth_code];
+        if (info) {
+          const now = Date.now();
+          const endDate = new Date(info.effective_end_date).getTime();
+          if (now > endDate) {
+            this.$message.warning('当前离线包激活已过期');
+            return false;
+          }
+          if (info.effective_count <= 0) {
+            this.$message.warning('当前离线包激活次数已用完');
+            return false;
+          }
+          info.effective_count -= 1;
+          storeData[auth_code] = info;
+          await window.electronStore.set(this.storeName, storeData);
+          return true;
+        }
+        this.$message.warning('当前离线包激活信息异常,请重新激活');
+        return false;
+      }
+
+      // 未激活,尝试在线激活
+      if (!window.navigator.onLine) {
+        this.$message.warning('当前离线包未激活,请连接网络后重新打开');
+        return false;
+      }
+      setBlockAllRequests(false);
+      try {
+        const { effective_count, effective_end_date } = await BookOfflinePackActivate({ auth_code, host_address });
+        this.$message.success('离线包激活成功');
+        setBlockAllRequests(true);
+        await this.modifyEepFileData(
+          'authorization.json',
+          JSON.stringify({
+            auth_code,
+            host_address,
+            is_activated: 'true',
+          }),
+        );
+        let storeData = await window.electronStore.get(this.storeName);
+        if (!storeData) {
+          storeData = {};
+        }
+        storeData[auth_code] = {
+          effective_count,
+          effective_end_date,
+        };
+        await window.electronStore.set(this.storeName, storeData);
+        return true;
+      } catch (e) {
+        console.error(e);
+        this.$message.error(`离线包激活失败: ${e && e.message ? e.message : String(e)}`);
+        return false;
+      }
+    },
+
+    /**
+     * 修改 eep 文件内部某个文件内容
+     * @param {string} entryPath 相对文件路径
+     * @param {Uint8Array} newData 新文件数据
+     * @param {string} encoding 编码格式
+     * @param {string} password 压缩包密码
+     */
+    async modifyEepFileData(entryPath, newData, encoding = 'utf-8', password = '1234567a') {
+      await window.fileAPI.modifyEepFileSync(this.filePath, entryPath, newData, encoding, password);
     },
 
     getProjectBaseInfo() {

+ 2 - 2
src/utils/http.js

@@ -5,7 +5,7 @@ import router from '@/router';
 import { getToken, getLocalStore } from '@/utils/auth';
 import { Message } from 'element-ui';
 
-// 全局开关:拦截所有请求(默认开启)
+// 全局开关:拦截所有请求
 let BLOCK_ALL_REQUESTS = true;
 
 // 设置拦截开关(外部可调用)
@@ -17,7 +17,7 @@ export function isBlockAllRequests() {
   return BLOCK_ALL_REQUESTS;
 }
 const service = axios.create({
-  baseURL: getLocalStore('server_address') || process.env.VUE_APP_EEP,
+  baseURL: '',
   timeout: 60000,
 });
 

+ 1 - 1
src/views/eep_view/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="eep-viewer">
-    <CommonPreview :entries="entries" :temp-dir="tempDir" />
+    <CommonPreview :entries="entries" :temp-dir="tempDir" :file-path="filePath" />
   </div>
 </template>